Support Synthetic pack quota responses#732
Conversation
There was a problem hiding this comment.
💡 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".
| if self.isQuotaPayload(dict) { | ||
| return [dict] | ||
| } |
There was a problem hiding this comment.
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 👍 / 👎.
367125b to
3a36cd1
Compare
There was a problem hiding this comment.
💡 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".
| if let slots = self.prioritizedQuotaSlots(from: root) { | ||
| let slotted: [SyntheticQuotaEntry?] = slots.map { $0.flatMap(self.parseQuota) } |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
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.
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>
0804800 to
d3a4416
Compare
What this changes
subscription.rateLimitshape and the current live root-level quota payloadrollingFiveHourLimitwindow into CodexBar's primary/session laneweeklyTokenLimitwindow into CodexBar's secondary/weekly lanenextTickAtandnextRegenAt5hrWhy
Synthetic replaced the old daily tool-call quota with two new limits:
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✅swift teststill has one unrelated existing failure in"reads large stdout without deadlock"Before
After
Their billing page