openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
96 lines (95 loc) • 4.39 kB
JavaScript
import { j as resolveTimerTimeoutMs, n as MAX_TIMER_TIMEOUT_MS, s as asFiniteNumber } from "./number-coercion-CJQ8TR--.js";
import { _ as sleep } from "./utils-CCC-BEJH.js";
import "./number-coercion-Z7n6tXLk.js";
import { t as generateSecureFraction } from "./secure-random-Ds4AFLgz.js";
//#region src/infra/retry.ts
const DEFAULT_RETRY_CONFIG = {
attempts: 3,
minDelayMs: 300,
maxDelayMs: 3e4,
jitter: 0
};
const clampNumber = (value, fallback, min, max) => {
const next = asFiniteNumber(value);
if (next === void 0) return fallback;
const floor = typeof min === "number" ? min : Number.NEGATIVE_INFINITY;
const ceiling = typeof max === "number" ? max : Number.POSITIVE_INFINITY;
return Math.min(Math.max(next, floor), ceiling);
};
function resolveAttemptCount(value, fallback) {
return Math.max(1, Math.round(typeof value === "number" && Number.isFinite(value) ? value : fallback));
}
function resolveRetryDelayMs(value) {
if (value === Number.POSITIVE_INFINITY) return MAX_TIMER_TIMEOUT_MS;
return resolveTimerTimeoutMs(value, 0, 0);
}
/** Resolves retry config overrides into clamped timer-safe settings. */
function resolveRetryConfig(defaults = DEFAULT_RETRY_CONFIG, overrides) {
const attempts = resolveAttemptCount(clampNumber(overrides?.attempts, defaults.attempts, 1), defaults.attempts);
const minDelayMs = resolveRetryDelayMs(Math.round(clampNumber(overrides?.minDelayMs, defaults.minDelayMs, 0)));
return {
attempts,
minDelayMs,
maxDelayMs: Math.max(minDelayMs, resolveRetryDelayMs(Math.round(clampNumber(overrides?.maxDelayMs, defaults.maxDelayMs, 0)))),
jitter: clampNumber(overrides?.jitter, defaults.jitter, 0, 1)
};
}
function applyJitter(delayMs, jitter, mode = "symmetric") {
if (jitter <= 0) return delayMs;
const fraction = generateSecureFraction();
const raw = delayMs * (1 + (mode === "positive" ? fraction * jitter : (fraction * 2 - 1) * jitter));
return Math.max(0, mode === "positive" ? Math.ceil(raw) : Math.round(raw));
}
/** Runs an async operation until it succeeds, retry policy stops, or attempts are exhausted. */
async function retryAsync(fn, attemptsOrOptions = 3, initialDelayMs = 300) {
if (typeof attemptsOrOptions === "number") {
const attempts = resolveAttemptCount(attemptsOrOptions, DEFAULT_RETRY_CONFIG.attempts);
let lastErr;
for (let i = 0; i < attempts; i += 1) try {
return await fn();
} catch (err) {
lastErr = err;
if (i === attempts - 1) break;
await sleep(resolveRetryDelayMs(initialDelayMs * 2 ** i));
}
throw toLintErrorObject(lastErr ?? /* @__PURE__ */ new Error("Retry failed"), "Non-Error thrown");
}
const options = attemptsOrOptions;
const resolved = resolveRetryConfig(DEFAULT_RETRY_CONFIG, options);
const maxAttempts = resolved.attempts;
const minDelayMs = resolved.minDelayMs;
const maxDelayMs = Number.isFinite(resolved.maxDelayMs) && resolved.maxDelayMs > 0 ? resolved.maxDelayMs : Number.POSITIVE_INFINITY;
const jitter = resolved.jitter;
const shouldRetry = options.shouldRetry ?? (() => true);
let lastErr;
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) try {
return await fn();
} catch (err) {
lastErr = err;
if (attempt >= maxAttempts || !shouldRetry(err, attempt)) break;
const retryAfterMs = options.retryAfterMs?.(err);
const hasRetryAfter = typeof retryAfterMs === "number" && Number.isFinite(retryAfterMs);
const baseDelay = hasRetryAfter ? Math.max(retryAfterMs, minDelayMs) : minDelayMs * 2 ** (attempt - 1);
let delay = Math.min(baseDelay, maxDelayMs);
delay = applyJitter(delay, jitter, hasRetryAfter && typeof retryAfterMs === "number" && retryAfterMs <= maxDelayMs ? "positive" : "symmetric");
delay = Math.min(Math.max(delay, minDelayMs), maxDelayMs);
options.onRetry?.({
attempt,
maxAttempts,
delayMs: delay,
err,
label: options.label
});
if (delay > 0) await sleep(delay);
}
throw toLintErrorObject(lastErr ?? /* @__PURE__ */ new Error("Retry failed"), "Non-Error thrown");
}
function toLintErrorObject(value, fallbackMessage) {
if (value instanceof Error) return value;
if (typeof value === "string") return new Error(value);
const error = new Error(fallbackMessage, { cause: value });
if (typeof value === "object" && value !== null || typeof value === "function") Object.assign(error, value);
return error;
}
//#endregion
export { retryAsync as n, resolveRetryConfig as t };