The page showed a monthly BALANCE ($0.08 left) but no monthly ALLOWANCE, so
"how much do I get and how much of it is gone" had no answer — the most useful
number on a plan that is nearly exhausted.
The vendor's credits endpoint reports what is left and never the cap, so a
percentage is not derivable from it. Two things supply the missing half:
- planId from subscriptions (here individual-goat), mapped to the caps the
vendor publishes ($10 Go, $70 GOAT/Pro/Max).
- totalMonthlyCredits from usage/summary, which counts over the BILLING PERIOD
and so pairs with the monthly allowance rather than with the rolling 5h and
weekly windows.
Because the cap is a table lookup and not an API fact, it is cross-checked
against monthly_used + balance: if the two disagree by more than 2% + $0.50,
the card says the figure may be off rather than confidently printing a wrong
percentage. A planId absent from the table renders the balance with no bar at
all — inventing a denominator would be worse than admitting there isn't one.
Also surfaces what the endpoints were already returning and the page was
dropping: period_end (with a countdown), cancel_at_period_end, plan name, and
the vendor's own belowThreshold flag, which is distinct from "balance > 0" —
an account can hold $3 and still be under their threshold.
Verified on production: both accounts show 每月 100% ($69.94/$70, red bar),
5 小时 0/14, 每周 0.803/35 on one; plan goat, 周期至 2026-10-17 / 2026-10-19.
Co-Authored-By: ModelRouter <noreply@modelrouter.dev>
The plugin page rendered nothing until the operator clicked refresh, and even
the click handler was never wired — while every unit test was green and the
API returned correct numbers. Four separate mistakes stacked here:
1. The script waited on DOMContentLoaded, an event that had already been
dispatched by the time the kernel injects a plugin mount. Replaced with
pluginAPI.onTabShown (the kernel's own mechanism, fires with the DOM in
place) PLUS an immediate call, because onTabShown alone leaves the page
blank when the tab is already open at boot. The billing page does both; I had
reduced it to one and broke first paint.
2. The re-entry guard flag lived in a closure variable. The tab's DOM is rebuilt
on re-entry, so the handler was wired against a node that no longer existed.
The flag now lives on the NODE (__ccqWired), which is what makes re-wiring
correct.
3. refresh() swallowed its fetch failure and rendered "no sources", so a broken
page and an empty account list looked identical. Failures now surface in the
header line, including when pluginAPI.call is missing (an old kernel).
4. THE ACTUAL REASON the page stayed blank: there were TWO ccquota plugins. The
embed (go:embed plugins/*.lua) version and the on-disk one both loaded, and
the UI injected the EMBED copy — 5182 characters, predating every fix — while
every edit I made and every hot reload went to the disk copy. The running
binary was serving stale plugin code no amount of reloading could fix. Only a
rebuild surfaced it, and only measuring the served <script> length (7053 vs
5182) showed which copy the browser had.
Lesson worth keeping: "the file on disk is what runs" is false for anything
under go:embed. Compare what the SERVER sends before editing the same file
twice and concluding the edit did nothing.
Verified in the browser: 2 account cards, 4 window bars, real balances, no
overflow, no console errors, refresh button 48x29 and functional.
Co-Authored-By: ModelRouter <noreply@modelrouter.dev>
A quota plugin cannot work on billing's model. Billing reports what the gateway
already measured, delivered on hooks during request forwarding. A plan balance
has no such arrival path: it exists only on the vendor's side, and the only way
to learn it is to ask — at the moment the operator clicks Refresh. No hook fires
on a click, and pluginAPI offered only state read/write.
So this adds the missing piece: POST /api/plugins/{name}/call/{fn}, plus
pluginAPI.call on the page side.
## Why the gate is an explicit export list
That endpoint is an HTTP surface into a Lua state. Without a declared allowlist
it would let any caller invoke the plugin's private helpers with crafted
arguments and read whatever it can see — for a plugin holding an API key, that
is handing the key to anyone who can POST. So a function is callable only if the
plugin listed it in plugin.exports. A plugin with no exports has no callable
surface; reading the missing list as "allow all" is exactly the failure mode
worth designing against. The endpoint is also admin-only.
## ccquota
One account per configured source on api.commandcode.ai, keyed by that source's
own key — two keys on one host are two seats, and both are shown. Reads
/alpha/whoami, /alpha/billing/credits, /alpha/billing/subscriptions and
/alpha/usage/summary. Each endpoint degrades independently: a vendor retiring
one route must not blank the others, and a key out of quota still reads credits
fine while inference 401s, so the report records WHICH part failed rather than
rendering a zero that reads as "no quota left".
The 401-is-everything trap: those endpoints need a bearer token plus three
headers. The token lives in the table now, per request. Passing the shared
header table straight through — my first version — yields 401 on every
endpoint, which is indistinguishable from a revoked key and sent me auditing the
account instead of the plugin.
## Also fixed: three real defects found by going live
- The bridge was registered as `httpGlobal` (the Go const's IDENTIFIER) instead
of `__llmsproxy_http` (its VALUE). Green unit tests, "attempt to call global
'__llmsproxy_http' (a nil value)" on first real request. Now a test boots a
real plugin and calls it the way a plugin does.
- The call router kept `call/` in the function name, so every call 502'd with
`does not export "call/collect_account"` while the exports list looked fine.
- readPluginExports used luaToJSON, which needs the `json` global — not yet
installed while a plugin is registering — so it silently returned an empty
list and every export looked undeclared. It uses luaTableToGoValue now.
Two tests hardcoded counts ("1 bundled plugin", "4 pages") and turned red the
moment a second bundled plugin existed. They now assert what they mean: billing
is among the bundled plugins, and the pages this test contributed all merged in
order. A count assertion is a changelog detector.
## Verified on production
commandcode 2198972886@qq.com active $0.0811 5h 0/14 wk 0.803/35 44,921 req
commmandcode2 jianf3112@gmail.com active $0.0700 5h 0/14 wk 0/35 43,827 req
Co-Authored-By: ModelRouter <noreply@modelrouter.dev>