VS Code extension for Google Antigravity IDE that provides real-time monitoring of context window usage, model quotas, and token consumption by reverse-engineering the internal language server RPC API.
graph TB
subgraph Extension Host
EXT[extension.ts<br/>Activation & Wiring]
POLL[Poller<br/>setTimeout chain]
DISC[Discovery<br/>Multi-LS Scanner]
RPC[RPC Client<br/>HTTP POST]
QS[Quota Service<br/>GetUserStatus]
CS[Context Service<br/>Multi-Source Token Tracking]
MR[Model Registry<br/>Cockpit Cache JSON]
SB[Status Bar<br/>Compact Metrics]
SIDE[Sidebar WebView<br/>Dashboard]
CFG[Config / Settings]
end
subgraph "Antigravity Language Servers (1 per workspace)"
LS1["LS₁ 127.0.0.1:PORT₁<br/>workspace: project-A"]
LS2["LS₂ 127.0.0.1:PORT₂<br/>workspace: project-B"]
end
subgraph OS / Filesystem
PS["Process Table<br/>(ps / ss)"]
CACHE["~/.antigravity_cockpit/cache<br/>quota_api_v1_plugin/*.json"]
end
EXT --> POLL
EXT --> DISC
EXT --> MR
EXT --> SB
EXT --> SIDE
DISC -->|"scan ps for --csrf_token<br/>+ --workspace_id"| PS
DISC -->|"probe ports via ss"| PS
DISC -->|ServerConnection| RPC
POLL -->|tick every N sec| QS
POLL -->|tick every N sec| CS
QS -->|"POST GetUserStatus"| RPC
CS -->|"POST GetCascadeTrajectorySteps<br/>POST GetCascadeTrajectoryGeneratorMetadata<br/>POST GetCascadeTrajectory"| RPC
RPC -->|"HTTP + CSRF header"| LS1
RPC -->|"HTTP + CSRF header"| LS2
MR -->|"watch & parse JSON"| CACHE
QS -->|QuotaSnapshot| SB
QS -->|QuotaSnapshot| SIDE
QS -->|"getModelLabelById()"| CS
CS -->|ContextSnapshot| SB
CS -->|ContextSnapshot| SIDE
MR -->|model limits| CS
CFG -->|display toggles| SB
flowchart TD
subgraph "Multi-Source Strategy (freshness order)"
S1["① GetCascadeTrajectorySteps<br/>→ steps[].metadata.modelUsage<br/>🟢 FRESHEST (per-flush)"]
S2["② GetCascadeTrajectoryGeneratorMetadata<br/>→ generatorMetadata[].chatModel.usage<br/>🟡 BATCH (lags 45+ entries)"]
S3["③ GetCascadeTrajectory<br/>→ numTotalSteps, numTotalGM<br/>🔵 METADATA only"]
end
S1 --> CMP{Compare totals}
S2 --> CMP
S3 -->|diagnostic| LOG[Debug Log]
CMP -->|"Compare step index<br/>(monotonic fallback)"| RESULT[StepTokenInfo]
RESULT --> SNAP[ContextSnapshot]
subgraph UI
SNAP --> SB[Status Bar]
SNAP --> SIDE[Sidebar]
end
Antigravity spawns one Language Server per workspace. Each LS has its own:
- PID, PPID, CSRF token, workspace_id
- HTTP port (JSON-RPC), HTTPS port (gRPC), extension port
- In-memory trajectory fork after
LoadTrajectory
Critical: A cascade/conversation may be loaded on ANY LS — not necessarily the one matching the current VS Code workspace. Per-conversation reads must be routed through the LS that owns the data, which may differ from the workspace-matched LS.
Workspace-based priority (v0.3.8): Discovery sorts LS candidates by workspace_id match against the current VS Code workspace path. Each IDE window runs its own Extension Host; since LS processes are children of --type=utility workers (not Extension Hosts directly), PPID matching is not reliable and was removed.
Discovery logs the computed wsId and each candidate with MATCH/no annotation:
Workspace matching: wsId="file_home_user_project_A" | candidates: file_home_user_project_A(MATCH), file_home_user_project_B(no)
| Endpoint | Purpose | Status | Freshness |
|---|---|---|---|
GetUserStatus |
Plan, quotas, model configs | ✅ Live, primary | Real-time |
GetAllCascadeTrajectories |
Discover cascadeId by workspace | ✅ Live (may return empty) | Real-time |
GetCascadeTrajectorySteps |
Step buffer (~1135 sliding window) | ✅ Primary token source | Per-flush (freshest) |
GetCascadeTrajectoryGeneratorMetadata |
GM array — one per LLM call | ✅ Fallback token source | Batch (lags 45+ entries) |
GetCascadeTrajectory |
Trajectory summary + numTotalSteps/numTotalGM |
✅ Diagnostics | Per-flush |
GetUserTrajectoryDescriptions |
List of trajectory IDs per workspace | ✅ Live, discovery only | Real-time |
StreamAgentStateUpdates |
Real-time push of full state | Real-time during RUNNING | |
GetBrowserOpenConversation |
Currently open conversation | Real-time |
GetCascadeTrajectorySteps returns a sliding window of ~1135 steps. Each step with metadata.modelUsage contains:
| Field | Type | Description |
|---|---|---|
inputTokens |
string | Uncached prompt tokens |
cacheReadTokens |
string | Cached prompt tokens (Anthropic prompt cache) |
outputTokens |
string | Model output tokens |
model |
string | Internal model ID |
apiProvider |
string | Provider (e.g. API_PROVIDER_ANTHROPIC_VERTEX) |
Walking backwards from the last step gives the freshest available token counts.
GetCascadeTrajectoryGeneratorMetadata returns the full generatorMetadata[] array. Each entry has chatModel.usage with the same fields. Updates in batches — can lag behind Steps by 45+ entries and 50K+ tokens.
Context window usage = estimatedTokensUsed ?? (inputTokens + cacheReadTokens + outputTokens)
When available,
estimatedTokensUsedfromchatModel.contextWindowMetadatais the authoritative server-computed value. GM estimate is only merged into Steps data when both share the sameprogressionIndex(v0.3.12: prevents stale cross-turn contamination). Fallback:inputTokens(uncached) +cacheReadTokens(cached) +outputTokens. Both input components occupy context window space.ContextSnapshot.totalSourcetracks which path was taken:'gm-estimate','derived-sum', or'none'.
GetCascadeTrajectorySteps: Returns a ~1135-step sliding window. IgnoresstartIndex/endIndexparams — always returns the same window centered around the latest checkpoint. The LAST step withmodelUsageis the freshest token data.GetCascadeTrajectoryGeneratorMetadata: Returns the fullgeneratorMetadata[]array directly. Batch-updated:numTotalGM(from trajectory) can exceed the returned array size by 45+ entries. No pagination params.GetCascadeTrajectory: ReturnsnumTotalStepsandnumTotalGeneratorMetadatafor diagnostic comparison. GM entries nested insidetrajectory.generatorMetadata[]— often less fresh than the dedicated GM endpoint.StreamAgentStateUpdates: Requires Connect streaming framing:Content-Type: application/connect+json- Binary envelope:
0x00 + uint32_be(length) + JSON_payload - Returns full state snapshot (17MB+) as first frame
- Accepts
{conversationId}(same as cascadeId) transfer-encoding: chunked— long-lived connection- During IDLE: sends initial snapshot only, no further deltas observed
- During RUNNING: likely sends delta frames (not yet confirmed)
- Future work: implement as primary real-time source
- Governs lifecycle and immediate wiring.
- Sync-First Binding: WebviewViewProvider endpoints and Commands are attached synchronously right at activation (
onView/onCommand). This satisfies VS Code's view lifecycle boundaries, preventing failures if initialization components likeModelRegistryact asynchronously. - State restoration from
globalStatecache directly passes rehydrated object hierarchies into initializing singletons (like Quota Service and Context Service).
- Cross-platform: Uses
ps -eo pid,ppid,argson Linux/macOS,wmicon Windows for process scanning. Port listing viass/lsof(Linux/macOS) ornetstat -ano(Windows). - Scans OS processes for ALL
language_serverinstances - Extracts
--csrf_tokenand--workspace_id(both--flag valueand--flag=valueformats) - Logs all LS instances with workspace IDs and PPID match status
- Prioritizes workspace-matched LS, falls back to first responder
- Discovers listening ports via
ss -tlnp(Linux) /lsof(macOS) matched by PID - Probes each port with HTTP POST to
GetUserStatusto find the JSON-RPC port - Filters out gRPC/HTTPS ports (only HTTP works without cert conflicts)
- Extracts
ServerConnection { host, port, csrfToken, pid, ppid }
- JSON-over-HTTP POST to
exa.language_server_pb.LanguageServerService/* - CSRF token authentication via
X-Codeium-Csrf-Tokenheader - HTTP only (avoiding HTTPS to prevent conflicts with IDE's internal gRPC)
- Shared
http.Agent({ keepAlive: true })used by both QuotaService and ContextService (v0.3.12) - Gracefully handles
ECONNREFUSEDinternally to suppress irrelevant or transitional error noise during standard reconnects.
- Non-overlapping setTimeout chain (not setInterval)
- Exponential backoff on failure (capped at 2 min)
- Immediate recovery to base interval on success
- AbortController for clean shutdown
- Parses
GetUserStatus→cascadeModelConfigDatafor per-model quotas remainingFractionis 0.0–1.0 float (missing = 0% = depleted)- Extracts
userTier.namefor plan name (e.g. "Google AI Ultra") - Extracts
userTier.availableCreditsfor total AI credits - Normalizes prompt/flow credits with percentage calculations
- Alphabetically sorted model list for stable UI
- Cache Hydration: Responsible for coercing serialized UI snapshot data (like nested
resetTimeproperties which stringify as ISO-8601 strings) back into JavascriptDateobjects upon startup. - Model ID Resolution (v0.3.11): Exposes
getModelLabelById(modelId)method that resolves internal model constants (e.g.MODEL_PLACEHOLDER_M47) to human-readable display labels (e.g.Gemini 3 Flash). Used byContextServiceas the authoritative, zero-cost model name resolution source.
- Step 1: Discover all language servers to route requests accurately. All RPC calls go through shared
rpc-client.tstransport (v0.3.12). - Step 2 (Pass 1): Collect-and-rank active conversations via
GetBrowserOpenConversationacross ALL LS instances. Score by: workspace match → last modified time → step count. No earlybreak— the highest-scoring candidate wins. - Step 2 (Pass 2 fallback): If Pass 1 returns nothing, fall through to
GetAllCascadeTrajectories. Workspace matching uses segment-boundary comparison (v0.3.12:uriSegmentMatch). Trajectories without workspace metadata are rejected unlessallowUnknownWorkspaceFallbackis enabled. - Step 3 (Owner Resolution): After selecting
cascadeId, concurrently query both thebestGlobalConn(active window LS) and the cached port (if any). The candidate with the higherprogressionIndexwins. If the active LS exceeds the cached progression, the cache is immediately evicted and replaced. Owner switch clears LoadTrajectory suppression for the cascade. Falls through to a full LS-scan only when neither candidate responds. Cache stored asownerCache(Map<cascadeId, {port, lastProgression}>). - Step 4: TTL-based
LoadTrajectoryfallback on the owner LS for recovering cold conversations. Suppression expires afterloadTrajectoryTtlSeconds(default 120s) and is cleared on owner port switch (v0.3.12). - Step 5: Multi-source token fetch via owner LS:
- Source 1:
GetCascadeTrajectorySteps→ last step withmetadata.modelUsage - Source 2:
GetCascadeTrajectoryGeneratorMetadata→ last GM entry with token data +contextWindowMetadata.estimatedTokensUsed - Source 3:
GetCascadeTrajectory→numTotalSteps/numTotalGMfor diagnostics
- Source 1:
- Step 6: Compare sources via monotonic step index fallback (picks by progression, not totals). GM
estimatedTokensUsedis only merged into Steps whengmProg === stepsProg(v0.3.12: prevents stale estimate contamination). - Step 7: Read server-computed
estimatedTokensUsedas authoritative context window usage. Fallback:inputTokens + cacheReadTokens + outputTokens.isEstimatedflag honestly reflects total source.ContextSnapshot.totalSourcetracks provenance. - Model name resolution (v0.3.11): Uses a 3-tier strategy:
- QuotaService lookup (authoritative):
QuotaService.getModelLabelById(modelId)resolves internal IDs (e.g.MODEL_PLACEHOLDER_M47) to display labels (e.g.Gemini 3 Flash) using liveGetUserStatusRPC data. This is the primary, zero-cost resolution path. - ModelRegistry fallback: Substring matching against Cockpit cache data with version-aware sorting (3.1 > 3.0 > 2.5).
- Provider fallback: Last resort — matches by
apiProviderfamily (Google, Anthropic, OpenAI), sorted by version then context limit.
- QuotaService lookup (authoritative):
- Context limits from Model Registry (
maxTokensper model) or QuotaService label matching.
- Reads
~/.antigravity_cockpit/cache/quota_api_v1_plugin/authorized/*.json - Parses chat model metadata:
displayName,maxTokens,modelId - FSWatcher for live updates when cache files change
- Provides
getChatModels()to Context Service for limit resolution
- Format:
$(pulse) Opus 133K/200K (67%) | 🟢Flash 100% 🔴Opus 0% | 💎10K - Configurable sections via settings:
statusBar.showContextWindow— toggle context displaystatusBar.models— filter which models show quota dots (empty = all)statusBar.showCredits— toggle credits display
- Model grouping: deduplicates variants (e.g. Gemini Pro High/Low → "Pro")
- Short names: Opus, Sonnet, Pro, Flash, GPT
- Rich tooltip with full breakdown
- Click → opens sidebar dashboard
- WebView with CSP + nonce security
- Native VS Code theme variable integration (
--vscode-*) - Sections: Connection, Context Window (with progress bar), Model Quotas, Credits
- PostMessage bridge for state updates
- Refresh and Show Logs buttons
graph LR
subgraph "Settings (antigravityEngineer.*)"
A[pollingInterval: 30s]
B[lowQuotaThreshold: 30%]
C[criticalQuotaThreshold: 10%]
D[contextLimitOverrides: object]
E[serverHost: 127.0.0.1]
F[debugMode: false]
G["statusBar.showContextWindow: true"]
H["statusBar.models: string[]"]
I["statusBar.showCredits: true"]
J["allowUnknownWorkspaceFallback: false"]
K["loadTrajectoryTtlSeconds: 120"]
end
- All traffic is local (
127.0.0.1only) - CSRF token from process arguments (never stored externally)
- WebView uses Content Security Policy with nonce
- No external network calls
- No telemetry or analytics
- Token values redacted in diagnostic logs
- Batch-updated data: Both Steps and GM sources update in batches (not per-turn). Token counts may lag a few turns behind the actual context window state.
- No per-turn push:
StreamAgentStateUpdatesonly sends an initial snapshot during IDLE. Delta frames during RUNNING are not yet confirmed/implemented. - Multi-LS routing: Addressed in v0.3.8 via workspace-based discovery sorting + concurrent live+cache owner resolution. Each poll concurrently checks both the active-window LS and the cached LS; the one with the higher
progressionIndexwins, so model/token display updates immediately after any new message without waiting for a cache eviction timeout. - Sliding window: Steps API returns ~1135 steps. For very long conversations, older steps fall out of the window.
- Best-effort workspace binding: Two IDE windows with identical workspace paths cannot be deterministically disambiguated. Winner is selected by freshness/step heuristics.