Skip to content

feat(quota): add ZCode (z.ai coding plan) live quota provider - #1347

Merged
iamtoruk merged 10 commits into
getagentseal:mainfrom
ozymandiashh:feat/zcode-quota
Sep 19, 2026
Merged

iamtoruk merged 10 commits into
getagentseal:mainfrom
ozymandiashh:feat/zcode-quota

Conversation

@ozymandiashh

Copy link
Copy Markdown
Collaborator

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

  • Same usage endpoint the ZCode app's embedded coding-plan browser calls: GET https://api.z.ai/api/monitor/usage/quota/limit (sibling of the zai provider, which serves the Pi CLI login; this one serves the ZCode desktop app's own login).
  • Credential: the oauth:zai:access_token JWT 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_DIR overrides the app-data root. Chromium owns that file's mode bits, so it's a plain capped read rather than readSecureFile.
  • The stored JWT has no exp claim — expiry is only visible as a body-level code: 401/403 on an HTTP 200, which maps to terminalFailure with "open the ZCode app and sign in again" guidance (only the app can mint a new login).
  • Window/label/percent semantics shared with zai.ts (unit 3 × number 5 → 5-hour, unit 6 × number 1 → Weekly; percentage as used, fallback currentValue / usage; data.level → plan label).

Surfaces

  • app/electron/quota/zcode.ts (origin) + registries: types.ts, index.ts (PROVIDERS after kimi), renderer providers.ts / types.ts / ConnectAffordance note / ProviderLogo (asset already exists).
  • src/quota/zcode.ts (ported copy) + READERS in src/quota/index.ts, per the two-copies convention.
  • Docs: quota section in 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 to disconnected until the webview writes a fresh journal entry. Menubar Swift mirror left for a follow-up, matching how other quota providers landed.

Testing

  • New 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) and tests/quota-zcode.test.ts (CLI).
  • Extended index.test.ts provider 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 pristine upstream/main).
  • Verified live against a real ZCode login on macOS: connected, plan Pro, 5-hour + Weekly windows with reset timestamps.

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.
@ozymandiashh

Copy link
Copy Markdown
Collaborator Author

Also wired ZCode into the native macOS menubar Capacity Dock (6f2eb10), mirroring the zai adapter: ZcodeSubscriptionService recovers the z.ai OAuth token from the ZCode app's coding-plan Local Storage journal (same scan as the CLI/Electron providers, ZCODE_DATA_DIR override honored) and calls the usage endpoint as a bearer token. Registers the catalog entry (live, local-app auth), the quota-service dispatch, a bundled provider-zcode.svg, pinned-inventory test updates, and new ZcodeQuotaTests.

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.

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.

@iamtoruk iamtoruk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not merging yet. Tests pass, rebase is clean, back-off matches zai. Three things first.

  1. Overlap. This hits the same endpoint as src/quota/zai.ts with 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.
  2. Proof. Real screenshots of the dock and the popover with a live ZCode account, dark and light.
  3. The fixture. journalEntry writes marker 0x01 then latin-1 bytes, but the adapter comment says 0x01 is 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.
@ozymandiashh

Copy link
Copy Markdown
Collaborator Author

All four points addressed in 5caa065.

1. Overlap — answered directly: yes, you would have gotten two rings. Both adapters hit https://api.z.ai/api/monitor/usage/quota/limit and the response carries no account identity (data is only limits + level), so same-account duplication can't be detected — two credentials for one z.ai account would render the same numbers twice. The dedupe now shipped: ZCode yields to Z.ai wherever both could show the same numbers.

  • CLI (collectQuota): a connected ZCode row is dropped while Z.ai is connected, with a note on the Z.ai row naming why; a rejected/stale Z.ai state never hides a working ZCode row.
  • Dock: autoSeedFromConnected skips ZCode while Z.ai is connected, and the popover quota candidates do the same. A ZCode pin in Settings always wins.
  • The Electron desktop app runs only the ZCode adapter (no zai reader), so it has no overlap to dedupe.

Tests pin all three surfaces (quota-providers.test.ts, CapacityDockPreferencesTests).

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 000003.log (5578 bytes; token value bytes masked in both columns — the JWT is ~1,403 bytes and starts after the fb 0a varint):

