feat(quota): add ZCode (z.ai coding plan) live quota provider - #1347
Conversation
The Plans sidebar gains a ZCode panel fed by the same usage endpoint the ZCode app's embedded coding-plan browser calls — the sibling of the zai provider (Pi CLI login), serving the ZCode desktop app's own login: - GET https://api.z.ai/api/monitor/usage/quota/limit with a bearer token - credential: `oauth:zai:access_token` scanned read-only out of the coding-plan webview's Chromium Local Storage journal (*.log), newest journal first, last write of the key wins; ZCODE_DATA_DIR overrides the app-data root - the stored JWT has no exp claim, so a body-level code 401/403 on an HTTP 200 is the expiry signal -> terminalFailure with reopen guidance (only the ZCode app can mint a new login) - windows/labels/percent semantics shared with zai.ts (unit 3x5 -> 5-hour, unit 6x1 -> Weekly; percentage as used, fallback currentValue/usage) Surfaces: desktop Plans page (app/electron/quota + renderer registries) and the CLI quota command (src/quota), per the two-copies convention. Journal compaction into .ldb is a documented blind spot that degrades to disconnected. Verified live against a real ZCode login (plan "Pro", 5-hour + Weekly windows). Menubar Swift mirror left for a follow-up.
0041a6a to
7313925
Compare
|
Also wired ZCode into the native macOS menubar Capacity Dock (6f2eb10), mirroring the zai adapter: Verified: 51/51 across the affected Swift suites, 29/29 on the TS side, live fetch against a real login returns connected/Pro with both windows, and the dock rail renders Codex/Claude/ZCode with the Z glance on a local build. |
6f2eb10 to
54c040f
Compare
Mirrors the zai adapter: a ZcodeSubscriptionService recovers the z.ai OAuth token from the ZCode app's coding-plan Local Storage journal (same scan as the CLI and Electron providers, ZCODE_DATA_DIR override honored) and calls the usage endpoint with it as a bearer token. Registers the catalog entry (live, local-app auth), the quota-service dispatch, a bundled provider-zcode.svg mark, and the pinned-inventory test updates.
54c040f to
2d66f13
Compare
iamtoruk
left a comment
There was a problem hiding this comment.
Not merging yet. Tests pass, rebase is clean, back-off matches zai. Three things first.
- Overlap. This hits the same endpoint as
src/quota/zai.tswith the same decoder, just a different credential. If I am signed into z.ai in the Pi CLI and the ZCode app, do I get two rings on the dock showing the same numbers? Answer that directly. If yes, say what the dedupe is. - Proof. Real screenshots of the dock and the popover with a live ZCode account, dark and light.
- The fixture.
journalEntrywrites marker0x01then latin-1 bytes, but the adapter comment says0x01is UTF-16. So the journal tests do not match real Chromium. Post the recorded response you built the fixtures from, redacted, and a real journal hexdump.
Also: decodeZcodeUsage is a verbatim copy of decodeZaiUsage, three times over counting Swift. Factor it. And inventoryRevision is still the old sha with the count at 70.
…aithful journal fixtures Four items from the getagentseal#1347 review: 1. Overlap answered and handled: ZCode now yields to Z.ai wherever the two could show the same numbers. The CLI's collectQuota drops a connected ZCode row while Z.ai is connected (with a note naming why) and never lets a rejected/stale Z.ai state hide a working ZCode row; the dock's auto-seed skips ZCode while Z.ai is connected, and the popover quota candidates do the same. A manual ZCode pin always wins. The desktop Electron app runs only the ZCode adapter, so it has no overlap to dedupe. 2. decodeZcodeUsage/decodeZaiUsage are no longer verbatim twins: the body decode lives in src/quota/zai-plan.ts and, in Swift, in ZaiPlanQuotaDecoder; both adapters map the shared outcome onto their own error vocabulary. TS wrappers keep the existing exported names. 3. Journal fixtures and comments now match a recorded journal: the map key is origin + NUL + 0x01 one-byte-string flag + key name; the value frame is a varint length (the recorded 1,403-byte login encodes as fb 0a), the same 0x01 flag, then Latin-1 bytes. The earlier comments had the marker semantics backwards. Redacted hexdump and recorded response are posted on the review thread. 4. inventoryRevision bumped; the catalog pin test follows.
|
All four points addressed in 5caa065. 1. Overlap — answered directly: yes, you would have gotten two rings. Both adapters hit
Tests pin all three surfaces ( 2. Screenshots — live ZCode account (Pro), dock card and popover, light and dark, follow this comment. 3. The fixture vs. the recording — you were right that they disagreed, and the recording settles which way. Redacted hexdump of the real So the recorded structure is: map key = origin, The recorded 200 response the decode fixtures mirror (redacted — it carries no account identity at all, only limits and the plan level): {
"code": 200,
"msg": "Operation successful",
"data": {
"limits": [
{
"type": "CREDIT_LIMIT",
"unit": 3,
"number": 5,
"usage": 12000,
"currentValue": 657,
"remaining": 11342,
"percentage": 5,
"nextResetTime": 1789523594808
},
{
"type": "CREDIT_LIMIT",
"unit": 6,
"number": 1,
"usage": 60000,
"currentValue": 27288,
"remaining": 32711,
"percentage": 45,
"nextResetTime": 1789899227973
}
],
"level": "pro"
},
"success": true
}4. Factored. The body decode now exists once per language: |
…t enum The 5-hour and weekly windows are fixed cycles — the unit enum says so — so the adapter vouches for their lengths (windowSeconds from the same metadata the labels come from, never inferred from the label text). This is what the early-reset monitor's windowSeconds contract needs (getagentseal#1339): an adapter that cannot vouch for fixed cycling passes no length and its windows get no opinion. zcode accounts do early-reset for real — one did today on the machine this was tested on.
|
The CI failure on e956fe5 was the zai twin of the legacy-window expectation the durations commit updated — debfd20 brings it in line. Also on that branch: the follow-up promised in the #1339 thread, deriving zcode's window lengths from the payload's unit/count enum so the early-reset monitor's windowSeconds contract covers it the moment both PRs land. |
|
Gentle nudge: all four review points were addressed in 5caa065 back on Sep 15 (dedupe shipped and tested on all three surfaces, screenshots, the redacted journal hexdump, and the CI fix landed in debfd20), and the branch has been rebased clean since. Happy to rework anything that doesn't fit — just pointing at it in case it fell off the queue. |
iamtoruk
left a comment
There was a problem hiding this comment.
Thanks for this. The ZCode provider itself is in good shape: the shared quota decoder (src/quota/zai-plan.ts and ZaiPlanQuotaDecoder.swift) is reused by both Z.ai and ZCode, the reader uses the bounded quotaRequestSignal with graceful disconnected/waiting/error states and never opens a socket when there is no login, numbers stay put (report/models never load the quota path), and the logo is wired on the CLI label, the Plans provider card (embossed via ProviderLogo, zcode.jpg), and the menubar tile (provider-zcode.svg). Root tests, swift test (728), test:locks (36) and tsc all pass.
One blocker after merging current main. Main added app/renderer/lib/menubarProviders.ts, whose MENUBAR_QUOTA_PROVIDERS is the desktop "supported providers" list rendered in MenuBarAbout.tsx and guarded by menubarProviders.test.ts, which re-reads ProviderConnectionCatalog.swift and fails the moment the two disagree. This PR marks zcode live: true in that catalog but does not add ZCode to MENUBAR_QUOTA_PROVIDERS, so:
- the desktop supported-providers list omits ZCode, and
- menubarProviders.test.ts fails (app vitest).
The PR CI is green only because it ran against the pre-guard base; merged into current main it goes red.
Fix is one line: add 'ZCode' to MENUBAR_QUOTA_PROVIDERS right after 'Z.ai' in app/renderer/lib/menubarProviders.ts (matching the catalog's live order), then merge latest main so CI runs against the guard. Once that lands this is good to go.
iamtoruk
left a comment
There was a problem hiding this comment.
Blocker resolved: pushed the one-line fix (85d3342) adding ZCode to MENUBAR_QUOTA_PROVIDERS so the desktop supported-provider list includes it and menubarProviders.test.ts passes. Full CI is green on the new head; root suite, app suite (only pre-existing env failures), swift test (728) and test:locks (36) all pass. Logo present and rendering on every surface, numbers untouched. Good to merge.




What
Adds live plan quota for ZCode (the z.ai coding plan) to the Plans sidebar — the panel the usage provider from #537 never had. Desktop Plans page + Settings→Plans, plus CLI parity in
codeburn quota.How
GET https://api.z.ai/api/monitor/usage/quota/limit(sibling of thezaiprovider, which serves the Pi CLI login; this one serves the ZCode desktop app's own login).oauth:zai:access_tokenJWT read-only out of the coding-plan webview's Chromium Local Storage journal (…/ZCode/session/Partitions/zcode-coding-plan/Local Storage/leveldb/*.log); newest journal first, last write of the key wins,ZCODE_DATA_DIRoverrides the app-data root. Chromium owns that file's mode bits, so it's a plain capped read rather thanreadSecureFile.code: 401/403on an HTTP 200, which maps toterminalFailurewith "open the ZCode app and sign in again" guidance (only the app can mint a new login).zai.ts(unit 3 × number 5→ 5-hour,unit 6 × number 1→ Weekly;percentageas used, fallbackcurrentValue / usage;data.level→ plan label).Surfaces
app/electron/quota/zcode.ts(origin) + registries:types.ts,index.ts(PROVIDERSafter kimi), rendererproviders.ts/types.ts/ConnectAffordancenote /ProviderLogo(asset already exists).src/quota/zcode.ts(ported copy) +READERSinsrc/quota/index.ts, per the two-copies convention.docs/providers/zcode.md+ index row.Known limitation (documented)
When leveldb compacts the journal into
.ldb, records are snappy-compressed and invisible to the raw scan; the gauge degrades todisconnecteduntil the webview writes a fresh journal entry. Menubar Swift mirror left for a follow-up, matching how other quota providers landed.Testing
app/electron/quota/zcode.test.ts(journal scan incl. UTF-16LE + rotation + newest-journal, decode suite, terminal/transient classification, business-401, 429 Retry-After, redaction) andtests/quota-zcode.test.ts(CLI).index.test.tsprovider order; all quota suites green (24 + 11 + 54 checks across runs), app typecheck clean, full app + root suites pass except pre-existing flakes unrelated to quota (verified: the failing sets differ run-to-run on this branch and on pristineupstream/main).connected, planPro, 5-hour + Weekly windows with reset timestamps.