Skip to content

feat: add official rate limits tracking for Antigravity 2.0 and IDE - #210

Merged
chattymin merged 1 commit into
chattymin:mainfrom
justinjeong5:feat/antigravity-rate-limits
Aug 23, 2026
Merged

chattymin merged 1 commit into
chattymin:mainfrom
justinjeong5:feat/antigravity-rate-limits

Conversation

@justinjeong5

@justinjeong5 justinjeong5 commented Aug 21, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds official rate limit / quota tracking for Antigravity 2.0 and IDE, connecting to the internal Google CloudCode Quota Summary API (retrieveUserQuotaSummary).

Previously, PokeTokenBar only supported official limits for Claude Code and Codex, leaving Antigravity with local token counts only. This change brings Antigravity to full feature parity with Claude and Codex official limits.

Follow-up to #141 (Antigravity CLI usage) and #182 (Antigravity 2.0 multi-root discovery).


UI changes

Before (As-Is) After (To-Be)
Antigravity only tracked local token spend; no official rate limit gauges or reset timers. Official quota buckets for Gemini Models & Claude/GPT 3P models (5h & weekly) with relative reset countdowns, plus menu bar limit display (AGY <percent>%).
Before After

Key Changes

1. Quota Summary Provider (AntigravityRateLimitsProvider.swift)

  • Queries https://daily-cloudcode-pa.googleapis.com/v1internal:retrieveUserQuotaSummary (with fallback to cloudcode-pa.googleapis.com).
  • Authenticates using the OAuth token stored in macOS Keychain (service: "gemini", account: "antigravity") or ~/.gemini/jetski-standalone-oauth-token.
  • Automatic Token Refresh: Automatically exchanges refresh_token with Google OAuth token endpoint (oauth2.googleapis.com/token) when access token expires.
  • No-UI Keychain Policy: Adheres to the strict background non-interactive Keychain reading policy (kSecUseAuthenticationUIFail) to prevent unexpected system authentication popups during background polling. Interactive unlock is only requested when users explicitly click "Tap to load".

2. Domain Models & Calculations (Models.swift)

  • Added AntigravityRateLimitStatus, AntigravityQuotaGroup, and AntigravityQuotaBucket.
  • Accurately converts CloudCode's remaining fraction ($0.0 \sim 1.0$) to usage percentage ($100.0 - \text{remainingFraction} \times 100.0$).
  • Separates quota groups into Gemini Models and Claude and GPT models (3P), exposing both 5-hour session and weekly reset buckets.

3. Pipeline & App Integration (UsageStore.swift, PopoverView.swift)

  • Menu Bar Line: Displays AGY <percent>% on the second line of the menu bar when Antigravity has active spend today.
  • Popover UI: Visualizes progress bars, usage percentages, and reset countdown relative timers for each model group in the Antigravity tab.
  • Rare Candy Rules: Registered in candyEligibleWindows with 1 candy for 5h session windows and 5 candies for weekly windows upon reaching 100% capacity.
  • Alerts & Floating Pet: Triggers 80% warning and 90% critical notifications and speech bubbles.

4. Localization (Localization.swift)

  • Full translation support for English, Korean, Japanese, and Spanish.

Type of change

  • New feature
  • Bug fix
  • Refactoring
  • Documentation

Checklist

  • swift build and swift test pass locally
  • Adheres to No-UI Keychain policy (no background password popups)
  • Preserves existing 2-line menu bar space constraints
  • Multi-language support included (en, ko, ja, es)
  • Unit tests added for quota parsing, used percent math, and candy window registration
  • Tested against live CloudCode Quota API responses with 100% accuracy

Tests

  • AntigravityRateLimitsProviderTests:
    • testDecodingQuotaSummary: Verifies JSON parsing of Gemini and 3P model groups, 5h/weekly bucket identification, and usedPercent inverted fraction calculations.
    • testUsageStoreIntegration: Verifies menuLines formatting (AGY 50%), candyEligibleWindows registration, and limit warning evaluations.
  • Full unit test suite: 734 tests passed, 0 failures.

