(fn: () => Promise<T>, opts: BackoffOptions = {})
| 307 | * async op (e.g. `agent.prompt()` in the pi-agent-core path). |
| 308 | */ |
| 309 | export async function withBackoff<T>(fn: () => Promise<T>, opts: BackoffOptions = {}): Promise<T> { |
| 310 | const maxRetries = opts.maxRetries ?? DEFAULT_MAX_RETRIES; |
| 311 | const baseDelayMs = opts.baseDelayMs ?? DEFAULT_BASE_DELAY_MS; |
| 312 | const classify = opts.classify ?? classifyError; |
| 313 | const signal = opts.signal; |
| 314 | |
| 315 | let lastError: unknown; |
| 316 | for (let attempt = 1; attempt <= maxRetries; attempt++) { |
| 317 | if (signal?.aborted) { |
| 318 | throw new CodesignError('Generation aborted by user', ERROR_CODES.PROVIDER_ABORTED); |
| 319 | } |
| 320 | try { |
| 321 | return await fn(); |
| 322 | } catch (err) { |
| 323 | lastError = err; |
| 324 | const decision = classify(err); |
| 325 | if (shouldStop(decision, attempt, maxRetries)) { |
| 326 | if (decision.reason === 'aborted') { |
| 327 | throw new CodesignError('Generation aborted by user', ERROR_CODES.PROVIDER_ABORTED, { |
| 328 | cause: err, |
| 329 | }); |
| 330 | } |
| 331 | throw err; |
| 332 | } |
| 333 | const info = buildRetryInfo(attempt, maxRetries, decision, baseDelayMs); |
| 334 | opts.onRetry?.(info); |
| 335 | await sleepWithAbort(info.delayMs, signal); |
| 336 | } |
| 337 | } |
| 338 | throw lastError instanceof Error |
| 339 | ? lastError |
| 340 | : new CodesignError('withBackoff exhausted', ERROR_CODES.PROVIDER_RETRY_EXHAUSTED); |
| 341 | } |
| 342 | |
| 343 | export async function completeWithRetry( |
| 344 | model: ModelRef, |
no test coverage detected