-
Notifications
You must be signed in to change notification settings - Fork 35
Expand file tree
/
Copy pathopenai-bridge-retry.js
More file actions
70 lines (63 loc) · 2.54 KB
/
Copy pathopenai-bridge-retry.js
File metadata and controls
70 lines (63 loc) · 2.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
const DEFAULT_BRIDGE_MAX_RETRIES = Infinity;
const BASE_TRANSIENT_RETRY_DELAY_MS = 200;
const MAX_TRANSIENT_RETRY_DELAY_MS = 5000;
function normalizeBridgeMaxRetries(value, fallback = DEFAULT_BRIDGE_MAX_RETRIES) {
const raw = Number(value);
const fallbackRaw = Number(fallback);
if (Number.isFinite(raw) && raw >= 0) return Math.floor(raw);
if (Number.isFinite(fallbackRaw) && fallbackRaw >= 0) return Math.floor(fallbackRaw);
return DEFAULT_BRIDGE_MAX_RETRIES;
}
function isTransientHttpStatus(status) {
const code = Number(status);
if (code === 408 || code === 409 || code === 425 || code === 429) return true;
return code >= 500 && code <= 599;
}
function isTransientNetworkError(error) {
const text = String(error || '').trim();
if (!text) return false;
if (/socket hang up/i.test(text)) return true;
if (/ECONNRESET|ECONNREFUSED|EPIPE|EPROTO|ETIMEDOUT/i.test(text)) return true;
if (/EAI_AGAIN/i.test(text)) return true;
if (/UND_ERR_SOCKET/i.test(text)) return true;
if (/disconnected before|secure tls|tls handshake/i.test(text)) return true;
return false;
}
function getTransientRetryDelayMs(attempt) {
const index = Math.max(0, Number(attempt) - 1);
return Math.min(MAX_TRANSIENT_RETRY_DELAY_MS, BASE_TRANSIENT_RETRY_DELAY_MS * Math.pow(3, index));
}
async function retryTransientRequest(executor, options = {}) {
const maxRetries = normalizeBridgeMaxRetries(options && options.maxRetries);
let lastResult = null;
for (let attempt = 0; attempt === 0 || attempt <= maxRetries; attempt += 1) {
if (attempt > 0) {
const delay = getTransientRetryDelayMs(attempt);
// eslint-disable-next-line no-await-in-loop
await new Promise((r) => {
const t = setTimeout(r, delay);
if (typeof t.unref === 'function') t.unref();
});
}
// eslint-disable-next-line no-await-in-loop
const result = await executor(attempt);
lastResult = result;
if (!result) return result;
if (result.status && result.status > 0) {
if (!isTransientHttpStatus(result.status)) return result;
continue;
}
if (result.ok) return result;
if (result.retry) return result;
if (!isTransientNetworkError(result.error)) return result;
}
return lastResult;
}
module.exports = {
DEFAULT_BRIDGE_MAX_RETRIES,
normalizeBridgeMaxRetries,
isTransientNetworkError,
isTransientHttpStatus,
getTransientRetryDelayMs,
retryTransientRequest
};