-
Notifications
You must be signed in to change notification settings - Fork 140
Read a relative Retry-After out of the message, not only an absolute deadline #1216
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
base: master
Are you sure you want to change the base?
Changes from all commits
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 |
|---|---|---|
|
|
@@ -149,15 +149,53 @@ export function parseModelRetiredInfo(rawMessage: string): ModelRetiredInfo | un | |
| const RATE_LIMIT_UNTIL_RE = /rate.?limited\s+until\s+([0-9T][0-9TZ:+.-]*[0-9Z])/i | ||
| const EXPLICIT_ZONE_RE = /[Zz]$|[+-]\d{2}:?\d{2}$/ | ||
|
|
||
| // A plain 429 does not use the gateway's "rate limited until" wording. What it | ||
| // carries instead is a Retry-After, which reaches this extension only as text -- | ||
| // pi hands the message along and not the response -- rendered as a relative | ||
| // wait. Without this, "429 Too Many Requests, retry after 30 seconds" yields no | ||
| // deadline at all and the retry backs off blindly against a limit that had just | ||
| // said how long it lasts. | ||
|
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.
💡 Suggestion: Anchor the trigger group with a word boundary: |
||
| const RATE_LIMIT_IN_RE = | ||
| /(?:retry|try again|available|reset[s]?)\s*(?:after|in)\s+(\d+(?:\.\d+)?)\s*(ms|milliseconds?|s|secs?|seconds?|m|mins?|minutes?|h|hours?)\b/i | ||
|
|
||
| const UNIT_MS: Record<string, number> = { | ||
| ms: 1, | ||
| millisecond: 1, | ||
| milliseconds: 1, | ||
| s: 1_000, | ||
| sec: 1_000, | ||
| secs: 1_000, | ||
| second: 1_000, | ||
| seconds: 1_000, | ||
| m: 60_000, | ||
| min: 60_000, | ||
| mins: 60_000, | ||
| minute: 60_000, | ||
| minutes: 60_000, | ||
| h: 3_600_000, | ||
| hour: 3_600_000, | ||
| hours: 3_600_000, | ||
| } | ||
|
|
||
| /** Epoch ms the gateway says the limit lifts, or undefined when it named none or it has passed. */ | ||
| export function parseRateLimitRetryAt(rawMessage: string, now: number = Date.now()): number | undefined { | ||
| const match = RATE_LIMIT_UNTIL_RE.exec(rawMessage) | ||
| if (!match?.[1]) return undefined | ||
| const stamp = match[1] | ||
| // The gateway reports UTC; Date.parse would read an unzoned stamp as local time. | ||
| const retryAt = Date.parse(EXPLICIT_ZONE_RE.test(stamp) ? stamp : `${stamp}Z`) | ||
| if (Number.isNaN(retryAt) || retryAt <= now) return undefined | ||
| return retryAt | ||
| if (match?.[1]) { | ||
|
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. ℹ️ When 💡 Suggestion: Restructure so the relative path is tried when the absolute parse fails: extract the absolute-parsing into a block that falls through instead of returning (e.g. compute |
||
| const stamp = match[1] | ||
| // The gateway reports UTC; Date.parse would read an unzoned stamp as local time. | ||
| const retryAt = Date.parse(EXPLICIT_ZONE_RE.test(stamp) ? stamp : `${stamp}Z`) | ||
| if (Number.isNaN(retryAt) || retryAt <= now) return undefined | ||
| return retryAt | ||
| } | ||
|
|
||
| // The absolute form is preferred above because it needs no arithmetic; this | ||
| // is the fallback for a message that states a duration instead. | ||
| const relative = RATE_LIMIT_IN_RE.exec(rawMessage) | ||
| if (!relative?.[1]) return undefined | ||
| const amount = Number(relative[1]) | ||
|
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. ℹ️ Relative durations are unbounded: 💡 Suggestion: Clamp the computed delay to a sane ceiling before returning, e.g. |
||
| const unit = UNIT_MS[relative[2].toLowerCase()] | ||
| if (!Number.isFinite(amount) || amount <= 0 || unit === undefined) return undefined | ||
| return now + amount * unit | ||
| } | ||
|
|
||
| /** The gateway reports UTC; only local wall-clock time tells the user when to come back. */ | ||
|
|
||
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.
ℹ️🔧 Maintainability
The comment block above the new tests repeats the rationale paragraph from
src/llm-gateway-error.ts(lines ~152-156) almost verbatim, so the two copies can drift apart as the wording evolves in one place only.💡 Suggestion: Keep the full rationale in the source next to
RATE_LIMIT_IN_REand shorten the test comment to a one-line pointer, e.g.// Covers the relative Retry-After text form; see RATE_LIMIT_IN_RE for why this exists.