(
config: RunnerConfig,
operationName: string,
run: (attempt: number, signal: AbortSignal) => Promise<T>,
)
| 276 | } |
| 277 | |
| 278 | async function withRetry<T>( |
| 279 | config: RunnerConfig, |
| 280 | operationName: string, |
| 281 | run: (attempt: number, signal: AbortSignal) => Promise<T>, |
| 282 | ): Promise<T> { |
| 283 | let lastError: Error | null = null; |
| 284 | |
| 285 | for (let attempt = 1; attempt <= config.maxRequestAttempts; attempt += 1) { |
| 286 | const controller = new AbortController(); |
| 287 | const timeout = setTimeout(() => controller.abort(), config.openCodeRequestTimeoutMs); |
| 288 | try { |
| 289 | return await run(attempt, controller.signal); |
| 290 | } catch (error) { |
| 291 | lastError = asError(error); |
| 292 | clearTimeout(timeout); |
| 293 | if (attempt >= config.maxRequestAttempts) break; |
| 294 | const retryDelay = config.retryBaseDelayMs * 2 ** (attempt - 1); |
| 295 | logLine( |
| 296 | `${operationName} failed on attempt ${attempt}/${config.maxRequestAttempts}: ${lastError.message}; retrying in ${retryDelay}ms`, |
| 297 | ); |
| 298 | await sleep(retryDelay); |
| 299 | |
| 300 | } finally { |
| 301 | clearTimeout(timeout); |
| 302 | } |
| 303 | } |
| 304 | |
| 305 | throw lastError ?? new Error(`${operationName} failed`); |
| 306 | } |
| 307 | |
| 308 | async function createSession( |
| 309 | client: OpencodeClient, |
no test coverage detected