dify/cli/src/http/retry.ts
L1nSn0w 07eb4903b8
feat: 429 rate-limit handling on the unified ErrorBody contract (openapi + difyctl) (#37313)
Co-authored-by: Xiyuan Chen <52963600+GareArc@users.noreply.github.com>
2026-06-12 06:35:15 +00:00

38 lines
1.5 KiB
TypeScript

import type { FetchContext } from './types.js'
export const RETRY_METHODS = ['GET', 'PUT', 'DELETE'] as const
// 429 is intentionally absent — it has a dedicated branch in execute(). shouldRetry covers
// transport errors / 408 / 413 / 5xx only.
export const RETRY_STATUS_CODES = [408, 413, 500, 502, 503, 504] as const
const RETRY_METHODS_SET: ReadonlySet<string> = new Set(RETRY_METHODS)
const RETRY_STATUS_SET: ReadonlySet<number> = new Set(RETRY_STATUS_CODES)
// GET/PUT/DELETE are idempotent — safe to auto-retry. The 429 branch reuses this to decide which
// methods may wait-and-retry a throttle without risking a double-run.
export function isIdempotentRetryMethod(method: string): boolean {
return RETRY_METHODS_SET.has(method)
}
export function shouldRetry(target: Response | unknown, ctx: FetchContext): boolean {
if (!RETRY_METHODS_SET.has(ctx.options.method))
return false
if (target instanceof Response)
return RETRY_STATUS_SET.has(target.status)
// Any other transport error on a retryable method retries. User aborts are filtered
// out earlier in dispatch (before this hook ever runs), so they never reach here.
return true
}
// Exponential backoff: 300ms base, doubling each attempt, capped at 30s
// (300ms, 600ms, 1.2s, ...).
const BACKOFF_BASE_MS = 300
const BACKOFF_CAP_MS = 30_000
export function backoffDelay(attempt: number): number {
if (attempt <= 0)
return 0
const exp = BACKOFF_BASE_MS * 2 ** (attempt - 1)
return Math.min(exp, BACKOFF_CAP_MS)
}