-
Notifications
You must be signed in to change notification settings - Fork 716
fix(providers): surface OpenCode Zen short-window rate limits #1330
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
Merged
Wibias
merged 3 commits into
lidge-jun:dev
from
Wibias:wibias215/ocx-56-opencode-zen-rate-limit-15-20-rpm-is-silent-no-header-no-doc
Aug 9, 2026
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| /** | ||
| * OpenCode Zen short-window rate-limit guidance (#1145 / OCX-56). | ||
| * | ||
| * OpenCode's keyed and keyless Zen chat endpoints share `https://opencode.ai/zen/v1`. | ||
| * Free-model traffic can hit a short-window burst ceiling around 15–20 RPM | ||
| * (community-measured). Zen often answers with opaque `429 Rate limit exceeded` | ||
| * bodies and may omit `Retry-After` / `X-RateLimit-*`; when those headers are | ||
| * present they still take precedence. Distinct from the keyless desktop | ||
| * ~200 requests / 5h quota documented on `opencode-free`. | ||
| */ | ||
| import { validateClientRetryAfterHeader } from "../lib/retry-after"; | ||
| import { registryEntryForProviderDestination } from "./registry"; | ||
|
|
||
| const OPENCODE_ZEN_PROVIDER_IDS = new Set(["opencode-zen", "opencode-free"]); | ||
|
|
||
| /** Observed free-model burst ceiling on Zen (not an official OpenCode figure). */ | ||
| export const OPENCODE_ZEN_OBSERVED_RPM_HINT = "roughly 15-20 requests per minute"; | ||
|
|
||
| /** | ||
| * Synthetic client backoff when Zen omits Retry-After after a rate-limit 429. | ||
| * Longer than the generic 2s default so Codex-shaped clients do not immediately | ||
| * re-hammer a ~15-20 RPM window. | ||
| */ | ||
| export const OPENCODE_ZEN_SYNTHETIC_RETRY_AFTER_SEC = 15; | ||
|
|
||
| const ENRICHMENT_MARKER = "15-20 requests per minute"; | ||
|
|
||
| export function isOpenCodeZenRateLimitProvider(opts: { | ||
| providerName?: string; | ||
| baseUrl?: string; | ||
| adapter?: string; | ||
| }): boolean { | ||
| const name = opts.providerName?.trim(); | ||
| if (name && OPENCODE_ZEN_PROVIDER_IDS.has(name)) return true; | ||
| const baseUrl = opts.baseUrl?.trim(); | ||
| if (!baseUrl) return false; | ||
| const entry = registryEntryForProviderDestination({ | ||
| baseUrl, | ||
| adapter: opts.adapter?.trim() || "openai-chat", | ||
| authMode: "key", | ||
| }); | ||
| return entry !== undefined && OPENCODE_ZEN_PROVIDER_IDS.has(entry.id); | ||
| } | ||
|
|
||
| /** | ||
| * Same-key `retryOn429` only applies on key-authenticated HTTP paths — not | ||
| * keyless `opencode-free` traffic and not custom `runTurn` transports. | ||
| */ | ||
| export function supportsOpenCodeZenRetryOn429Guidance(opts: { | ||
| authMode?: string; | ||
| hasApiKey?: boolean; | ||
| supportsHttpSameKeyRetry?: boolean; | ||
| }): boolean { | ||
| if (opts.supportsHttpSameKeyRetry === false) return false; | ||
| if (opts.authMode !== undefined && opts.authMode !== "key") return false; | ||
| return opts.hasApiKey === true; | ||
| } | ||
|
|
||
| /** | ||
| * Append actionable Zen rate-limit context to a generic upstream 429 message and | ||
| * embed a parseable `try again in Ns` hint so {@link resolveClientRetryAfter} | ||
| * surfaces a useful Retry-After when the gateway sent none. | ||
| */ | ||
| export function enrichOpenCodeZenRateLimitMessage( | ||
| message: string, | ||
| opts: { | ||
| status: number; | ||
| providerName?: string; | ||
| baseUrl?: string; | ||
| adapter?: string; | ||
| authMode?: string; | ||
| hasApiKey?: boolean; | ||
| /** Upstream Retry-After header; when valid, skip the synthetic 15s text hint. */ | ||
| upstreamRetryAfter?: string | null; | ||
| /** False for custom `runTurn` transports outside the HTTP retry loop. */ | ||
| supportsHttpSameKeyRetry?: boolean; | ||
| now?: number; | ||
| }, | ||
| ): string { | ||
| if (opts.status !== 429) return message; | ||
| if (!isOpenCodeZenRateLimitProvider(opts)) return message; | ||
| if (!/rate\s*limit/i.test(message)) return message; | ||
| if (message.includes(ENRICHMENT_MARKER)) return message; | ||
|
|
||
| const upstreamRetry = validateClientRetryAfterHeader( | ||
| opts.upstreamRetryAfter, | ||
| opts.now ?? Date.now(), | ||
| ); | ||
| const retryHint = upstreamRetry || /try again in \d/i.test(message) | ||
| ? "" | ||
| : ` Try again in ${OPENCODE_ZEN_SYNTHETIC_RETRY_AFTER_SEC}s.`; | ||
| const paceHint = supportsOpenCodeZenRetryOn429Guidance(opts) | ||
| ? " Slow the request pace, or set providers.opencode-zen.retryOn429 for same-key backoff." | ||
| : " Slow the request pace."; | ||
| return ( | ||
| `${message}` | ||
| + ` OpenCode Zen free-model traffic is often limited to ${OPENCODE_ZEN_OBSERVED_RPM_HINT}` | ||
| + ` (observed; OpenCode does not publish this RPM, and may omit rate-limit headers).` | ||
| + `${retryHint}` | ||
| + paceHint | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.