-
Notifications
You must be signed in to change notification settings - Fork 1.2k
fix(providers): harden OpenRouter quota reset cooldowns #4980
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
7f53ef3
a7f6be0
79689aa
613e224
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,6 +25,7 @@ import { | |
| hasKeyPoolFailover, | ||
| rotateProviderTransportOn401, | ||
| rateLimitRetryDelayMs, | ||
| readQuotaResetAt, | ||
| rotateProviderTransportOn429, | ||
| } from "../../providers/key-failover"; | ||
| import { | ||
|
|
@@ -675,11 +676,32 @@ export async function prepareAdapterExchange( | |
| // SAME request once per remaining key. OAuth/forward providers and single-key pools | ||
| // return null immediately, so this stays a no-op for them (src/providers/key-failover.ts). | ||
| while (upstreamResponse.status === 429 && hasKeyPoolFailover(route.provider)) { | ||
| // A quota exhaustion is dated in the BODY, not in `Retry-After` — OpenRouter | ||
| // sends no header for it (#4024). Read a bounded prefix before the socket is | ||
| // released below; a failed or slow read just leaves the header path in charge. | ||
| // Peeks a bounded prefix and hands back a Response still carrying the whole | ||
| // body, so the cancel below still releases the socket. | ||
| let peeked: Awaited<ReturnType<typeof readQuotaResetAt>>; | ||
| try { | ||
| peeked = await readQuotaResetAt(upstreamResponse, { signal: options.abortSignal }); | ||
|
Comment on lines
+684
to
+686
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The body peek is wired only into the initial Responses adapter-dispatch loop. An ordinary OpenRouter Useful? React with 👍 / 👎. |
||
| } catch { | ||
| cleanupUpstreamAbort(); | ||
| upstream.abort(); | ||
| return clientCancelledResponse(); | ||
| } | ||
| if (options.abortSignal?.aborted) { | ||
| cleanupUpstreamAbort(); | ||
| upstream.abort(); | ||
| return clientCancelledResponse(); | ||
| } | ||
| upstreamResponse = peeked.response; | ||
| const quotaResetAt = peeked.at; | ||
| const rotated = rotateProviderTransportOn429(config, route.providerName, route.provider, { | ||
| retryAfter: upstreamResponse.headers.get("retry-after"), | ||
| now: Date.now(), | ||
| attemptedKey: route.provider.apiKey, | ||
| promptCacheKey: parsed.options.promptCacheKey, | ||
| quotaResetAt, | ||
| }); | ||
| if (!rotated) break; | ||
| // Release the failed response's socket before retrying; unread bodies otherwise linger | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This changes both
src/providers/andsrc/server/, but the commit updates onlystructure/transports/responses.md;structure/INDEX.mdmapssrc/providers/to four documents andsrc/server/to multiple additional documents. The repository’s ownership contract requires every document listed for a changed area to be updated in the same change, so update the remaining mapped documents or narrow the manifest ownership where those documents do not actually describe this behavior.AGENTS.md reference: structure/AGENTS.md:L44-L50
Useful? React with 👍 / 👎.