Handle Codex capacity errors as retryable

This commit is contained in:
pjpj
2026-03-25 23:25:31 +08:00
parent 76c064c729
commit 36973d4a6f
2 changed files with 41 additions and 2 deletions
+28 -2
View File
@@ -685,13 +685,39 @@ func applyCodexHeaders(r *http.Request, auth *cliproxyauth.Auth, token string, s
} }
func newCodexStatusErr(statusCode int, body []byte) statusErr { func newCodexStatusErr(statusCode int, body []byte) statusErr {
err := statusErr{code: statusCode, msg: string(body)} errCode := statusCode
if retryAfter := parseCodexRetryAfter(statusCode, body, time.Now()); retryAfter != nil { if isCodexModelCapacityError(body) {
errCode = http.StatusTooManyRequests
}
err := statusErr{code: errCode, msg: string(body)}
if retryAfter := parseCodexRetryAfter(errCode, body, time.Now()); retryAfter != nil {
err.retryAfter = retryAfter err.retryAfter = retryAfter
} }
return err return err
} }
func isCodexModelCapacityError(errorBody []byte) bool {
if len(errorBody) == 0 {
return false
}
candidates := []string{
gjson.GetBytes(errorBody, "error.message").String(),
gjson.GetBytes(errorBody, "message").String(),
string(errorBody),
}
for _, candidate := range candidates {
lower := strings.ToLower(strings.TrimSpace(candidate))
if lower == "" {
continue
}
if strings.Contains(lower, "selected model is at capacity") ||
strings.Contains(lower, "model is at capacity. please try a different model") {
return true
}
}
return false
}
func parseCodexRetryAfter(statusCode int, errorBody []byte, now time.Time) *time.Duration { func parseCodexRetryAfter(statusCode int, errorBody []byte, now time.Time) *time.Duration {
if statusCode != http.StatusTooManyRequests || len(errorBody) == 0 { if statusCode != http.StatusTooManyRequests || len(errorBody) == 0 {
return nil return nil
@@ -60,6 +60,19 @@ func TestParseCodexRetryAfter(t *testing.T) {
}) })
} }
func TestNewCodexStatusErrTreatsCapacityAsRetryableRateLimit(t *testing.T) {
body := []byte(`{"error":{"message":"Selected model is at capacity. Please try a different model."}}`)
err := newCodexStatusErr(http.StatusBadRequest, body)
if got := err.StatusCode(); got != http.StatusTooManyRequests {
t.Fatalf("status code = %d, want %d", got, http.StatusTooManyRequests)
}
if err.RetryAfter() != nil {
t.Fatalf("expected nil explicit retryAfter for capacity fallback, got %v", *err.RetryAfter())
}
}
func itoa(v int64) string { func itoa(v int64) string {
return strconv.FormatInt(v, 10) return strconv.FormatInt(v, 10)
} }