Skip to content

feat(limits): per-window quota cutoffs across all providers with usage data - #2267

Merged
diegosouzapw merged 3 commits into
cheaper-inference:release/v3.8.0from
payne0420:feat/per-connection-quota-cutoffs
May 14, 2026
Merged

diegosouzapw merged 3 commits into
cheaper-inference:release/v3.8.0from
payne0420:feat/per-connection-quota-cutoffs

Conversation

@payne0420

Copy link
Copy Markdown
Contributor

Summary

Lets operators set a usage cutoff per quota window — e.g. Codex session=95% · weekly=80%, Claude weekly_sonnet=60% · weekly_designer=70%, GLM monthly=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

  • Generic quota fetcher (open-sse/services/genericQuotaFetcher.ts) wraps every provider with a getUsageForProvider implementation 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.
  • preflightQuota iterates each window when the fetcher supplies a windows map and blocks if ANY window crosses its threshold. Names the worst window in the response so logs are actionable. Legacy single-signal path preserved for fetchers without per-window data.
  • Cascading resolver in src/sse/services/auth.ts:
    connection.quotaWindowThresholds[window] → resilience.providerWindowDefaults[provider][window] → global defaultThresholdPercent (98%).
  • Latency gate: the upstream usage fetch is skipped 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 the generic registration can't drift from the switch statement.

Storage

  • Migration 056 adds quota_window_thresholds_json TEXT to provider_connections.
  • Shape: { "<windowName>": <0-100 int> }. Window keys match the quota names surfaced by getUsageForProvider, so the dashboard's quota bars and the persisted overrides reference the same windows.
  • Sanitization happens once at the write boundary; the serializer trusts its input.

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 (max 64) and value range (0-100).
  • New GET /api/providers/quota-windows surfaces resilience-settings defaults so the modal can render per-window placeholders.

UI

  • Dashboard › Limits gets a Cutoff column with one button per connection. Button shows Default or a summary like 5h:95% · 7d:80% (clipped to 2 entries + +N).
  • Clicking opens a modal listing every window the connection's quota cache exposes, with placeholders showing the resolved default for that (provider, window). Save sends a merge patch; Reset all clears every override on that connection.
  • Button is disabled until the connection's quota data has loaded.

Test plan

  • 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/provider/id/email, which would have shipped the feature inert for non-Codex providers. Adding this test surfaced the bug.
  • 89/89 quota-related tests + 51/51 sse-auth tests pass; typecheck and lint clean; dev container rebuilt and migration applied cleanly.
  • Manual: open Dashboard › Limits with multiple Codex / Claude / GLM connections, set per-window cutoffs, watch routing skip an account when its window crosses the threshold.

🤖 Generated with Claude Code

…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>
@payne0420
payne0420 requested a review from diegosouzapw as a code owner May 14, 2026 21:38

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/lib/db/core.ts Outdated
Comment on lines +446 to +453
} 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;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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;
      }

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/app/api/providers/[id]/route.ts Outdated
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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread src/sse/services/auth.ts Outdated
Comment on lines +1392 to +1394
if (!hasConnectionOverrides && !providerHasDefaults && !legacyForceEnable) {
return credentials;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/lib/db/providers.ts Outdated
Comment on lines +57 to +63
if (!source || !Object.hasOwn(source, "quotaWindowThresholds")) {
return record;
}
return {
...record,
quotaWindowThresholds: source.quotaWindowThresholds ?? null,
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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,
  };

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@payne0420

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +171 to +174
const existingMap =
existing.quotaWindowThresholds && typeof existing.quotaWindowThresholds === "object"
? { ...(existing.quotaWindowThresholds as Record<string, number>) }
: {};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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": {} }.

Comment on lines +11 to +13
export async function GET() {
try {
const windows = getAllProviderQuotaWindows();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

payne0420 and others added 2 commits May 14, 2026 22:10
…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>
@diegosouzapw

Copy link
Copy Markdown
Collaborator

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.

@diegosouzapw
diegosouzapw merged commit aa0e312 into cheaper-inference:release/v3.8.0 May 14, 2026
3 checks passed
HouMinXi pushed a commit to HouMinXi/OmniRoute that referenced this pull request Aug 2, 2026
…e data (cheaper-inference#2267)

feat(limits): per-window quota cutoffs across all providers with usage data (cheaper-inference#2267 — thanks @payne0420)
Poid-ZA pushed a commit to Poid-ZA/OmniRoute that referenced this pull request Aug 5, 2026
…e data (cheaper-inference#2267)

feat(limits): per-window quota cutoffs across all providers with usage data (cheaper-inference#2267 — thanks @payne0420)
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…e data (cheaper-inference#2267)

feat(limits): per-window quota cutoffs across all providers with usage data (cheaper-inference#2267 — thanks @payne0420)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants