Skip to content

Support Synthetic pack quota responses#732

Merged
steipete merged 4 commits intosteipete:mainfrom
baanish:fix/synthetic-quota-pack-format
Apr 18, 2026
Merged

Support Synthetic pack quota responses#732
steipete merged 4 commits intosteipete:mainfrom
baanish:fix/synthetic-quota-pack-format

Conversation

@baanish
Copy link
Copy Markdown
Contributor

@baanish baanish commented Apr 17, 2026

What this changes

  • expand Synthetic quota parsing to support both the experimental nested subscription.rateLimit shape and the current live root-level quota payload
  • parse the live rollingFiveHourLimit window into CodexBar's primary/session lane
  • parse the live weeklyTokenLimit window into CodexBar's secondary/weekly lane
  • recognize new reset fields like nextTickAt and nextRegenAt
  • keep support for string-based windows like 5hr
  • add regression coverage for both the nested experimental payload and the current live API response

Why

Synthetic replaced the old daily tool-call quota with two new limits:

  • a rolling five-hour request cap
  • a weekly token-credit quota that regenerates continuously

The live API now exposes those as separate root-level objects, and Synthetic's rollout notes confirm that's the intended model. CodexBar was only partially parsing the new response, so it could show the five-hour bar while dropping the weekly credit lane.

Validation

  • swift test --filter SyntheticUsageSnapshotTests
  • local CodexBar app reloaded against the updated build ✅
  • swift test still has one unrelated existing failure in "reads large stdout without deadlock" ⚠️

Before

CleanShot 2026-04-17 at 21 17 13@2x

After

image

Their billing page

CleanShot 2026-04-17 at 21 18 16@2x

Copy link
Copy Markdown

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

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: 3a191d8478

ℹ️ 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 +289 to +291
if self.isQuotaPayload(dict) {
return [dict]
}
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 Recurse into nested quota objects before accepting parent payload

The new extractQuotaObjects short-circuits on any dictionary that matches isQuotaPayload, but this commit also broadens limitKeys with messages, which makes parent containers easy to misclassify. If a response has metadata like subscription.messages plus a nested subscription.rateLimit object, this branch returns only the parent dict and never descends into rateLimit; parseQuota then cannot derive used and the parser can fail with missing quota data despite a valid nested quota being present.

Useful? React with 👍 / 👎.

@baanish baanish marked this pull request as draft April 17, 2026 03:18
@baanish baanish force-pushed the fix/synthetic-quota-pack-format branch 3 times, most recently from 367125b to 3a36cd1 Compare April 17, 2026 19:20
@baanish baanish marked this pull request as ready for review April 18, 2026 01:08
Copy link
Copy Markdown

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

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: 3a36cd1c6d

ℹ️ 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 +166 to +167
if let slots = self.prioritizedQuotaSlots(from: root) {
let slotted: [SyntheticQuotaEntry?] = slots.map { $0.flatMap(self.parseQuota) }
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 Keep fallback parsing when only some Synthetic slots are found

The parser now returns early whenever prioritizedQuotaSlots finds any known root key, which skips fallbackQuotaObjects entirely. In mixed rollout payloads (for example, weeklyTokenLimit at root plus legacy subscription.rateLimit for the rolling lane), this path keeps only the weekly slot and drops the valid nested quota, leaving the primary lane empty even though usable data is present. This is introduced by the early return in parse(data:now:) and affects users whose responses combine old and new shapes.

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.

Verified against the live GET /v2/quotas response: the API returns subscription (with limit/requests/renewsAt only — no nested rateLimit) alongside rollingFiveHourLimit and weeklyTokenLimit at root. The subscription.rateLimit shape is the older format from before rolling/weekly were lifted to root, and the API doesn't appear to mix them.

The existing test at SyntheticProviderTests.swift:92 covers this exact live payload. Skipping.

baanish and others added 4 commits April 18, 2026 21:43
Parse Synthetic's current quota response (rollingFiveHourLimit,
weeklyTokenLimit, search.hourly) in addition to the legacy pack
format, and surface weekly credit regeneration pacing in the menu
card. Keep slot identity stable so a missing lane never promotes
another lane into the wrong UI label, and rebuild the countdown
at render time so it doesn't freeze between snapshot refreshes.
Drop the hardcoded 5% rolling-tick assumption: thread the API's
tickPercent through SyntheticQuotaEntry into RateWindow.nextRegenPercent
and require it in syntheticRollingRegenDetail. Also flatten the string
window-suffix matcher into a single longest-first list so future units
can't shadow each other.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@steipete steipete force-pushed the fix/synthetic-quota-pack-format branch from 0804800 to d3a4416 Compare April 18, 2026 20:46
@steipete steipete merged commit 5a1c06e into steipete:main Apr 18, 2026
1 check passed
@steipete
Copy link
Copy Markdown
Owner

Landed via temp rebase onto main.

  • Gate: swiftformat Sources Tests; swiftlint --strict; pnpm check; swift build; swift test -q; ./Scripts/compile_and_run.sh
  • Land commit: d3a4416
  • Merge commit: 5a1c06e

Thanks @baanish!

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