* Enforce the user-configured provider rate limit. * * NOTE: This is intentionally treated as expected behavior and is surfaced via * the `api_req_rate_limit_wait` say type (not an error).
(retryAttempt: number)
| 3941 | * the `api_req_rate_limit_wait` say type (not an error). |
| 3942 | */ |
| 3943 | private async maybeWaitForProviderRateLimit(retryAttempt: number): Promise<void> { |
| 3944 | const state = await this.providerRef.deref()?.getState() |
| 3945 | const rateLimitSeconds = |
| 3946 | state?.apiConfiguration?.rateLimitSeconds ?? this.apiConfiguration?.rateLimitSeconds ?? 0 |
| 3947 | |
| 3948 | const lastRequestTime = this.rateLimitClock.getLastRequestTime() |
| 3949 | if (rateLimitSeconds <= 0 || !lastRequestTime) { |
| 3950 | return |
| 3951 | } |
| 3952 | |
| 3953 | const now = performance.now() |
| 3954 | const timeSinceLastRequest = now - lastRequestTime |
| 3955 | const rateLimitDelay = Math.ceil( |
| 3956 | Math.min(rateLimitSeconds, Math.max(0, rateLimitSeconds * 1000 - timeSinceLastRequest) / 1000), |
| 3957 | ) |
| 3958 | |
| 3959 | // Only show the countdown UX on the first attempt. Retry flows have their own delay messaging. |
| 3960 | if (rateLimitDelay > 0 && retryAttempt === 0) { |
| 3961 | for (let i = rateLimitDelay; i > 0; i--) { |
| 3962 | // Send structured JSON data for i18n-safe transport |
| 3963 | const delayMessage = JSON.stringify({ seconds: i }) |
| 3964 | await this.say("api_req_rate_limit_wait", delayMessage, undefined, true) |
| 3965 | await delay(1000) |
| 3966 | } |
| 3967 | // Finalize the partial message so the UI doesn't keep rendering an in-progress spinner. |
| 3968 | await this.say("api_req_rate_limit_wait", undefined, undefined, false) |
| 3969 | } |
| 3970 | } |
| 3971 | |
| 3972 | public async *attemptApiRequest( |
| 3973 | retryAttempt: number = 0, |
no test coverage detected