🤖 Generated with Antigravity 2.0 (AGY 2.0)

- Add AntigravityRateLimitsProvider to query Google CloudCode Quota API (v1internal:retrieveUserQuotaSummary)
- Support both Gemini Models and Claude/GPT 3P models with 5h session and weekly quota buckets
- Implement silent background Keychain read (No-UI policy) and automatic Google OAuth refresh
- Integrate Antigravity quota into menu bar limits, popover gauge view, limit warning alerts, and Rare Candy grants
- Add unit tests for quota decoding, used percentage calculations, and UsageStore integration
@justinjeong5
justinjeong5 force-pushed the feat/antigravity-rate-limits branch from d52c1f5 to 14e43f5 Compare August 21, 2026 05:39
@chattymin
chattymin merged commit de6c996 into chattymin:main Aug 23, 2026
2 checks passed
@chattymin

Copy link
Copy Markdown
Owner

Thank you for your contribution!

chattymin added a commit that referenced this pull request Aug 23, 2026
#185 made `L.t` five-argument at 14:45:33; #210 merged four-argument
Antigravity strings 104 seconds later. GitHub reported the merge as
clean because the two changes never touch the same lines, so `main`
landed with 24 "missing argument for parameter #5" errors.

Adds `fr` to the three Antigravity limit strings. No behaviour change.
chattymin added a commit that referenced this pull request Aug 23, 2026
* fix: keep the automatic poll out of the Keychain for Antigravity

`allowKeychainPrompt: false` is not "do not prompt", it is "do not query
the Keychain at all" — a no-UI query does not suppress the password
dialog for a locked or unapproved login keychain, which is why the
Claude path ends before `SecItemCopyMatching`. The Antigravity provider
took the silent read on both branches, so a background poll issued two
Keychain queries.

Measured on the merged code, same call on the same machine:

    antigravity auto-path keychain reads = 2
    claude      auto-path keychain reads = 0

The file credential also never short-circuited: a valid token was read,
cached, and then the code fell through to the Keychain anyway.

Routes both `SecItemCopyMatching` call sites through `KeychainReader` so
the contract is countable, and adds the guard the store-level test could
not provide — it only ever checked the flag the store passes, which was
already correct.

* docs: record Antigravity official limits and the hosts they contact

#210 shipped Antigravity 5h/weekly limits and a menu bar line without
touching the READMEs, which still listed Antigravity as having no
official limits.