# Real Chromium Local Storage journal (000003.log, 5578 bytes) from the ZCode app
# coding-plan partition on macOS. Token value bytes are masked (·) in BOTH columns —
# only structure is shown: write-batch framing, varint lengths, the key name,
# and the single-byte value marker. Two occurrences of the key exist; the LAST
# one is the one the adapter's newest-write-wins rule reads.

## occurrence 1 of 2, key at 0x17e:

000167  01 2b 5f 68 74 74 70 73 3a 2f 2f 7a 63 6f 64 65  |·+_https://zcode|
000177  2e 7a 2e 61 69 00 01 6f 61 75 74 68 3a 7a 61 69  |.z.ai··oauth:zai|
000187  3a 61 63 63 65 73 73 5f 74 6f 6b 65 6e fb 0a 01  |:access_token···|
000197  ·· ·· ·· ·· ·· ·· ·· ·· ·· ·· ·· ·· ·· ·· ·· ··  |················|
0001a7  ·· ·· ·· ·· ·· ·· ·· ·· ·· ·· ·· ·· ·· ·· ·· ··  |················|
0001b7  ·· ·· ·· ·· ·· ·· ·· ·· ·· ·· ·· ·· ·· ·· ·· ··  |················|

## occurrence 2 of 2, key at 0xb96:

000b7f  01 2b 5f 68 74 74 70 73 3a 2f 2f 7a 63 6f 64 65  |·+_https://zcode|
000b8f  2e 7a 2e 61 69 00 01 6f 61 75 74 68 3a 7a 61 69  |.z.ai··oauth:zai|
000b9f  3a 61 63 63 65 73 73 5f 74 6f 6b 65 6e fb 0a 01  |:access_token···|
000baf  ·· ·· ·· ·· ·· ·· ·· ·· ·· ·· ·· ·· ·· ·· ·· ··  |················|
000bbf  ·· ·· ·· ·· ·· ·· ·· ·· ·· ·· ·· ·· ·· ·· ·· ··  |················|
000bcf  ·· ·· ·· ·· ·· ·· ·· ·· ·· ·· ·· ·· ·· ·· ·· ··  |················|

So the recorded structure is: map key = origin, 00, the 0x01 one-byte-string flag, key name; value frame = varint length (fb 0a for the 1,403-byte login), the same 0x01 flag, then single-byte (Latin-1) token characters. My earlier comment had the marker semantics backwards (it called 0x01 the UTF-16 marker); the fixture's gap bytes were also unlike the recording. Both fixed: the comment now states the evidenced flag semantics, and journalEntry builds the recorded frame (origin + 00 01 + key + fb 0a + 01 + value). The scanner's assumption that the gap contains no token characters now holds for the recorded login and is written down in the fixture comment.

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: src/quota/zai-plan.ts (decodeZaiPlanUsage(provider:body:)) serves both TS adapters behind their existing exported names, and ZaiPlanQuotaDecoder serves both Swift services, each keeping its own error vocabulary. inventoryRevision is bumped and the catalog pin test follows.

@ozymandiashh

ozymandiashh commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator Author

Live ZCode account (Pro) — dock and popover, light and dark, as requested:

1. Capacity Dock card, light — the ZCode ring in the rail beside Codex and Claude; card shows Pro, 5-hour 5% (resets 3h 34m), Weekly 45%.

2. Popover, light — the ZCode tab beside All and Claude.

3. Popover, dark — the same tab in dark appearance.

4. Capacity Dock, dark — the rail expanded to all three rings (ZCode, Codex, Claude); card shows Pro, 5-hour 10%, Weekly 46%.

1-dock-card-zcode-light 2-popover-light-zcode-tab 3-popover-dark-zcode-tab Captură de ecran din 2026-09-16 la 01 43 11

…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.
@ozymandiashh

Copy link
Copy Markdown
Collaborator Author

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.

@ozymandiashh

Copy link
Copy Markdown
Collaborator Author

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 iamtoruk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 iamtoruk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@iamtoruk
iamtoruk merged commit 3e87863 into getagentseal:main Sep 19, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants