* Executes an operation with exponential backoff retry logic * * @param operation - Operation name for logging * @param attemptFn - Function to execute on each attempt, returns RetryResult * @returns The successful result value * @throws Error if all retries exhausted
( operation: string, attemptFn: (attempt: number) => Promise<RetryResult<T>>, )
| 95 | * @throws Error if all retries exhausted |
| 96 | */ |
| 97 | async function retryWithBackoff<T>( |
| 98 | operation: string, |
| 99 | attemptFn: (attempt: number) => Promise<RetryResult<T>>, |
| 100 | ): Promise<T> { |
| 101 | let lastError = '' |
| 102 | |
| 103 | for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) { |
| 104 | const result = await attemptFn(attempt) |
| 105 | |
| 106 | if (result.done) { |
| 107 | return result.value |
| 108 | } |
| 109 | |
| 110 | lastError = result.error || `${operation} failed` |
| 111 | logDebug( |
| 112 | `${operation} attempt ${attempt}/${MAX_RETRIES} failed: ${lastError}`, |
| 113 | ) |
| 114 | |
| 115 | if (attempt < MAX_RETRIES) { |
| 116 | const delayMs = BASE_DELAY_MS * Math.pow(2, attempt - 1) |
| 117 | logDebug(`Retrying ${operation} in ${delayMs}ms...`) |
| 118 | await sleep(delayMs) |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | throw new Error(`${lastError} after ${MAX_RETRIES} attempts`) |
| 123 | } |
| 124 | |
| 125 | /** |
| 126 | * Downloads a single file from the Anthropic Public Files API |
no test coverage detected