(error: any)
| 4 | |
| 5 | // Since the functions are not exported, we need to recreate them for testing |
| 6 | function isRetryableError(error: any): boolean { |
| 7 | // Handle null/undefined |
| 8 | if (!error) { |
| 9 | return false; |
| 10 | } |
| 11 | |
| 12 | // Network errors are retryable |
| 13 | if ( |
| 14 | error.code === "ECONNRESET" || |
| 15 | error.code === "ENOTFOUND" || |
| 16 | error.code === "ETIMEDOUT" || |
| 17 | error.code === "EPIPE" || |
| 18 | error.code === "ECONNREFUSED" |
| 19 | ) { |
| 20 | return true; |
| 21 | } |
| 22 | |
| 23 | // HTTP status codes that are retryable |
| 24 | if (error.status) { |
| 25 | const status = error.status; |
| 26 | // 429 (Too Many Requests), 502 (Bad Gateway), 503 (Service Unavailable), 504 (Gateway Timeout) |
| 27 | return status === 429 || status === 502 || status === 503 || status === 504; |
| 28 | } |
| 29 | |
| 30 | // OpenAI specific errors |
| 31 | if (error.type === "server_error" || error.type === "rate_limit_exceeded") { |
| 32 | return true; |
| 33 | } |
| 34 | |
| 35 | // Anthropic specific errors |
| 36 | const lower = error.message?.toLowerCase(); |
| 37 | if (lower?.includes("overloaded")) { |
| 38 | return true; |
| 39 | } |
| 40 | |
| 41 | // Check for premature close errors by message content |
| 42 | if ( |
| 43 | lower?.includes("premature close") || |
| 44 | lower?.includes("premature end") || |
| 45 | lower?.includes("connection reset") || |
| 46 | lower?.includes("socket hang up") || |
| 47 | lower?.includes("aborted") |
| 48 | ) { |
| 49 | return true; |
| 50 | } |
| 51 | |
| 52 | return false; |
| 53 | } |
| 54 | |
| 55 | function calculateDelay( |
| 56 | attempt: number, |
no outgoing calls
no test coverage detected