feat: add live gRPC-web quota fetcher for grok-cli (#6844)#7714
feat: add live gRPC-web quota fetcher for grok-cli (#6844)#7714diegosouzapw wants to merge 4 commits into
Conversation
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
Live validation against a real Grok Build account — the fetcher never returns quotaI ran this branch against the real Three defects, though, and the first one means the feature is currently a silent no-op. 1. The request has no body → upstream rejects it
Since the body comes back empty, Minimum fix: 2. The field mapping doesn't match the real responseWith a valid request frame, So the percentage is a 3. The trailer frame isn't handledThe response carries a second frame with flag Full hex of the real response, usable directly as a fixture: Why CI stayed greenThe 16 tests pass because they build their buffers with the test's own mirrored encoder — the request side and the real schema are never exercised. Replacing the synthetic fixtures with the bytes above would have caught all three. Verdict moved from |
…fig schema Live validation against grok.com (real bearer token, tier-4 account) proved the #6844 grok-cli quota fetcher was a silent no-op: - The POST to GetGrokCreditsConfig had no body. gRPC-web requires a request frame even for a no-argument RPC; without one the upstream returns `grpc-status: 13 "Missing request message."` with a 0-byte response. Fixed by sending the empty gRPC-web frame (flag 0x00 + 4-byte length 0). - The decoder's field mapping (top-level field 1 = double percent, field 2 = string resetAt) was reverse-engineered from a third-party doc and never matched the real response. The real shape is: top-level field 1 is a NESTED message whose subfield 1 is a fixed32 float usage ratio (0..1) and subfield 5 is a Timestamp{seconds,nanos} reset time. grokCliQuotaFrame.ts now decodes that nested shape; grokCliQuotaFetcher.ts's buildQuota() rescales the decoder's 0-100 percentUsed back to the 0-1 fraction the rest of the quota pipeline expects (quotaPreflight.ts::remainingPercentFrom). - The response's 2nd gRPC-web frame (trailer, flag 0x80) is now explicitly walked-and-skipped instead of relying on incidental length-bounding. Test fixtures in both files now encode the real captured wire structure (nested message, fixed32 ratio, Timestamp reset, trailer frame) instead of the old synthetic fixed64-double buffers, and the stale "Cloudflare non-blocking is an assumption" comment is corrected to reflect that it is now live-validated.
Summary
Adds a live quota fetcher for the
grok-cli(Grok Build) provider, replacing sole reliance on the static 864 req/day / 18M tokens/day plan (src/lib/quota/planRegistry.ts) with a poll of xAI's actual shared weekly credit pool.Scoped to the primary source only per the implementation plan's "2-3 PRs" split — the unauthenticated gRPC-web POST to
grok.com/grok_api_v2.GrokBuildBilling/GetGrokCreditsConfigusing the connection's existing bearer token. The optional local-CLI ACP secondary path, dashboard UI card, and reset-aware routing integration are explicitly deferred to follow-up PRs (see Non-Goals in the plan).open-sse/services/grokCliQuotaFrame.ts(new): defensive, dependency-free varint/length-delimited protobuf decoder for the gRPC-web response — handles both framed (5-byte header) and raw response shapes, treats an omittedcredit_usage_percentfield as 0% used (proto3 default), and never throws (malformed/truncated buffers returnnull).open-sse/services/grokCliQuotaFetcher.ts(new):fetchGrokCliQuota()/invalidateGrokCliQuotaCache()/registerGrokCliQuotaFetcher(), mirroring thev0QuotaFetcher.ts/agentrouterQuotaFetcher.tspattern (60s in-memory TTL cache,throttleQuotaFetch(), fail-open on 401/5xx — no proactiverefreshCredentials()call in this PR).open-sse/services/quotaTrackersBatch.ts: wired the new fetcher into the existing batch-registration module — zero changes to the frozensrc/sse/handlers/chat.tschokepoint file.src/lib/quota/planRegistry.ts: comment-only edit noting the staticgrok-cliplan is now the explicit fallback used only when the live fetch returnsnull.How it was validated
TDD (Hard Rule #18): both new test files fail before this change (no
grokCliQuotaFetcher.ts/grokCliQuotaFrame.tsmodule exists to import) and pass after:Test coverage:
null, empty buffer →null, frame-header probe rejection cases.null, correct headers + URL, happy-path percent/limitReached mapping, 401/5xx fail-open (no throw), unparseable body →null, TTL cache hit, cache invalidation,registerGrokCliQuotaFetcher()→preflightQuota()integration.Gates run (all green):
node scripts/check/check-test-discovery.mjsnpm run typecheck:corenpm run typecheck:noimplicit:core(pre-existing unrelated errors incombo.ts/usageTracking.ts/cliRuntime.tsconfirmed untouched by this diff)npx eslint --suppressions-location config/quality/eslint-suppressions.json <changed files>npm run check:complexity-ratchets(2059/890, matches baseline)node scripts/check/check-file-size.mjs(no✗on touched files)npm run check:cyclesnpm run test:coveragewas intentionally not run in this session (shared/loaded box) — this PR is additive-only (2 new service files, 2 new test files, a 1-line import in the batch registration file, a comment-only edit toplanRegistry.ts); CI runs the coverage gate authoritatively.Closes #6844