fix(core): reduce default API timeout to 60s and enable retries for undici timeouts (#26191)

This commit is contained in:
Adib234
2026-04-29 16:05:45 -04:00
committed by GitHub
parent 25f422d0e4
commit 99235fc59d
4 changed files with 33 additions and 9 deletions
+1 -1
View File
@@ -209,7 +209,7 @@ describe('fetch utils', () => {
expect(ProxyAgent).toHaveBeenCalledWith({
uri: proxyUrl,
headersTimeout: 45773134,
bodyTimeout: 45773134,
bodyTimeout: 300000,
});
expect(setGlobalDispatcher).toHaveBeenCalled();
});
+10 -8
View File
@@ -28,14 +28,15 @@ export class PrivateIpError extends Error {
}
}
let defaultTimeout = 300000; // 5 minutes
let defaultHeadersTimeout = 60000; // 60 seconds
const defaultBodyTimeout = 300000; // 5 minutes
let currentProxy: string | undefined = undefined;
// Configure default global dispatcher with higher timeouts
setGlobalDispatcher(
new Agent({
headersTimeout: defaultTimeout,
bodyTimeout: defaultTimeout,
headersTimeout: defaultHeadersTimeout,
bodyTimeout: defaultBodyTimeout,
}),
);
@@ -45,14 +46,15 @@ export function updateGlobalFetchTimeouts(timeoutMs: number) {
`Invalid timeout value: ${timeoutMs}. Must be a positive finite number.`,
);
}
defaultTimeout = timeoutMs;
defaultHeadersTimeout = timeoutMs;
// We keep body timeout high for LLM streaming responses
if (currentProxy) {
setGlobalProxy(currentProxy);
} else {
setGlobalDispatcher(
new Agent({
headersTimeout: defaultTimeout,
bodyTimeout: defaultTimeout,
headersTimeout: defaultHeadersTimeout,
bodyTimeout: defaultBodyTimeout,
}),
);
}
@@ -214,8 +216,8 @@ export function setGlobalProxy(proxy: string) {
setGlobalDispatcher(
new ProxyAgent({
uri: proxy,
headersTimeout: defaultTimeout,
bodyTimeout: defaultTimeout,
headersTimeout: defaultHeadersTimeout,
bodyTimeout: defaultBodyTimeout,
}),
);
}
+19
View File
@@ -451,6 +451,25 @@ describe('retryWithBackoff', () => {
});
await vi.runAllTimersAsync();
await expect(promise).resolves.toBe('success');
expect(mockFn).toHaveBeenCalledTimes(2);
});
it('should retry on undici timeout error codes (UND_ERR_HEADERS_TIMEOUT)', async () => {
const error = new Error('Headers timeout error');
(error as any).code = 'UND_ERR_HEADERS_TIMEOUT';
const mockFn = vi
.fn()
.mockRejectedValueOnce(error)
.mockResolvedValue('success');
const promise = retryWithBackoff(mockFn, {
retryFetchErrors: false,
initialDelayMs: 1,
maxDelayMs: 1,
});
await vi.runAllTimersAsync();
await expect(promise).resolves.toBe('success');
expect(mockFn).toHaveBeenCalledTimes(2);
});
it('should retry on SSL error code (ERR_SSL_SSLV3_ALERT_BAD_RECORD_MAC)', async () => {
+3
View File
@@ -55,6 +55,9 @@ const RETRYABLE_NETWORK_CODES = [
'ECONNREFUSED',
'ERR_SSL_WRONG_VERSION_NUMBER',
'EPROTO', // Generic protocol error (often SSL-related)
'UND_ERR_HEADERS_TIMEOUT',
'UND_ERR_BODY_TIMEOUT',
'UND_ERR_CONNECT_TIMEOUT',
];
// Node.js builds SSL error codes by prepending ERR_SSL_ to the uppercased