-
Notifications
You must be signed in to change notification settings - Fork 878
feat(combos): add random, least-used, and reset-window routing strategies #2050
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: dev
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 |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| import type { ProviderQuota } from "../providers/quota"; | ||
|
|
||
| function collectResetCandidates(quota: ProviderQuota): number[] { | ||
| const candidates: number[] = []; | ||
| if (typeof quota.fiveHourResetAt === "number") candidates.push(quota.fiveHourResetAt); | ||
| if (typeof quota.weeklyResetAt === "number") candidates.push(quota.weeklyResetAt); | ||
| if (typeof quota.monthlyResetAt === "number") candidates.push(quota.monthlyResetAt); | ||
| if (quota.customWindows) { | ||
| for (const w of quota.customWindows) { | ||
| if (typeof w.resetAt === "number") candidates.push(w.resetAt); | ||
|
Comment on lines
+3
to
+10
Contributor
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. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Reject non-finite reset timestamps. The Proposed fix- if (typeof quota.fiveHourResetAt === "number") candidates.push(quota.fiveHourResetAt);
- if (typeof quota.weeklyResetAt === "number") candidates.push(quota.weeklyResetAt);
- if (typeof quota.monthlyResetAt === "number") candidates.push(quota.monthlyResetAt);
+ if (Number.isFinite(quota.fiveHourResetAt)) candidates.push(quota.fiveHourResetAt);
+ if (Number.isFinite(quota.weeklyResetAt)) candidates.push(quota.weeklyResetAt);
+ if (Number.isFinite(quota.monthlyResetAt)) candidates.push(quota.monthlyResetAt);
if (quota.customWindows) {
for (const w of quota.customWindows) {
- if (typeof w.resetAt === "number") candidates.push(w.resetAt);
+ if (Number.isFinite(w.resetAt)) candidates.push(w.resetAt);
}
}Also applies to: 20-27 🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
| return candidates; | ||
| } | ||
|
|
||
| /** | ||
| * Earliest future reset timestamp from a cached provider quota snapshot, | ||
| * or null when no fresh quota data exists or all resets have elapsed. | ||
| */ | ||
| export function earliestQuotaResetAt( | ||
| quota: ProviderQuota | null, | ||
| now: number, | ||
| ): number | null { | ||
| if (!quota) return null; | ||
| const future = collectResetCandidates(quota).filter(ts => ts > now); | ||
| if (future.length > 0) return Math.min(...future); | ||
| return null; | ||
| } | ||
|
|
||
| /** | ||
| * Milliseconds until the soonest known quota-window reset. | ||
| * Returns Infinity when no quota data exists, quota is stale, or all known | ||
| * reset timestamps have elapsed. An elapsed reset is stale evidence — it | ||
| * does not prove the next request has fresh capacity. | ||
| */ | ||
| export function quotaResetRemainingMs( | ||
| quota: ProviderQuota | null, | ||
| now: number, | ||
| ): number { | ||
| const nearest = earliestQuotaResetAt(quota, now); | ||
| if (nearest === null) return Number.POSITIVE_INFINITY; | ||
| return nearest - now; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import type { ProviderQuota, ProviderQuotaReport } from "./quota"; | ||
|
|
||
| const quotaCache = new Map<string, ProviderQuota>(); | ||
|
|
||
| export function clearCachedProviderQuotas(): void { | ||
| quotaCache.clear(); | ||
| } | ||
|
|
||
| export function replaceCachedProviderQuotas(reports: ProviderQuotaReport[]): void { | ||
| quotaCache.clear(); | ||
| for (const report of reports) { | ||
| quotaCache.set(report.provider, report.quota); | ||
| } | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| export function getCachedProviderQuota( | ||
| provider: string, | ||
| now: number, | ||
| maxAgeMs = 30 * 60_000, | ||
| ): ProviderQuota | null { | ||
| const quota = quotaCache.get(provider); | ||
| if (!quota) return null; | ||
| if (now - quota.updatedAt > maxAgeMs) return null; | ||
| return quota; | ||
| } | ||
|
|
||
| export function setCachedProviderQuotaForTests( | ||
| provider: string, | ||
| quota: ProviderQuota, | ||
| ): void { | ||
| quotaCache.set(provider, quota); | ||
| } | ||
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.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Reject or report ignored
--stickyvalues for non-round-robin strategies.Line 77 still accepts
--stickyfor every strategy, but Line 89 only serializes it for"round-robin". For example,ocx combo set demo --strategy random --sticky 5succeeds and silently drops5. Reject--stickyunless the strategy is"round-robin", or clearly report that the option is ignored.🤖 Prompt for AI Agents