* Consume a model stream, automatically re-establishing the request up to * MAX_STREAM_RETRIES times on a transient/connection failure. A user abort is * never retried. * * Before the first chunk of an attempt nothing has streamed, so the retry is * always clean. Once chunks have streamed,
( makeStream: () => ReturnType<LLMClient["createMessage"]>, signal: AbortSignal, onRestart?: () => boolean, )
| 582 | * propagates. |
| 583 | */ |
| 584 | private async *streamWithRetry( |
| 585 | makeStream: () => ReturnType<LLMClient["createMessage"]>, |
| 586 | signal: AbortSignal, |
| 587 | onRestart?: () => boolean, |
| 588 | ): ReturnType<LLMClient["createMessage"]> { |
| 589 | for (let attempt = 0; ; attempt++) { |
| 590 | let produced = false |
| 591 | try { |
| 592 | for await (const chunk of makeStream()) { |
| 593 | produced = true |
| 594 | yield chunk |
| 595 | } |
| 596 | return |
| 597 | } catch (error) { |
| 598 | if (signal.aborted || (error as Error).name === "AbortError") throw error |
| 599 | if (attempt >= MAX_STREAM_RETRIES || !isRetryableStreamError(error)) throw error |
| 600 | // Output already streamed this attempt: only retry if the caller can |
| 601 | // cleanly roll it back, otherwise a restart would duplicate it. |
| 602 | if (produced && !(onRestart?.() ?? false)) throw error |
| 603 | const delayMs = retryBackoffMs(attempt) |
| 604 | this.options.callbacks.onEvent({ |
| 605 | type: "system", |
| 606 | message: `Connection to the model failed (${(error as Error).message}). Retrying ${attempt + 1}/${MAX_STREAM_RETRIES} in ${Math.ceil(delayMs / 1000)}s…`, |
| 607 | isError: false, |
| 608 | }) |
| 609 | await interruptibleDelay(delayMs, signal) |
| 610 | } |
| 611 | } |
| 612 | } |
| 613 | |
| 614 | /** Run one model request + tool execution round. Returns true when the turn is over. */ |
| 615 | private async runStep(): Promise<boolean> { |
no test coverage detected