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 2 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 |
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