Repository navigation
feat(limits): per-window quota cutoffs across all providers with usage data - #2267
Conversation
…e data
Lets operators set a usage cutoff per quota window (e.g. Codex 5h=95%, weekly=80%; Claude
weekly=70%, weekly_sonnet=60%) so the dispatch loop skips a connection before it reaches
its upstream limit, with no latency penalty when nothing is configured.
Routing:
- Generic quota fetcher (open-sse/services/genericQuotaFetcher.ts) wraps every provider
with a getUsageForProvider implementation (~16 providers) and exposes a per-window
breakdown to the preflight evaluator.
- preflightQuota iterates each window when the fetcher supplies a windows map and blocks
when ANY window crosses its threshold; falls back to the legacy single-signal path
otherwise.
- Cascading resolver in src/sse/services/auth.ts: connection.quotaWindowThresholds[window]
→ resilience.providerWindowDefaults[provider][window] → defaultThresholdPercent (98%).
- Latency gate skips the upstream usage fetch entirely when no override, no provider
default, and no legacy quotaPreflightEnabled flag is set — i.e. zero overhead for the
default case.
- USAGE_FETCHER_PROVIDERS exported from usage.ts so generic registration can't drift
from the switch statement.
Storage:
- Migration 056 adds quota_window_thresholds_json TEXT to provider_connections.
- Stored shape: { "<windowName>": <0-100 int> }; window keys match the quota names
surfaced by getUsageForProvider so the dashboard and the persisted overrides reference
the same windows.
API:
- PUT /api/providers/[id] accepts quotaWindowThresholds with three patch shapes:
null (clear all), partial map (merge), per-key null (clear that window). Zod-validates
key length and value range.
- New GET /api/providers/quota-windows surfaces the resilience-settings defaults so
the modal can show per-window placeholders.
UI:
- Dashboard › Limits › Provider Limits gets a Cutoff column with one button per
connection. Button shows "Default" or a summary like "5h:95% · 7d:80%". Clicking
opens a modal with one input per window the connection's quota cache exposes.
- Button disabled until the connection's quota data has loaded.
Tests (140 total green):
- Per-window evaluation (block-if-any, omitted-resolver fallback, window-name resolver
semantics).
- Generic fetcher conversion (remainingPercentage, used/total, clamp, skip unlimited).
- Resilience settings defaults + clamping + per-provider seeding.
- DB round-trip + sanitization + Zod validation.
- Skip-gate regression: fetcher must not run when no limits are configured.
- Counterpart regression: fetcher must run when an override is set — caught a critical
bug where ProviderConnectionView was dropping quotaWindowThresholds, which would have
shipped the feature inert for non-Codex providers.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request introduces a per-window quota preflight system, enabling granular usage enforcement across different time windows for various providers. It includes a new generic fetcher adapter, database migrations for threshold persistence, and a dashboard UI for managing per-connection cutoffs. Feedback highlights a need to generalize JSON column parsing in the database utilities, correct a logic discrepancy in the API route regarding empty threshold maps, and refine the latency gate in the authentication service to respect global default thresholds. There is also a recommendation to ensure consistent object structures between the connection creation and update paths.
| } else if (camelKey === "quotaWindowThresholdsJson" && typeof v === "string") { | ||
| // Surface the parsed object under the friendlier name | ||
| // `quotaWindowThresholds` (the DB column is suffixed _json for clarity). | ||
| try { | ||
| result.quotaWindowThresholds = JSON.parse(v); | ||
| } catch { | ||
| result.quotaWindowThresholds = null; | ||
| } |
There was a problem hiding this comment.
Hardcoding specific column names like quotaWindowThresholdsJson in a generic utility like rowToCamel is a maintainability issue. This logic should be generalized to handle any column with a Json suffix, which would also simplify the implementation and make it reusable for future features.
} else if (camelKey.endsWith("Json") && typeof v === "string") {
const baseKey = camelKey.slice(0, -4);
try {
result[baseKey] = JSON.parse(v);
} catch {
result[baseKey] = null;
}There was a problem hiding this comment.
Agree — generalized in c5b912a. rowToCamel now treats any column whose camel-case key ends in Json as a JSON-encoded TEXT column and surfaces the parsed value under the key minus the suffix (e.g. quotaWindowThresholdsJson → quotaWindowThresholds). Established the same convention for any future _json column.
| if (incomingWindowThresholds !== undefined) { | ||
| // Three patch shapes are accepted: | ||
| // 1. null → clear every per-window override on this connection | ||
| // 2. {} (empty map) → same as null (no overrides set) |
There was a problem hiding this comment.
The comment states that an empty map {} should clear every per-window override, but the implementation at lines 170-184 performs a merge. If incomingWindowThresholds is {}, the loop is skipped and existing overrides are preserved. Please either update the logic to clear on {} or correct the comment to reflect that only null clears all overrides.
There was a problem hiding this comment.
Agree the comment was misleading — fixed it in c5b912a. Kept the merge-on-empty behavior (PATCH convention: empty patch = no-op, only explicit null clears all). Comment now reads:
// PATCH semantics:
// • null → clear every per-window override on this connection
// • {} (empty map) → no-op (no keys to merge); existing overrides preserved
// • partial map → merge into the existing map; a `null` value at any
// key clears just that window's override
| if (!hasConnectionOverrides && !providerHasDefaults && !legacyForceEnable) { | ||
| return credentials; | ||
| } |
There was a problem hiding this comment.
The latency gate skips the upstream usage fetch if no per-connection or per-provider overrides are set. However, it does not account for the global defaultThresholdPercent from resilience settings. If an operator sets a restrictive global threshold (e.g., 50%), it will be ignored for all connections without specific overrides, potentially leading to unexpected routing behavior. The gate should also check if the global threshold is restrictive enough (e.g., < 98%) to warrant preflight.
There was a problem hiding this comment.
Good catch — agree. Fixed in c5b912a. The latency gate now also runs preflight when the global defaultThresholdPercent has been raised above the factory no-op level (2% remaining = effectively "right before 429"). Anything stricter (e.g. 20% remaining = stop at 80% used) means the operator wants enforcement and preflight runs.
Added regression test in tests/unit/sse-auth.test.ts: registers a fetcher, tightens the global to 20%, asserts the fetcher fires; resets to factory, asserts the fetcher does NOT fire.
| if (!source || !Object.hasOwn(source, "quotaWindowThresholds")) { | ||
| return record; | ||
| } | ||
| return { | ||
| ...record, | ||
| quotaWindowThresholds: source.quotaWindowThresholds ?? null, | ||
| }; |
There was a problem hiding this comment.
The withNullableQuotaWindowThresholds wrapper uses Object.hasOwn to decide whether to add the field, which causes an inconsistency between the create and update paths. In createProviderConnection, the key is deleted if sanitized to null (lines 300-301), causing it to be missing from the returned object. In updateProviderConnection, it is preserved. It is better to remove the hasOwn check to match the behavior of withNullableMaxConcurrent, ensuring the key is always present in the returned object.
return {
...record,
quotaWindowThresholds: source?.quotaWindowThresholds ?? null,
};There was a problem hiding this comment.
Agree — simplified in c5b912a. withNullableQuotaWindowThresholds now always surfaces the field (possibly null), matching withNullableMaxConcurrent's contract. The createProviderConnection code path that used to delete the key when sanitization collapsed to no-overrides is gone; both create and update return the same shape now.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 165bb0a11e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const existingMap = | ||
| existing.quotaWindowThresholds && typeof existing.quotaWindowThresholds === "object" | ||
| ? { ...(existing.quotaWindowThresholds as Record<string, number>) } | ||
| : {}; |
There was a problem hiding this comment.
Treat empty quotaWindowThresholds patch as clear-all
The handler documents {} as “clear every per-window override”, but this branch initializes existingMap from the current stored overrides; when the incoming object is empty, the merge loop is a no-op and the old overrides are written back unchanged. Any client that sends {} (per the API contract/comments) will fail to clear thresholds, leaving quota preflight behavior unexpectedly active.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Partial agree: the comment was wrong, the implementation was right. Fixed in c5b912a. Empty {} is intentionally a no-op (PATCH convention — empty patch should preserve state). Only an explicit null for the whole field clears every override. Comment now reflects this:
// PATCH semantics:
// • null → clear every per-window override on this connection
// • {} (empty map) → no-op (no keys to merge); existing overrides preserved
// • partial map → merge into the existing map; a `null` value at any
// key clears just that window's override
If you want "clear all" you send { "quotaWindowThresholds": null }, not { "quotaWindowThresholds": {} }.
| export async function GET() { | ||
| try { | ||
| const windows = getAllProviderQuotaWindows(); |
There was a problem hiding this comment.
Enforce management auth on quota-windows endpoint
This new providers endpoint returns operational routing policy (providerWindowDefaults and global threshold) but does not call requireManagementAuth, unlike other /api/providers/* management routes. As implemented, unauthenticated callers can fetch internal quota cutoff configuration via GET /api/providers/quota-windows, which is an authorization regression for provider management data.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Agree — fixed in c5b912a. GET /api/providers/quota-windows now calls requireManagementAuth before returning anything. Confirmed via curl that an unauthenticated request gets 401, matching sibling /api/providers/* routes.
…review
Semantics flip (the cutoff number means the same thing as the dashboard's
quota bars — "minimum REMAINING %" — so setting "10" means "stop using this
account when only 10% remaining"):
- preflightQuota threshold check inverted: block when (1 - percentUsed) * 100
<= cutoff. Log messages now say "X% remaining" instead of "X% used".
- Resilience-settings factory defaults flipped:
• defaultThresholdPercent 98 → 2 (stop at 2% remaining)
• warnThresholdPercent 80 → 20 (warn at 20% remaining)
• providerWindowDefaults.codex.session 95 → 5
• providerWindowDefaults.codex.weekly 80 → 20
- Normalizer invariant flipped: warn must be GREATER THAN cutoff (more
remaining means earlier alert).
- Resolver callback names renamed for clarity:
resolveExhaustionPercent → resolveMinRemainingPercent.
- Modal copy + i18n updated: "Default: stop at 5% remaining" etc. Tests
rewritten for the new direction; 39/39 quota tests + 52/52 sse-auth pass.
PR review fixes:
- Add requireManagementAuth to GET /api/providers/quota-windows (P2 from
codex review — endpoint exposes operational routing policy, same as the
rest of /api/providers/*).
- Clarify PATCH semantics comment in PUT /api/providers/[id]: empty `{}` is
a no-op (existing overrides preserved), only null clears all (chatgpt-codex
P1 + gemini-code-assist; impl was correct, comment was misleading).
- Generalize rowToCamel JSON parsing: any column with a `_json` suffix is
now parsed and surfaced under its key without the suffix (gemini-code-
assist), instead of hardcoding the new column name.
- Make withNullableQuotaWindowThresholds always surface the field (drop the
hasOwn gate) so create and update paths return the same shape — matches
withNullableMaxConcurrent's contract.
- Latency gate now also runs preflight when the global defaultThresholdPercent
has been raised above the factory no-op level (2% remaining). Added
regression test that asserts the fetcher fires when the operator tightens
the global, and stops firing when settings reset to factory defaults.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The factory `providerWindowDefaults.codex = { session: 5, weekly: 20 }` made
Codex's cutoff modal show different placeholders ("stop at 5% / 20% remaining")
than every other provider ("stop at 2% remaining"). The cadence-aware reasoning
behind those numbers was sound, but the inconsistency was confusing in the UI.
Drop the seed. `providerWindowDefaults` defaults to `{}`; all providers fall
through to the global `defaultThresholdPercent: 2`. Operators can still set
per-(provider, window) defaults via resilience settings, or per-connection
overrides via the modal — the infrastructure is unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Thanks @payne0420! 🚀 This is a fantastic contribution — extending quota preflight from Codex-only to 16+ providers with per-window granularity is a major quality-of-life upgrade for operators. The cascading threshold resolver (connection → provider default → global 98%) is well-architected, and the zero-latency gate for the default case shows great attention to performance. The test suite (89 quota + 51 auth tests) is impressive, and catching the ProviderConnectionView field-dropping regression via tests is excellent practice. Merging into release/v3.8.0. |
aa0e312
into
cheaper-inference:release/v3.8.0
…e data (cheaper-inference#2267) feat(limits): per-window quota cutoffs across all providers with usage data (cheaper-inference#2267 — thanks @payne0420)
…e data (cheaper-inference#2267) feat(limits): per-window quota cutoffs across all providers with usage data (cheaper-inference#2267 — thanks @payne0420)
…e data (cheaper-inference#2267) feat(limits): per-window quota cutoffs across all providers with usage data (cheaper-inference#2267 — thanks @payne0420)
Summary
Lets operators set a usage cutoff per quota window — e.g. Codex
session=95% · weekly=80%, Claudeweekly_sonnet=60% · weekly_designer=70%, GLMmonthly=50%— so the dispatch loop skips a connection before it hits its upstream limit, with zero latency when nothing is configured.Before: a single 98% threshold hardcoded into preflight, Codex-only enforcement, no UI to change it. After: per-window thresholds on every provider with usage data (~16 providers via the existing
getUsageForProvider), configurable per connection through a modal in Dashboard › Limits.Routing changes
open-sse/services/genericQuotaFetcher.ts) wraps every provider with agetUsageForProviderimplementation and surfaces a per-window breakdown to the preflight evaluator. Bespoke fetchers (Codex, CROF, DeepSeek, Bailian Coding Plan) take precedence — the generic registrar skips them.src/sse/services/auth.ts:connection.quotaWindowThresholds[window]→resilience.providerWindowDefaults[provider][window]→ globaldefaultThresholdPercent(98%).quotaPreflightEnabledflag is set — i.e. zero overhead for the default case.USAGE_FETCHER_PROVIDERSexported fromusage.tsso the generic registration can't drift from the switch statement.Storage
quota_window_thresholds_json TEXTtoprovider_connections.{ "<windowName>": <0-100 int> }. Window keys match the quota names surfaced bygetUsageForProvider, so the dashboard's quota bars and the persisted overrides reference the same windows.API
PUT /api/providers/[id]acceptsquotaWindowThresholdswith three patch shapes:null(clear all), partial map (merge), per-keynull(clear that window). Zod-validates key length (max 64) and value range (0-100).GET /api/providers/quota-windowssurfaces resilience-settings defaults so the modal can render per-window placeholders.UI
Defaultor a summary like5h:95% · 7d:80%(clipped to 2 entries ++N).Test plan
remainingPercentage,used/total, clamp, skip unlimited).ProviderConnectionViewwas droppingquotaWindowThresholds/provider/id/email, which would have shipped the feature inert for non-Codex providers. Adding this test surfaced the bug.🤖 Generated with Claude Code