fix: update Chutes quota usage display#106
Conversation
Integrate Nano-GPT as a quota-based provider with daily/monthly reset tracking, local icon assets, and auth parsing support. Add UI/CLI wiring and tests so Nano-GPT usage, balances, and the subscription preset are available end-to-end.
…ations Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
# Conflicts: # CopilotMonitor/CopilotMonitor.xcodeproj/project.pbxproj # CopilotMonitor/CopilotMonitor/App/StatusBarController.swift # CopilotMonitor/CopilotMonitor/Helpers/ProviderMenuBuilder.swift # CopilotMonitor/CopilotMonitor/Providers/NanoGptProvider.swift # CopilotMonitor/CopilotMonitorTests/NanoGptProviderTests.swift
|
This PR enhances the Chutes provider by adding monthly value cap tracking (5x subscription cost) alongside daily requests, and introduces a robust fallback mechanism for the Antigravity provider when cache fetching fails. Solid work here on adding the monthly cap tracking and the Antigravity fallback logic, it's a much-needed reliability boost. ngl, the recursive JSON parsing in For a detailed, line-by-line code review (including bug detection and security checks), please write a comment like |
✅ AI Code Review Completed
Reviewed with |
There was a problem hiding this comment.
Review Summary
nice work overall — the Chutes dual-quota model (daily requests + monthly 5× value cap) is well thought out and cleanly integrated. the Antigravity accounts fallback is a solid reliability improvement too. tests cover the new static methods and even the UTC month-boundary edge case, which is great.
a few things worth calling out:
Good stuff:
- the
extractMonthlyValueUsedUSDrecursive parser is clever with its fallback chain (aggregate → nested → item sum). handles both structured and flat API responses gracefully. - UTC date handling for month boundaries is correct and the test proves it (
2026-03-01T00:30:00+14:00→ February in UTC). that's exactly the edge case people forget. - Antigravity fallback account resolution logic is thorough — preferred index → active index → first enabled account, with proper validation at each step.
- all CI checks pass (Build, Test, Lint, CI) ✅
Minor concerns (see inline comments):
- JSON string interpolation in the Antigravity fallback request body is a latent injection risk
- the balance-based fallback for monthly value tracking has an assumption that may not hold
- the generic key matching in
extractMonthlyValueUsedUSDcould pick up unrelated fields
none of these are blockers — the PR is solid and ready to merge with a couple of things to keep in mind for future iterations.
The bot will re-review automatically when a new commit is pushed to this branch, or you can trigger a review by mentioning @op-gg-ai-devops in a comment. You can also add specific directions, e.g.:
@op-gg-ai-devops review typos@op-gg-ai-devops review security@op-gg-ai-devops focus on performance
| request.httpMethod = "POST" | ||
| request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization") | ||
| request.setValue("application/json", forHTTPHeaderField: "Content-Type") | ||
| request.httpBody = "{\"project\":\"\(account.projectId)\"}".data(using: .utf8) |
There was a problem hiding this comment.
String-interpolated JSON body: injection risk
String interpolation inside a raw JSON literal means if projectId ever contains a double-quote, backslash, or other JSON-special character, this produces malformed JSON (or worse, allows structure injection).
The existing GeminiCLIProvider does the same thing so it's a pre-existing pattern, but since you're adding a new callsite it's worth fixing here:
| request.httpBody = "{\"project\":\"\(account.projectId)\"}".data(using: .utf8) | |
| let bodyDict = ["project": account.projectId] | |
| request.httpBody = try JSONSerialization.data(withJSONObject: bodyDict) |
This guarantees proper escaping regardless of projectId contents.
| } | ||
|
|
||
| if balance >= 0, balance <= monthlyValueCapUSD { | ||
| let inferredUsed = max(0, monthlyValueCapUSD - balance) |
There was a problem hiding this comment.
Balance-based fallback: may misattribute unrelated balance
This fallback assumes balance directly tracks the monthly value cap (i.e., used = cap - balance). But ChutesUserProfile.balance looks like it's an independent credits/wallet balance, not the remaining monthly value allowance.
If a user deposited extra credits, balance could exceed monthlyValueCapUSD, and this branch is skipped (the balance <= monthlyValueCapUSD guard). But if a user has a low balance for unrelated reasons, inferredUsed would be artificially high.
Consider adding a comment explaining when this heuristic is valid, or gate it behind a more specific condition (e.g., only use it for plans where balance actually tracks cap consumption).
| let encodedUserId = userId.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? userId | ||
| var components = URLComponents(string: "https://api.chutes.ai/users/\(encodedUserId)/usage") | ||
| components?.queryItems = [ | ||
| URLQueryItem(name: "page", value: "1"), |
There was a problem hiding this comment.
Pagination cap + generic key matching: potential undercount
Two things about this endpoint call:
-
Pagination:
limit=500is hardcoded with no continuation. If a heavy user has 500+ usage records in a month,sumMonthlyValueUsedUSDwill underreport the total cost. Consider either bumping the limit or adding pagination (fetch page 2+ until results are exhausted). -
Generic key matching in
extractMonthlyValueUsedUSD: The fallback key list ("cost","amount_usd", etc.) is very broad and searches recursively. If the API response contains a field like"cost"meaning per-unit cost rate rather than total cost, or a nested object happens to have"cost"for something unrelated, the extracted value would be wrong. This is a reasonable defensive approach when the API schema isn't documented, but worth a TODO to lock it down once the actual response shape is confirmed.
Summary
Update
Verification
xcodebuild build -project "CopilotMonitor/CopilotMonitor.xcodeproj" -scheme "CopilotMonitor" -configuration Debug -destination 'platform=macOS' -clonedSourcePackagesDirPath "/Users/rubenbeuker/Library/Developer/Xcode/DerivedData/CopilotMonitor-aggycqisuzewkzczboobrrtuajll/SourcePackages"xcodebuild test -project "CopilotMonitor/CopilotMonitor.xcodeproj" -scheme "CopilotMonitor" -destination 'platform=macOS' -clonedSourcePackagesDirPath "/Users/rubenbeuker/Library/Developer/Xcode/DerivedData/CopilotMonitor-aggycqisuzewkzczboobrrtuajll/SourcePackages" -only-testing:CopilotMonitorTests/ProviderUsageTests/usr/bin/log show --last 2m --predicate 'subsystem == "com.opencodeproviders"'