package proxy import ( "net/http" "strings" ) // isRetriable reports whether an upstream status code should trigger failover // to the next account before any bytes are streamed to the client. // // 429 — rate limit / usage limit (retryable cooldown) // 402 — payment required; treated as retryable when the body text matches a // // usage-window pattern ("usage limit", "limit reached", "exhausted", // "daily/weekly/monthly limit"). A bare "insufficient credits" is also // retriable (billing disable) — we still fail over, just with a longer // cooldown. // // 5xx — transient server error // 408 — request timeout // Other 4xx are not retriable: the request is malformed or the model is // unknown, so retrying on another account would fail identically. func isRetriable(status int) bool { switch { case status == http.StatusTooManyRequests: return true case status == http.StatusPaymentRequired: return true case status >= 500: return true case status == http.StatusRequestTimeout: return true default: return false } } // shouldCooldown reports whether an account should be marked cooldown after a // retriable failure. 5xx does not trigger cooldown (the error may be // transient and unrelated to this account's quota). 429 and 402 always do. func shouldCooldown(status int, bodyPreview string) bool { switch { case status == http.StatusTooManyRequests: return true case status == http.StatusPaymentRequired: return true default: return false } } // isBillingDisable reports whether the error body indicates a billing/credit // exhaustion (longer cooldown) rather than a transient usage-window limit. // Used for logging/diagnostics; the cooldown duration is the same in v1. func isBillingDisable(bodyPreview string) bool { low := strings.ToLower(bodyPreview) for _, marker := range []string{"insufficient credits", "credit balance", "billing"} { if strings.Contains(low, marker) { return true } } return false }