Skip to content

feat(codex): keep GPT slots in rotation on spendable ChatGPT credits - #508

Merged
dofastted merged 2 commits into
dofastted:mainfrom
gang151:feat/codex-credits-quota-bypass
Oct 17, 2026
Merged

dofastted merged 2 commits into
dofastted:mainfrom
gang151:feat/codex-credits-quota-bypass

Conversation

@gang151

@gang151 gang151 commented Oct 17, 2026

Copy link
Copy Markdown

Summary

Once a GPT slot's 5h/7d window hits 100%, ChatGPT can keep serving it by spending account credits. Until now vm2api parked the slot until the window reset, even when credits were available. With this PR, a fresh reading that credits are spendable keeps the slot schedulable. The upstream 429 stays the authority on real exhaustion.

Credit snapshot

  • Parsed from /wham/usage credits and from the x-codex-credits-has-credits / -unlimited / -balance headers on every Responses reply. This is the same contract as the official Codex CLI (codex-rs/codex-api/src/rate_limits.rs): both flags are required, true/false/1/0 are accepted, and the balance is trimmed.
  • Stored as vm.codex.credits = { has_credits, unlimited, balance, fetched_at }, separate from reset_credits because the two mean different things. balance stays a string, so long decimals keep full precision.
  • Returned by the quota refresh / usage probe (credits), on the pool summary (codex_credits), and on the panel VM list (codex_credits).

One predicate in every quota gate

hasSpendableCodexCredits(vm) is true when the reading is within CODEX_CREDITS_TTL_MS (10 min) and has_credits || unlimited. A hidden balance: null still counts. When it holds, only the window caps are waived, in:

  • codexQuotaPark (new waiveWindowCaps option) → isCodexSlotParked / parkUntil
  • evaluateOpenAIQuotaGate (local limit_5h / limit_7d)
  • codexQuotaWindowReason and codexRestrictionUntil → evaluateCodexQuotaSchedule, so no temp_unschedulable is written and an existing quota restriction is cleared
  • persistCodexUsage: a successful reply past the cap no longer derives a codex_limited_until from the spent window

Still blocks

  • Real exhaustion: an upstream 429 still parks the slot through the existing limitedUntil path and drops the credits reading, so the window caps apply again until a newer reading arrives.
  • Explicit park: a live codex_limited_until outranks credits.
  • Operator and health blocks: operator disable, missing credentials, proxy blocks and runtime caps run before the quota checks and are unchanged.
  • Unknown, stale or empty credits (has_credits=false && unlimited=false): parking is exactly as before.

Behavior notes for review

  • Refresh clears an old park: a /wham/usage refresh that reports spendable credits clears an older codex_limited_until. If upstream refuses again, the next 429 re-parks.
  • Local limits are waived too: fresh credits also waive a local limit_5h / limit_7d below 1, as the original proposal specifies. A follow-up could add an opt-out switch if operators want those limits to reserve headroom even when credits are available.
  • TTL behavior: each reply refreshes the reading, so a slot in active rotation keeps serving. A slot that sits idle for more than 10 minutes (or gets a 429) parks as before. It is revived by a quota refresh (panel, or the health probe if enabled), or when the window resets.

Tests

New test/unit/codex-credits.test.mjs (13 tests). It covers balance parsing, long-decimal precision, unlimited, has_credits with balance=null, has_credits=false not bypassing the weekly cap, TTL expiry, every local gate passing at 7d=100% with fresh credits, operator/credential blocks, the protocol path keeping the slot on a successful reply, an upstream 429 parking and dropping credits, and reset credits being unaffected. Nine of these fail with the gate wiring reverted.

  • node --test test/unit/*codex*.test.mjs test/unit/openai*.test.mjs: 212/212 pass (199 existing + 13 new)
  • Full npm run test:unit on macOS: 7 files fail (egress, oauth-binary, session-oauth-seam, slot-shell-rc, slot-recovery, host-path, guest-identity-reader). All 7 fail identically on the untouched base; they need Linux binaries or paths.
  • biome format: clean. git diff --check: clean.

Not run: a live ChatGPT account with real credits, the web UI (no frontend change), or the Go tests.

luogang and others added 2 commits October 17, 2026 20:04
A slot whose 5h/7d window is spent but whose ChatGPT credits are still
spendable now stays schedulable instead of parking until the window reset.

- Parse `credits` from /wham/usage and `x-codex-credits-*` reply headers
  (same contract as the Codex CLI; balance kept as a string) into
  vm.codex.credits, separate from reset_credits, and project it to the
  pool summary (`codex_credits`) and panel responses.
- One predicate, hasSpendableCodexCredits (fresh within 10 min and
  has_credits or unlimited), waives only window caps in every quota gate:
  codexQuotaPark, evaluateOpenAIQuotaGate, codexQuotaWindowReason,
  evaluateCodexQuotaSchedule, and the park derived on successful replies.
- An upstream 429 still parks the slot and drops the credits reading;
  an explicit park, operator disable, auth and proxy blocks are unchanged.
Co-authored-by: ted <kuhlmangarry24@gmail.com>
@dofastted
dofastted merged commit db4ad85 into dofastted:main Oct 17, 2026
5 checks passed
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.

3 participants