The outbound-request paragraph was also wrong, and that one is a claim
about network behaviour: it enumerated seven hosts by name, and the
quota API adds `cloudcode-pa.googleapis.com`,
`daily-cloudcode-pa.googleapis.com` and `oauth2.googleapis.com`.
cte13 pushed a commit to cte13/PokeTokenBar-iOS that referenced this pull request Aug 23, 2026
Brings in upstream features and integrates them with the fork's iOS/OpenCode Go work:
- Antigravity 2.0 / IDE official rate-limit tracking (chattymin#210, chattymin#219, chattymin#220)
- French (fr) UI language (chattymin#185) — added fr strings to fork-only labels
- Per-provider additional scan folders (chattymin#177/chattymin#187)
- Account-labeled official limits (chattymin#199), swift.org Swift 6.3 toolchains (chattymin#216),
  claude-fable-5 pricing (chattymin#198)

Conflicts resolved in Localization, UsageStore, PopoverView, and their tests by
keeping both the fork's OpenCode Go limits and upstream's Antigravity limits, and
by giving fork-only localized labels their French values.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
chattymin added a commit that referenced this pull request Aug 23, 2026
…ers (en/ko/ja)

Three UI features have shipped since v2.5.1 without an image: pinning a
representative Pokémon (#158), official rate limits for Antigravity (#210), and
per-provider additional scan folders (#187). Per-provider scan folders were not
mentioned in the READMEs at all.

Adds two screenshots per language for the Antigravity limits panel and the
Advanced scan-folder controls, and inserts the Representative Pokémon row into
the existing settings shots rather than redrawing them, so every pixel outside
the inserted row is byte-identical to the previous asset.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
chattymin added a commit that referenced this pull request Aug 24, 2026
…ers (en/ko/ja) (#223)

Three UI features have shipped since v2.5.1 without an image: pinning a
representative Pokémon (#158), official rate limits for Antigravity (#210), and
per-provider additional scan folders (#187). Per-provider scan folders were not
mentioned in the READMEs at all.

Adds two screenshots per language for the Antigravity limits panel and the
Advanced scan-folder controls, and inserts the Representative Pokémon row into
the existing settings shots rather than redrawing them, so every pixel outside
the inserted row is byte-identical to the previous asset.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
lukemonaghan pushed a commit to lukemonaghan/PokeTokenBar that referenced this pull request Aug 24, 2026
…hattymin#210)

- Add AntigravityRateLimitsProvider to query Google CloudCode Quota API (v1internal:retrieveUserQuotaSummary)
- Support both Gemini Models and Claude/GPT 3P models with 5h session and weekly quota buckets
- Implement silent background Keychain read (No-UI policy) and automatic Google OAuth refresh
- Integrate Antigravity quota into menu bar limits, popover gauge view, limit warning alerts, and Rare Candy grants
- Add unit tests for quota decoding, used percentage calculations, and UsageStore integration
lukemonaghan pushed a commit to lukemonaghan/PokeTokenBar that referenced this pull request Aug 24, 2026
…ttymin#221)

* fix: keep the automatic poll out of the Keychain for Antigravity

`allowKeychainPrompt: false` is not "do not prompt", it is "do not query
the Keychain at all" — a no-UI query does not suppress the password
dialog for a locked or unapproved login keychain, which is why the
Claude path ends before `SecItemCopyMatching`. The Antigravity provider
took the silent read on both branches, so a background poll issued two
Keychain queries.

Measured on the merged code, same call on the same machine:

    antigravity auto-path keychain reads = 2
    claude      auto-path keychain reads = 0

The file credential also never short-circuited: a valid token was read,
cached, and then the code fell through to the Keychain anyway.

Routes both `SecItemCopyMatching` call sites through `KeychainReader` so
the contract is countable, and adds the guard the store-level test could
not provide — it only ever checked the flag the store passes, which was
already correct.

* docs: record Antigravity official limits and the hosts they contact

chattymin#210 shipped Antigravity 5h/weekly limits and a menu bar line without
touching the READMEs, which still listed Antigravity as having no
official limits.

The outbound-request paragraph was also wrong, and that one is a claim
about network behaviour: it enumerated seven hosts by name, and the
quota API adds `cloudcode-pa.googleapis.com`,
`daily-cloudcode-pa.googleapis.com` and `oauth2.googleapis.com`.
lukemonaghan pushed a commit to lukemonaghan/PokeTokenBar that referenced this pull request Aug 24, 2026
…ers (en/ko/ja) (chattymin#223)

Three UI features have shipped since v2.5.1 without an image: pinning a
representative Pokémon (chattymin#158), official rate limits for Antigravity (chattymin#210), and
per-provider additional scan folders (chattymin#187). Per-provider scan folders were not
mentioned in the READMEs at all.

Adds two screenshots per language for the Antigravity limits panel and the
Advanced scan-folder controls, and inserts the Representative Pokémon row into
the existing settings shots rather than redrawing them, so every pixel outside
the inserted row is byte-identical to the previous asset.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
cte13 pushed a commit to cte13/PokeTokenBar-iOS that referenced this pull request Aug 30, 2026
…redentials

The limits providers in UsageStore.init default to real implementations, so any
test that constructs a store without injecting a stub uses the real
OAuthLimitsProvider. That looked harmless because the no-prompt path never reads
the Keychain — but that only holds when the credential is absent as a file. On a
machine with ~/.claude/.credentials.json (Linux Claude Code, or a Mac mirroring
the Keychain into a file) the no-prompt path succeeds, and the suite calls the
usage and profile endpoints with the developer's real account token. Verified by
removing the guard: the fetch returned a live LimitStatus in 0.669s.

OpenCodeGoLimitsProvider already gated exactly this; Claude and Antigravity were
left out of that sweep. The gate goes at the network boundary rather than at
fetch entry — gating before token acquisition zeroes the Keychain query count on
the user path, which makes the companion assertion in KeychainAutoPathTests
vacuous (chattymin#210). What must be blocked is the outgoing call, not the credential
read. Antigravity's refreshGoogleToken is gated separately, since spending the
user's refresh token is a larger side effect than a read.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
cte13 pushed a commit to cte13/PokeTokenBar-iOS that referenced this pull request Aug 31, 2026
…-only

The probe answered its question, so the diagnostic comes out as promised in #34.

The conclusion is narrower than I first wrote, and the correction matters enough
to state plainly: this is not a missing feature, it is a product mismatch.
Official Antigravity limits were built for Antigravity 2.0 and the IDE — that is
what chattymin#210 added, what the README advertises, and what the shipped screenshot
shows working. They are not broken. They simply do not cover the Antigravity CLI.

With a CLI credential, retrieveUserQuotaSummary returns 403 "You do not have a
valid license of this product". That reads as a statement about the account; it
is a statement about the endpoint, whose licence belongs to the IDE side. I
misread it that way twice.

There is no CLI-side substitute either. The CLI never calls that method — 46
streamGenerateContent, 5 loadCodeAssist, 2 fetchAvailableModels, 0
retrieveUserQuotaSummary — and the loadCodeAssist response its quota_manager does
use carries no quota, usage, limit or reset field at depth 3. Its tier
descriptions are marketing copy with no figures, including one named "Antigravity
Starter Quota" that states no number. That leaves the CLI's /usage panel deriving
figures from the streamGenerateContent calls it already makes, which a passive
monitor cannot replicate without consuming the quota it wants to measure.

So for a CLI-only install: usage tracking works from local logs, limit bars do
not appear, and there is nothing in this app to fix. Installing the IDE is the
answer for anyone who wants the bars.

The defect log entry leads with "read this before digging again" because this
went down four wrong paths — account licence, CLI-versus-IDE method, a
loadCodeAssist parser, and finally "no consumer quota API at all", which was also
too broad.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
cte13 added a commit to cte13/PokeTokenBar-iOS that referenced this pull request Aug 31, 2026
…nly (#37)

#36 merged the version of this entry I wrote before seeing the upstream feature
description. It claims Antigravity has no consumer-facing quota API, which is
too broad and would send the next reader down a fifth wrong path from inside the
document meant to prevent exactly that.

Official Antigravity limits work. chattymin#210 added them as "official rate limits
tracking for Antigravity 2.0 and IDE", the README says "Antigravity 2.0 and the
IDE report real quota, not an estimate", and assets/screenshot-antigravity-limits.png
shows the buckets rendering. The correct scoping is not consumer versus
enterprise, it is CLI versus IDE.

So a CLI-only install is a product mismatch rather than a defect: usage tracking
works from local logs, limit bars do not appear, and installing the IDE is the
answer for anyone who wants them. Nothing in this app needs changing.

The measurements behind it are unchanged — the 403 on retrieveUserQuotaSummary,
the CLI never calling that method, and loadCodeAssist carrying no quota field at
depth 3. Only the conclusion drawn from them was wrong.

Co-authored-by: Cory Eurom <ce@Corys-MacBook-Pro.local>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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