model-usage: implement Antigravity provider - #6472
Conversation
There was a problem hiding this comment.
Pull request overview
Adds Antigravity usage and quota reporting to the model-usage panel.
Changes:
- Adds an Antigravity provider and scanner.
- Integrates Antigravity into the panel, defaults, and installation.
- Adds local-history scanner tests and branding.
Tip
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.
Reviewed changes
Copilot reviewed 7 out of 8 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
shell/plugins/model-usage/providers/Antigravity.qml |
Defines the provider interface and refresh lifecycle. |
shell/plugins/model-usage/scripts/antigravity_usage_scanner.py |
Collects local activity and IDE quota data. |
shell/plugins/model-usage/Main.qml |
Registers Antigravity with model usage. |
shell/plugins/model-usage/Panel.qml |
Adds branding and empty-state text. |
shell/plugins/model-usage/manifest.json |
Adds default provider settings. |
shell/plugins/model-usage/assets/antigravity.svg |
Provides the provider icon. |
install/user/mise.sh |
Installs the agy CLI. |
test/shell.d/model-usage-antigravity-scanner-test.sh |
Tests local history aggregation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| result = subprocess.run(["lsof", "-nP", "-iTCP", "-sTCP:LISTEN", "-p", pid], | ||
| capture_output=True, text=True, timeout=10) | ||
| for line in result.stdout.splitlines()[1:]: | ||
| for match in re.finditer(r":(\d+)$", line): |
|
|
||
| Antigravity { | ||
| id: antigravityProvider | ||
| enabled: root.providerEnabled("antigravity") |
| return response.status, response.read() | ||
|
|
||
|
|
||
| def parse_limits(payload): |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 8 changed files in this pull request and generated 2 comments.
Suppressed comments (3)
shell/plugins/model-usage/Main.qml:14
- Antigravity is disabled for the shipped bare widget entry.
BarModel.entrySettings()supplies{}here, whileproviderEnabled()defaults only Claude and Codex to enabled when no provider setting exists, so the scanner never runs despite the new manifest default. Default this provider to enabled unless it is explicitly disabled.
enabled: root.providerEnabled("antigravity")
shell/plugins/model-usage/scripts/antigravity_usage_scanner.py:31
- Invalid timestamps are converted to today's date, so a malformed history entry is counted as a prompt/session today instead of being ignored. Return a failure sentinel from
local_day_from_ms()and skip that entry before updating counters.
except Exception:
return datetime.now().strftime("%Y-%m-%d")
shell/plugins/model-usage/scripts/antigravity_usage_scanner.py:83
- These values are prompt counts, but the existing panel contract renders
recentDays.messageCountunder “TOKENS BY DAY” and formats each value as tokens. Antigravity users will therefore see prompts misreported as token usage. Either extend the provider/UI contract with the metric unit and render prompts, or leave this token series empty.
# Populate recentDays with local daily prompt counts. While the UI refers
# to this as "TOKENS BY DAY", displaying prompt counts allows drawing
# the weekly activity bar chart for Antigravity instead of hiding it.
"recentDays": [recent_days_map[date] for date in recent_dates],
| try: | ||
| result = subprocess.run(["ss", "-tlnp"], capture_output=True, text=True, timeout=10) | ||
| for line in result.stdout.splitlines(): | ||
| if f"pid={pid}" not in line: |
| result = subprocess.run(["lsof", "-nP", "-iTCP", "-sTCP:LISTEN", "-p", pid], | ||
| capture_output=True, text=True, timeout=10) |
Work all done by Antigravity. Prompt was: Implement shell/plugins/model-usage/providers/Antigravity.qml Signed-off-by: Rosen Penev <rosenp@gmail.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 8 changed files in this pull request and generated no new comments.
Suppressed comments (6)
shell/plugins/model-usage/scripts/antigravity_usage_scanner.py:139
- The fallback has the same discovery failure: normal
lsof -iTCP -sTCP:LISTENoutput ends with(LISTEN), so:(\d+)$cannot match it. Ifssyields no ports, this leaves only--extension_server_portto probe and can make limits appear unavailable even while the language server is running.
for match in re.finditer(r":(\d+)$", line):
ports.append(match.group(1))
shell/plugins/model-usage/Main.qml:71
- The new provider participates in the aggregate refresh state but is omitted from
lastRefreshedAtMsimmediately below. With only Antigravity enabled, a successful scan leaves the module-level timestamp at zero, so consumers of the model-usage plugin never observe its refresh time. Include the Antigravity timestamp in the aggregate.
property bool refreshing: antigravityProvider.refreshing || claudeProvider.refreshing || codexProvider.refreshing || syncRunning
shell/plugins/model-usage/scripts/antigravity_usage_scanner.py:129
- This regex only accepts a port at the end of the line, but
ss -tlnpappendsusers:(...,pid=...,fd=...)after the peer address. Therefore every line that passes the PID check fails this match, and the language server's listening ports are never discovered throughss. Parse the local-address column instead.
This issue also appears on line 138 of the same file.
for match in re.finditer(r":(\d+)(?:\s+\(LISTEN\))?\s*$", line):
ports.append(match.group(1))
shell/plugins/model-usage/scripts/antigravity_usage_scanner.py:83
- These values are prompt counts, but
recentDays.messageCountis the existing token-total field:Panel.qml:491labels it “TOKENS BY DAY” and line 697 formats each value as tokens. This makes, for example, three prompts display as three tokens and also sums incompatible units during sync. LeaverecentDaysempty until the UI supports a provider-specific “prompts by day” metric.
# Populate recentDays with local daily prompt counts. While the UI refers
# to this as "TOKENS BY DAY", displaying prompt counts allows drawing
# the weekly activity bar chart for Antigravity instead of hiding it.
"recentDays": [recent_days_map[date] for date in recent_dates],
shell/plugins/model-usage/scripts/antigravity_usage_scanner.py:31
- A malformed but present timestamp is silently converted to today's date. Such an entry is then counted as one of today's prompts/sessions and as an active day rather than being skipped, producing incorrect usage totals. Return an invalid marker here and have
scan_local_statscontinue past that entry before updating counters.
except Exception:
return datetime.now().strftime("%Y-%m-%d")
test/shell.d/model-usage-antigravity-scanner-test.sh:16
- Using the current instant and then adding one or two seconds makes this test fail when it starts just before local midnight: the later fixtures belong to tomorrow, while the assertions still expect three prompts today. Anchor both fixtures at noon so the additions cannot cross a date boundary.
today_ms=$(python3 -c "import time; print(int(time.time() * 1000))")
yesterday_ms=$((today_ms - 86400000))
Work all done by Antigravity.
Prompt was:
Implement shell/plugins/model-usage/providers/Antigravity.qml