feat(opencode-go): show subscription quota on the dashboard - #3337
Open
ntdatt812 wants to merge 1 commit into
Open
feat(opencode-go): show subscription quota on the dashboard#3337ntdatt812 wants to merge 1 commit into
ntdatt812 wants to merge 1 commit into
Conversation
opencode-go connections rendered nothing on the quota page: the registry
carried no features block, so USAGE_SUPPORTED_PROVIDERS / USAGE_APIKEY_PROVIDERS
never listed the provider and /api/usage/[connectionId] answered "Usage not
available" before any fetch. misc.js had no fetcher and USAGE_HANDLERS had no
entry either.
The usage endpoint is declared as transport.usage.url and read through U(),
the way glm and vercel-ai-gateway already do, rather than hardcoded in the
fetcher. Behaviour follows the upstream route
(anomalyco/opencode packages/console/app/src/routes/zen/go/v1/usage.ts):
- 401 is an invalid key, but 403 is an EntitlementError — the key is fine and
the account has no Go plan. Reporting that as an expired key would send the
user off to reissue a working one.
- Error bodies are {type:"error",error:{message}}; surface the message instead
of echoing the JSON envelope.
- percent is a server-floored 0..100 and resetsAt an ISO string, so used is the
percent, total is 100, and resetAt goes through parseResetTime.
- Labels carry no duration: only the rolling window is a fixed span (its length
is plan config and absent from the payload), weekly resets on a calendar week
boundary and monthly on the subscription anniversary, so "7d"/"30d" would be
wrong. Each row already renders its own countdown from resetAt.
opencode-go joins the deepseek case in parseQuotaData so remainingPercentage is
forwarded and no absolute `remaining` is (the UI reads `remaining` as a 0-100
percentage). For a used=percent/total=100 row the default branch computes the
same number, so this is for intent, not a rendering fix.
Diagnosis by the reporter in decolua#3334.
ntdatt812
force-pushed
the
feat/opencode-go-quota-3334
branch
from
August 15, 2026 08:24
e364a96 to
0ecef82
Compare
This was referenced Aug 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #3334.
opencode-goconnections render nothing on the quota dashboard whileollama,deepseekandantigravityrender progress bars. The provider is a paid subscription with real rolling/weekly/monthly windows, so there is something to show.The diagnosis in #3334 is the reporter's. I checked each of its claims against this codebase and against the upstream route source rather than taking them on trust — three hold, one does not, and the upstream source settled several details the issue could only guess at.
What the upstream route actually returns
The endpoint is first-party but undocumented. Its implementation is
anomalyco/opencode→packages/console/app/src/routes/zen/go/v1/usage.ts(added in anomalyco/opencode#16513), and it decides the whole contract:{ "usage": { "rolling": { "status": "ok", "percent": 12, "resetsAt": "<ISO>" }, "weekly": { "status": "ok", "percent": 47, "resetsAt": "<ISO>" }, "monthly": { "status": "rate-limited", "percent": 100, "resetsAt": "<ISO>" } }}Four things follow from reading it, none of which are in the issue:
percentis already a floored, clamped0..100integer —usagePercent: Math.floor(Math.min(100, (usage / limit) * 100))inconsole/core/src/subscription.ts. Sousedis the percent andtotalis100; my clamp is belt-and-braces, not a scale conversion.resetsAtis an ISO string,new Date(Date.now() + resetInSec * 1000).toISOString()— handled by the sharedparseResetTime.403 EntitlementError "OpenCode Go subscription required."when the key authenticates but the workspace has no Go plan. Folding 403 into the 401 branch — as the proposed patch does — tells a user with a perfectly good key to go reissue it. They are split here.0%bar that reads as "quota untouched".That last point also fixes labels. I originally wrote
Weekly (7d)/Monthly (30d), copying the issue. Both are wrong:analyzeWeeklyUsageresets on a calendar week boundary (getWeekBounds) andanalyzeMonthlyUsageon the subscription anniversary (getMonthlyBounds(now, timeSubscribed)), not rolling spans. The rolling window's length is server-side plan config and never appears in the payload, so(5h)was a guess too. Labels are now plainRolling/Weekly/Monthly— each row already renders its own countdown fromresetAt, which is the honest version of the same information.Error bodies are
{type:"error", error:{type, message}}, so the message is unwrapped rather than echoed as a raw JSON envelope.The four claims in the issue
1. Registry
featuresblock — confirmed, and it is the actual blocker.USAGE_SUPPORTED_PROVIDERS/USAGE_APIKEY_PROVIDERSare derived insrc/shared/constants/providers.jsby filtering the registry onfeatures.usage/features.usageApikey. Without them/api/usage/[connectionId]returns"Usage not available for this connection"before any fetch happens, andProviderLimitsnever calls for the connection.2. No fetcher in
misc.js— confirmed.3. No
USAGE_HANDLERSentry — confirmed.getUsageForProviderwould return"Usage API not implemented for opencode-go".4. "The default case does not pass
remainingPercentage, causing the progress bars to fail to render" — this one is not right. Both render paths fall back to the same arithmetic:QuotaTable→getRemainingPercentage(quota)→calculatePercentage(used, total)whenremainingPercentageis absent.ProviderLimitCardrecomputesMath.round(((total - used) / total) * 100)and does not readremainingPercentageat all.For a
used = percent, total = 100row both yield100 - used— exactly whatremainingPercentagecarries, including at theused = 0andused = 100edges. I proved it rather than reasoning about it: deleting thecase "opencode-go":line makes the field-forwarding test fail while "renders the remaining percentage the provider reported" still passes. The bars would have rendered correctly without any frontend change.I kept the frontend hunk anyway — it is one line, it puts the provider with
deepseek/ollama/kimiwhere it belongs, and it keeps the row correct if the shape ever stops being a straight percentage — but it is grouped for intent, not a fix, and the comment and commit message now say so. Note also that the diff in the issue is written against the built bundle (([e,t])=>{…}), which is why it namesProviderLimits/index.js; the realswitch (provider)is inProviderLimits/utils.js.One more deviation
The proposal hardcodes the URL inside
misc.js. This repo keeps usage endpoints intransport.usage.urland reads them throughU(id)inservices/usage/shared.js— that is howglm,glm-cnandvercel-ai-gatewaywork — so it is declared next to the other transports, with a guard for the case where it is absent.What I could not verify
I have no OpenCode Go subscription, so I have never seen a live 200 body. The shape above is read off the upstream source and cross-checked against four independent third-party clients that consume the same endpoint, but it is not a captured response. Every failure path therefore degrades to a
{message}instead of throwing or rendering a wrong bar: unknown shape → "No quota windows reported", non-JSON → "response was not JSON", transport error → the error text. Usage fetchers are display-only and off the routing path, so the worst case of a shape drift is a message where bars would be.If a maintainer or the reporter can paste one redacted 200 body, I will tighten the parser to it.
Verification
15 tests in
unit/opencode-go-usage-3334.test.js, following the existingdeepseek-usage.test.jsstructure: the registry flags land in both derived lists, the endpoint comes from the registry, the request is a GET withAuthorization: Bearer, the three windows map with the remainder derived, labels assert no span, an unreadable window is skipped, an out-of-range percent clamps, an empty payload returns a message, a missing key never calls out, 401 reads as an invalid key, 403 reads as a missing subscription and explicitly not as an invalid key, an upstream error message is unwrapped from its envelope, and a non-JSON body plus a transport failure both return messages. TwoparseQuotaDatatests close it: one that the field is forwarded, one that the rendered percentage is right.Mutation-checked, since a passing test proves nothing about whether it bites:
featuresfrom the registryexpected [...] to include 'opencode-go'case "opencode-go":Full suite (
npx vitest run unit translator): 1808 passed / 93 failed, failing set byte-identical to master's,commempty in both directions.npx eslintreports no issues on the three changed files.