feat(usage): show Grok Build billing limits (#9205)

* feat(usage): show Grok Build billing limits

* test(usage): keep Grok quota reset fixture in the future

* fix(i18n): add Grok billing labels to pt-BR

* fix(i18n): add Grok billing labels to Vietnamese
This commit is contained in:
Xiangzhe
2026-08-05 05:07:02 +08:00
committed by GitHub
parent 712910612b
commit 3440c118e0
19 changed files with 1485 additions and 74 deletions

View File

@@ -3307,11 +3307,6 @@
"count": 1
}
},
"src/lib/usage/providerLimits.ts": {
"no-restricted-imports": {
"count": 1
}
},
"src/lib/ws/handshake.ts": {
"no-restricted-imports": {
"count": 1

View File

@@ -66,6 +66,7 @@ import { getVertexUsage } from "./usage/vertex.ts";
import { getXiaomiMimoUsage } from "./usage/xiaomi-mimo.ts";
import { getXaiUsage } from "./usage/xai.ts";
import { getXaiOauthUsage } from "./usage/xaiOauth.ts";
import { getGrokCliUsage } from "./usage/grokCli.ts";
import { getFirecrawlUsage } from "./usage/firecrawl.ts";
type JsonRecord = Record<string, unknown>;
@@ -116,6 +117,7 @@ export const USAGE_FETCHER_PROVIDERS = [
"xai",
"xai-oauth",
"xao",
"grok-cli",
"vertex",
"vertex-partner",
"codebuddy-cn",
@@ -210,6 +212,8 @@ export async function getUsageForProvider(
case "xai-oauth":
case "xao":
return await getXaiOauthUsage(id || "", accessToken, connection);
case "grok-cli":
return await getGrokCliUsage(accessToken);
case "codebuddy-cn":
return await getCodeBuddyCnUsage(accessToken, apiKey, providerSpecificData);
case "promptql":

View File

@@ -0,0 +1,278 @@
import { z } from "zod";
import { GROK_BUILD_PROXY_BASE_URL, getGrokBuildModelsHeaders } from "../../config/grokBuild.ts";
import {
GROK_BUILD_ADDITIONAL_CREDITS_URL,
type GrokAutoTopUpStatus,
} from "../../../src/shared/utils/grokBilling.ts";
const GROK_BUILD_FETCH_TIMEOUT_MS = 10_000;
const GROK_BUILD_MAX_RESPONSE_BYTES = 256 * 1024;
const optionalNonEmptyString = z
.string()
.trim()
.min(1)
.max(256)
.optional()
.nullable()
.catch(undefined);
const optionalPercent = z.number().finite().min(0).max(100).optional().nullable().catch(undefined);
const centSchema = z
.object({ val: z.number().finite().int().safe().optional() })
.passthrough()
.transform(({ val }) => ({ val: Math.abs(val ?? 0) }));
const userSchema = z
.object({
userId: optionalNonEmptyString,
subscriptionTier: optionalNonEmptyString,
})
.passthrough();
const productUsageSchema = z
.object({
product: z.string().trim().min(1).max(128),
usagePercent: z.number().finite().min(0).max(100),
})
.passthrough();
const productUsageListSchema = z
.array(z.unknown())
.max(100)
.transform((items) =>
items.flatMap((item) => {
const parsed = productUsageSchema.safeParse(item);
return parsed.success ? [parsed.data] : [];
})
);
const currentPeriodSchema = z
.object({
type: optionalNonEmptyString,
start: optionalNonEmptyString,
end: optionalNonEmptyString,
})
.passthrough();
const billingConfigSchema = z
.object({
creditUsagePercent: optionalPercent,
currentPeriod: currentPeriodSchema.optional().nullable().catch(undefined),
productUsage: productUsageListSchema.optional().nullable().catch(undefined),
prepaidBalance: centSchema.optional().nullable().catch(undefined),
})
.passthrough();
const billingSchema = z
.object({
config: billingConfigSchema.optional().nullable().catch(undefined),
})
.passthrough();
const autoTopUpRuleSchema = z
.object({
enabled: z.boolean().optional(),
minBeforeHittingSl: centSchema.optional().nullable().catch(undefined),
topupAmount: centSchema.optional().nullable().catch(undefined),
maxAmountPerMonth: centSchema.optional().nullable().catch(undefined),
})
.passthrough();
const autoTopUpSchema = z
.object({
rule: autoTopUpRuleSchema.optional().nullable().catch(undefined),
})
.passthrough();
type JsonSchema<T> = z.ZodType<T>;
type GrokBuildHeaders = ReturnType<typeof getGrokBuildModelsHeaders>;
function finitePercent(value: number): number {
return Math.max(0, Math.min(100, value));
}
function normalizeProduct(value: string): { key: string; displayName: string } {
const compact = value
.normalize("NFKC")
.trim()
.toLowerCase()
.replace(/[^a-z0-9]+/g, "");
if (compact === "grokbuild" || compact === "productgrokbuild") {
return { key: "grok_build", displayName: "Grok Build" };
}
const slug = value
.normalize("NFKD")
.toLowerCase()
.replace(/[^a-z0-9]+/g, "_")
.replace(/^_+|_+$/g, "");
return { key: slug || "unknown", displayName: value };
}
function percentageQuota(used: number, resetAt: string | null, displayName?: string) {
const normalizedUsed = finitePercent(used);
const remaining = 100 - normalizedUsed;
return {
...(displayName ? { displayName } : {}),
used: normalizedUsed,
total: 100,
remaining,
remainingPercentage: remaining,
resetAt,
isPercentageOnly: true,
};
}
async function readBoundedJson<T>(response: Response, schema: JsonSchema<T>): Promise<T | null> {
if (!response.ok) return null;
const declaredLength = Number(response.headers.get("content-length"));
if (Number.isFinite(declaredLength) && declaredLength > GROK_BUILD_MAX_RESPONSE_BYTES)
return null;
const reader = response.body?.getReader();
if (!reader) return null;
const chunks: Uint8Array[] = [];
let size = 0;
while (true) {
const { done, value } = await reader.read();
if (done) break;
size += value.byteLength;
if (size > GROK_BUILD_MAX_RESPONSE_BYTES) {
await reader.cancel();
return null;
}
chunks.push(value);
}
try {
const bytes = new Uint8Array(size);
let offset = 0;
for (const chunk of chunks) {
bytes.set(chunk, offset);
offset += chunk.byteLength;
}
return schema.parse(JSON.parse(new TextDecoder().decode(bytes)));
} catch {
return null;
}
}
async function fetchGrokBuildJson<T>(
path: string,
headers: GrokBuildHeaders,
schema: JsonSchema<T>
): Promise<T | null> {
try {
const response = await fetch(`${GROK_BUILD_PROXY_BASE_URL}${path}`, {
method: "GET",
headers,
redirect: "error",
signal: AbortSignal.timeout(GROK_BUILD_FETCH_TIMEOUT_MS),
});
return await readBoundedJson(response, schema);
} catch {
return null;
}
}
function buildProductQuotas(
productUsage: z.infer<typeof productUsageSchema>[] | null | undefined,
resetAt: string | null
): Record<string, ReturnType<typeof percentageQuota>> {
const quotas: Record<string, ReturnType<typeof percentageQuota>> = {};
for (const product of productUsage ?? []) {
const normalized = normalizeProduct(product.product);
const baseKey = `product_${normalized.key}`;
let key = baseKey;
let suffix = 2;
while (key in quotas) {
key = `${baseKey}_${suffix++}`;
}
quotas[key] = percentageQuota(product.usagePercent, resetAt, normalized.displayName);
}
return quotas;
}
function buildAutoTopUp(ruleResponse: z.infer<typeof autoTopUpSchema> | null): GrokAutoTopUpStatus {
const rule = ruleResponse?.rule;
if (!rule) return { available: false };
const enabled = rule.enabled === true;
return {
available: true,
enabled,
...(enabled && rule.minBeforeHittingSl
? { thresholdMinorUnits: rule.minBeforeHittingSl.val }
: {}),
...(enabled && rule.topupAmount ? { amountMinorUnits: rule.topupAmount.val } : {}),
...(enabled && rule.maxAmountPerMonth
? { maxMonthlyMinorUnits: rule.maxAmountPerMonth.val }
: {}),
};
}
export async function getGrokCliUsage(accessToken?: string) {
if (!accessToken) {
return { message: "Grok Build usage unavailable" };
}
const baseHeaders = getGrokBuildModelsHeaders({ token: accessToken });
const user = await fetchGrokBuildJson("/user?include=subscription", baseHeaders, userSchema);
const userId = user?.userId || null;
const tier = user?.subscriptionTier || null;
const billing = await fetchGrokBuildJson(
"/billing?format=credits",
userId ? getGrokBuildModelsHeaders({ token: accessToken, userId }) : baseHeaders,
billingSchema
);
if (!billing?.config) {
return {
...(tier ? { plan: tier } : {}),
message: "Grok Build billing status unavailable",
};
}
const config = billing.config;
const resetAt = config.currentPeriod?.end || null;
const quotas: Record<string, ReturnType<typeof percentageQuota>> = {};
if (config.creditUsagePercent != null) {
quotas.weekly = percentageQuota(config.creditUsagePercent, resetAt);
}
Object.assign(quotas, buildProductQuotas(config.productUsage, resetAt));
const autoTopUpResponse = userId
? await fetchGrokBuildJson(
"/auto-topup-rule",
getGrokBuildModelsHeaders({ token: accessToken, userId }),
autoTopUpSchema
)
: null;
return {
quotas,
...(tier ? { plan: tier } : {}),
billing: {
currency: "USD",
...(config.prepaidBalance ? { extraCreditsMinorUnits: config.prepaidBalance.val } : {}),
autoTopUp: buildAutoTopUp(autoTopUpResponse),
additionalCreditsUrl: GROK_BUILD_ADDITIONAL_CREDITS_URL,
},
};
}
export const __testing = {
billingSchema,
userSchema,
autoTopUpSchema,
readBoundedJson,
networkPolicy: {
method: "GET",
redirect: "error",
timeoutMs: GROK_BUILD_FETCH_TIMEOUT_MS,
maxResponseBytes: GROK_BUILD_MAX_RESPONSE_BYTES,
} as const,
};

View File

@@ -2,6 +2,7 @@
import { useMemo, useState } from "react";
import Card from "@/shared/components/Card";
import type { GrokBillingStatus } from "@/shared/utils/grokBilling";
import { pickDisplayValue } from "@/shared/utils/maskEmail";
import {
normalizePlanTier,
@@ -34,6 +35,8 @@ interface QuotaCardProps {
quotas?: any[];
plan?: string | null;
message?: string | null;
billing?: GrokBillingStatus | null;
raw?: { billing?: GrokBillingStatus | null };
stale?: { since?: string; reason?: string } | null;
}
| undefined;
@@ -89,13 +92,22 @@ export default function QuotaCard({
const tierMeta = useMemo(
() =>
normalizePlanTier(
resolvePlanValue(quota?.plan ?? null, connection.providerSpecificData ?? null)
resolvePlanValue(
quota?.plan ?? null,
connection.providerSpecificData ?? null,
connection.provider
)
),
[quota?.plan, connection.providerSpecificData]
[quota?.plan, connection.providerSpecificData, connection.provider]
);
const resolvedPlan = useMemo(
() => resolvePlanValue(quota?.plan ?? null, connection.providerSpecificData ?? null),
[quota?.plan, connection.providerSpecificData]
() =>
resolvePlanValue(
quota?.plan ?? null,
connection.providerSpecificData ?? null,
connection.provider
),
[quota?.plan, connection.providerSpecificData, connection.provider]
);
const accountLabel = useMemo(
() =>
@@ -138,6 +150,9 @@ export default function QuotaCard({
loading={loading}
error={error}
message={quota?.message ?? null}
billing={
connection.provider === "grok-cli" ? (quota?.billing ?? quota?.raw?.billing) : null
}
refreshedAt={displayRefreshedAt}
hasStaleData={hasStaleData}
onRefresh={onRefresh}

View File

@@ -17,6 +17,7 @@ export const PROVIDER_LABEL: Record<string, string> = {
deepseek: "DeepSeek",
"xai-oauth": "xAI OAuth (Grok)",
xao: "xAI OAuth (Grok)",
"grok-cli": "Grok Build",
};
export const PROVIDER_ORDER: Record<string, number> = {
@@ -36,6 +37,7 @@ export const PROVIDER_ORDER: Record<string, number> = {
nanogpt: 15,
"xai-oauth": 16,
xao: 16,
"grok-cli": 17,
};
export const TIER_FILTERS = [

View File

@@ -8,7 +8,7 @@ import {
formatQuotaLabel,
formatCountdown,
normalizePlanTier,
resolvePlanValue,
buildProviderLimitsResolvedPlans,
calculatePercentage,
matchesProviderFilter,
buildProviderOptions,
@@ -535,13 +535,10 @@ export default function ProviderLimits({
}, [filteredConnections]);
const visibleQuotaData = useVisibleQuotaData(sortedConnections, quotaData);
const resolvedPlanByConnection = useMemo(() => {
const out: Record<string, string | null> = {};
for (const conn of sortedConnections) {
out[conn.id] = resolvePlanValue(quotaData[conn.id]?.plan, conn.providerSpecificData);
}
return out;
}, [sortedConnections, quotaData]);
const resolvedPlanByConnection = useMemo(
() => buildProviderLimitsResolvedPlans(sortedConnections, quotaData),
[sortedConnections, quotaData]
);
const tierByConnection = useMemo(() => {
const out: Record<string, ReturnType<typeof normalizePlanTier>> = {};

View File

@@ -1,7 +1,8 @@
"use client";
import { useMemo, useState } from "react";
import { useTranslations } from "next-intl";
import { useLocale, useTranslations } from "next-intl";
import { buildGrokBillingCardRows, type GrokBillingStatus } from "@/shared/utils/grokBilling";
import {
formatCountdown,
formatQuotaLabel,
@@ -26,6 +27,47 @@ const CURRENCY_SYMBOLS: Record<string, string> = {
const DEFAULT_VISIBLE_ROWS = 3;
function GrokBillingDetails({ billing }: { billing: GrokBillingStatus }) {
const t = useTranslations("usage");
const locale = useLocale();
const rows = buildGrokBillingCardRows(billing, locale, (key, fallback) =>
translateUsageOrFallback(t, key, fallback)
);
return (
<div className="flex flex-col gap-1.5 border-t border-border/40 pt-2 text-[11px] text-text-main">
{rows.map((row) =>
row.kind === "link" ? (
<a
key={row.kind}
href={row.href}
target={row.target}
rel={row.rel}
className="inline-flex w-fit items-center gap-1 font-medium text-primary hover:underline"
>
{row.label}
<span className="material-symbols-outlined text-[12px]">open_in_new</span>
</a>
) : (
<div
key={row.kind}
className={`flex justify-between gap-2 ${
row.kind === "status" ? "items-start" : "items-center"
}`}
>
<span>{row.label}</span>
<span
className={`font-semibold ${row.kind === "status" ? "text-right" : "tabular-nums"}`}
>
{row.value}
</span>
</div>
)
)}
</div>
);
}
/** Pure helper — sorts quotas by remaining percentage, highest first. */
export function sortQuotasByRemaining(quotas: any[]): any[] {
return [...quotas].sort(
@@ -73,6 +115,7 @@ interface Props {
loading: boolean;
error: string | null;
message?: string | null;
billing?: GrokBillingStatus | null;
refreshedAt?: string;
hasStaleData: boolean;
onRefresh: () => void;
@@ -240,6 +283,7 @@ export default function QuotaCardExpanded({
loading,
error,
message,
billing,
refreshedAt,
hasStaleData,
onRefresh,
@@ -313,6 +357,8 @@ export default function QuotaCardExpanded({
</div>
)}
{providerId === "grok-cli" && billing && <GrokBillingDetails billing={billing} />}
{hiddenQuotaRows.length > 0 && (
<div className="flex flex-wrap items-center gap-1 border-t border-border/40 pt-1.5 text-[10px] text-text-muted">
<span className="material-symbols-outlined text-[12px]">visibility_off</span>

View File

@@ -69,6 +69,10 @@ function normalizeQuotaEntry(name: string, quota: any = {}, extras: any = {}) {
? { extraCreditsInferred: Number(quota.extraCreditsInferred) || 0 }
: {}),
...(quota?.overPlan !== undefined ? { overPlan: quota.overPlan === true } : {}),
...(quota?.displayName !== undefined ? { displayName: String(quota.displayName) } : {}),
...(quota?.isPercentageOnly !== undefined
? { isPercentageOnly: quota.isPercentageOnly === true }
: {}),
...extras,
};
}

View File

@@ -180,9 +180,11 @@ export function calculatePercentage(used, total) {
* Resolve the best available plan label using live usage first, then persisted
* provider-specific connection metadata.
*/
export function resolvePlanValue(plan, providerSpecificData) {
const psd = toRecord(providerSpecificData);
export function resolvePlanValue(plan, providerSpecificData, providerId) {
const livePlan = normalizePlanCandidate(plan);
if (String(providerId || "").toLowerCase() === "grok-cli") return livePlan || null;
const psd = toRecord(providerSpecificData);
const persistedCandidates = [
psd.workspacePlanType,
psd.plan,
@@ -214,6 +216,29 @@ export function resolvePlanValue(plan, providerSpecificData) {
return livePlan || null;
}
/**
* Page-level Provider Limits plan map used by tier stats/filters.
* Always passes provider so grok-cli never classifies from persisted PSD tiers.
*/
export function buildProviderLimitsResolvedPlans(
connections: Array<{
id: string;
provider?: string | null;
providerSpecificData?: unknown;
}>,
quotaData: Record<string, { plan?: unknown } | null | undefined>
): Record<string, string | null> {
const out: Record<string, string | null> = {};
for (const conn of connections) {
out[conn.id] = resolvePlanValue(
quotaData[conn.id]?.plan,
conn.providerSpecificData,
conn.provider
);
}
return out;
}
function unknownPlanTier(raw: string | null = null) {
return { key: "unknown", label: "Unknown", variant: "default", rank: 0, raw };
}

View File

@@ -8465,6 +8465,16 @@
},
"usage": {
"title": "Usage",
"grokExtraUsageCredits": "Extra Usage Credits",
"grokAutoTopUp": "Auto Top-Up",
"grokAutoTopUpUnavailable": "Unavailable",
"grokAutoTopUpEnabled": "Enabled",
"grokAutoTopUpDisabled": "Disabled",
"grokAutoTopUpAt": "at",
"grokAutoTopUpAdd": "add",
"grokAutoTopUpMax": "max",
"grokAutoTopUpMonth": "month",
"grokAdditionalCredits": "Additional Credits",
"loggerTab": "Logger",
"proxyTab": "Proxy",
"budgetManagement": "Budget Management",

View File

@@ -8465,6 +8465,16 @@
},
"usage": {
"title": "Uso",
"grokExtraUsageCredits": "Créditos de uso extra",
"grokAutoTopUp": "Recarga automática",
"grokAutoTopUpUnavailable": "Indisponível",
"grokAutoTopUpEnabled": "Ativada",
"grokAutoTopUpDisabled": "Desativada",
"grokAutoTopUpAt": "em",
"grokAutoTopUpAdd": "adicionar",
"grokAutoTopUpMax": "máximo",
"grokAutoTopUpMonth": "mês",
"grokAdditionalCredits": "Créditos adicionais",
"loggerTab": "Logger",
"proxyTab": "Proxy",
"budgetManagement": "Gerenciamento de Orçamento",

View File

@@ -8465,6 +8465,16 @@
},
"usage": {
"title": "Mức sử dụng",
"grokExtraUsageCredits": "Tín dụng sử dụng bổ sung",
"grokAutoTopUp": "Tự động nạp thêm",
"grokAutoTopUpUnavailable": "Không khả dụng",
"grokAutoTopUpEnabled": "Đã bật",
"grokAutoTopUpDisabled": "Đã tắt",
"grokAutoTopUpAt": "tại",
"grokAutoTopUpAdd": "thêm",
"grokAutoTopUpMax": "tối đa",
"grokAutoTopUpMonth": "tháng",
"grokAdditionalCredits": "Tín dụng bổ sung",
"loggerTab": "Logger",
"proxyTab": "Proxy",
"budgetManagement": "Quản lý ngân sách",

View File

@@ -1,3 +1,4 @@
import { sanitizeGrokBillingStatus, type GrokBillingStatus } from "@/shared/utils/grokBilling";
import { getDbInstance, isBuildPhase, isCloud } from "./core";
type JsonRecord = Record<string, unknown>;
@@ -25,6 +26,7 @@ export interface ProviderLimitsCacheEntry {
fetchedAt: string;
source?: string | null;
bankedResetCredits?: number;
billing?: GrokBillingStatus;
}
const PROVIDER_LIMITS_CACHE_NAMESPACE = "providerLimitsCache";
@@ -41,6 +43,12 @@ function toRecord(value: unknown): JsonRecord | null {
return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : null;
}
function sanitizeCacheEntryForStorage(entry: ProviderLimitsCacheEntry): ProviderLimitsCacheEntry {
const { billing: rawBilling, ...rest } = entry;
const billing = sanitizeGrokBillingStatus(rawBilling);
return billing ? { ...rest, billing } : rest;
}
function normalizeCacheEntry(value: unknown): ProviderLimitsCacheEntry | null {
const record = toRecord(value);
if (!record) return null;
@@ -50,6 +58,7 @@ function normalizeCacheEntry(value: unknown): ProviderLimitsCacheEntry | null {
if (!fetchedAt) return null;
const bankedResetCredits = Number(record.bankedResetCredits);
const billing = sanitizeGrokBillingStatus(record.billing);
return {
quotas: toRecord(record.quotas),
@@ -58,6 +67,7 @@ function normalizeCacheEntry(value: unknown): ProviderLimitsCacheEntry | null {
fetchedAt,
source: typeof record.source === "string" ? record.source : null,
...(Number.isFinite(bankedResetCredits) ? { bankedResetCredits } : {}),
...(billing ? { billing } : {}),
};
}
@@ -92,14 +102,15 @@ export function setProviderLimitsCache(
connectionId: string,
entry: ProviderLimitsCacheEntry
): ProviderLimitsCacheEntry {
if (isBuildPhase || isCloud) return entry;
const sanitized = sanitizeCacheEntryForStorage(entry);
if (isBuildPhase || isCloud) return sanitized;
const db = getDbInstance() as unknown as DbLike;
db.prepare("INSERT OR REPLACE INTO key_value (namespace, key, value) VALUES (?, ?, ?)").run(
PROVIDER_LIMITS_CACHE_NAMESPACE,
connectionId,
JSON.stringify(entry)
JSON.stringify(sanitized)
);
return entry;
return sanitized;
}
export function setProviderLimitsCacheBatch(
@@ -113,7 +124,11 @@ export function setProviderLimitsCacheBatch(
const tx = db.transaction(
(items: Array<{ connectionId: string; entry: ProviderLimitsCacheEntry }>) => {
for (const item of items) {
insert.run(PROVIDER_LIMITS_CACHE_NAMESPACE, item.connectionId, JSON.stringify(item.entry));
insert.run(
PROVIDER_LIMITS_CACHE_NAMESPACE,
item.connectionId,
JSON.stringify(sanitizeCacheEntryForStorage(item.entry))
);
}
}
);

View File

@@ -1,22 +1,23 @@
import {
getAllProviderLimitsCache,
getProviderConnectionById,
getProviderConnections,
updateProviderConnection,
} from "@/lib/db/providers";
import { getSettings, resolveProxyForConnection, updateSettings } from "@/lib/db/settings";
import {
getAllProviderLimitsCache,
getProviderLimitsCache,
getSettings,
resolveProxyForConnection,
setProviderLimitsCache,
setProviderLimitsCacheBatch,
updateProviderConnection,
updateSettings,
type ProviderLimitsCacheEntry,
} from "@/lib/localDb";
} from "@/lib/db/providerLimits";
import { syncToCloud } from "@/lib/cloudSync";
import { setQuotaCache } from "@/domain/quotaCache";
import { buildClaudeExtraUsageConnectionUpdate } from "@/lib/providers/claudeExtraUsage";
import { clearRecoveredProviderState } from "@/sse/services/auth";
import { getMachineId } from "@/shared/utils/machine";
import { USAGE_SUPPORTED_PROVIDERS } from "@/shared/constants/providers";
import { mergeProviderLimitsCacheEntry, toProviderLimitsCacheEntry } from "./providerLimitsCache";
import { getExecutor } from "@omniroute/open-sse/executors/index.ts";
import { getUsageForProvider } from "@omniroute/open-sse/services/usage.ts";
import {
@@ -94,22 +95,6 @@ const PROVIDER_LIMITS_AUTO_SYNC_SETTING_KEY = "provider_limits_auto_sync_last_ru
const DEFAULT_PROVIDER_LIMITS_POST_USAGE_REFRESH_DELAY_MS = 5_000;
const pendingPostUsageRefreshes = new Set<string>();
function toProviderLimitsCacheEntry(
usage: JsonRecord,
source: SyncSource,
fetchedAt = new Date().toISOString()
): ProviderLimitsCacheEntry {
const value = Number(usage.bankedResetCredits);
return {
quotas: isRecord(usage.quotas) ? usage.quotas : null,
plan: usage.plan ?? null,
message: typeof usage.message === "string" ? usage.message : null,
fetchedAt,
source,
bankedResetCredits: Number.isFinite(value) ? value : undefined,
};
}
function getProviderLimitsPostUsageRefreshDelayMs(): number {
const raw = Number(process.env.PROVIDER_LIMITS_POST_USAGE_REFRESH_DELAY_MS ?? "");
return Number.isFinite(raw) && raw >= 0
@@ -890,30 +875,32 @@ export async function fetchAndPersistProviderLimits(
allowRotatingRefresh: opts.allowRotatingRefresh,
});
const newCache = toProviderLimitsCacheEntry(usage, source);
const previous = getProviderLimitsCache(connectionId);
const cache = mergeProviderLimitsCacheEntry(connection.provider, newCache, previous);
// Don't persist error-only entries (429 etc.) — would wipe prior good cache.
// Serve the prior entry instead; only successful fetches update the cache.
const fetchFailed = !newCache.quotas && newCache.message;
if (fetchFailed) {
const previous = getProviderLimitsCache(connectionId);
if (previous?.quotas && Object.keys(previous.quotas).length > 0) {
const staleUsage: JsonRecord = {
...usage,
quotas: previous.quotas,
plan: previous.plan ?? usage.plan ?? null,
bankedResetCredits: previous.bankedResetCredits,
message: null,
_stale: true,
_staleSince: previous.fetchedAt,
_staleReason: newCache.message,
};
return { connection, usage: staleUsage, cache: previous };
}
return { connection, usage, cache: newCache };
if (cache === previous && newCache.message) {
const staleUsage: JsonRecord = {
...usage,
quotas: previous.quotas,
plan: previous.plan ?? usage.plan ?? null,
bankedResetCredits: previous.bankedResetCredits,
billing: previous.billing,
message: null,
_stale: true,
_staleSince: previous.fetchedAt,
_staleReason: newCache.message,
};
return { connection, usage: staleUsage, cache: previous };
}
setProviderLimitsCache(connectionId, newCache);
return { connection, usage, cache: newCache };
const mergedUsage: JsonRecord = {
...usage,
...(cache.billing ? { billing: cache.billing } : {}),
};
setProviderLimitsCache(connectionId, cache);
return { connection, usage: mergedUsage, cache };
}
export async function syncAllProviderLimits(
@@ -942,14 +929,9 @@ export async function syncAllProviderLimits(
) => {
if (result.status === "fulfilled") {
const { cache } = result.value;
// Don't persist error-only entries; show prior cache or pass through.
if (!cache.quotas && cache.message) {
const previous = getProviderLimitsCache(connectionId);
if (previous?.quotas && Object.keys(previous.quotas).length > 0) {
caches[connectionId] = previous;
} else {
caches[connectionId] = cache;
}
const previous = getProviderLimitsCache(connectionId);
if (cache === previous) {
caches[connectionId] = cache;
return;
}
cacheEntries.push({ connectionId, entry: cache });
@@ -968,7 +950,8 @@ export async function syncAllProviderLimits(
const { usage } = await fetchLiveProviderLimitsWithOptions(connection.id, {
forceRefresh,
});
const cache = toProviderLimitsCacheEntry(usage, source);
const nextCache = toProviderLimitsCacheEntry(usage, source);
const cache = mergeProviderLimitsCacheEntry(connection.provider, nextCache, existingCache);
return { connectionId: connection.id, cache };
};

View File

@@ -0,0 +1,57 @@
import type { ProviderLimitsCacheEntry } from "@/lib/db/providerLimits";
import { sanitizeGrokBillingStatus } from "@/shared/utils/grokBilling";
const GROK_CLI_PROVIDER = "grok-cli";
type JsonRecord = Record<string, unknown>;
function isRecord(value: unknown): value is JsonRecord {
return Boolean(value && typeof value === "object" && !Array.isArray(value));
}
function hasUsableCachedData(cache: ProviderLimitsCacheEntry | null | undefined): boolean {
return Boolean(cache?.billing || (cache?.quotas && Object.keys(cache.quotas).length > 0));
}
export function toProviderLimitsCacheEntry(
usage: JsonRecord,
source: string,
fetchedAt = new Date().toISOString()
): ProviderLimitsCacheEntry {
const bankedResetCredits = Number(usage.bankedResetCredits);
return {
quotas: isRecord(usage.quotas) ? usage.quotas : null,
plan: usage.plan ?? null,
message: typeof usage.message === "string" ? usage.message : null,
fetchedAt,
source,
bankedResetCredits: Number.isFinite(bankedResetCredits) ? bankedResetCredits : undefined,
billing: sanitizeGrokBillingStatus(usage.billing),
};
}
export function mergeProviderLimitsCacheEntry(
provider: string,
next: ProviderLimitsCacheEntry,
previous: ProviderLimitsCacheEntry | null | undefined
): ProviderLimitsCacheEntry {
if (!previous) return next;
if (!next.quotas && next.message && hasUsableCachedData(previous)) {
return previous;
}
if (provider !== GROK_CLI_PROVIDER) return next;
const nextBilling = next.billing;
const previousAutoTopUp = previous.billing?.autoTopUp;
if (!nextBilling || nextBilling.autoTopUp.available || !previousAutoTopUp) return next;
return {
...next,
billing: {
...nextBilling,
autoTopUp: previousAutoTopUp,
},
};
}

View File

@@ -453,6 +453,8 @@ export const USAGE_SUPPORTED_PROVIDERS = [
// xAI OAuth (Grok) weekly quota (id + public alias, same pattern as ha/agy)
"xai-oauth",
"xao",
// Grok Build subscription, billing credits, and auto top-up status
"grok-cli",
// Firecrawl team credits (GET /v2/team/credit-usage)
"firecrawl",
];

View File

@@ -0,0 +1,161 @@
export const GROK_BUILD_ADDITIONAL_CREDITS_URL = "https://grok.com/build?_s=usage";
export interface GrokAutoTopUpStatus {
available: boolean;
enabled?: boolean;
thresholdMinorUnits?: number;
amountMinorUnits?: number;
maxMonthlyMinorUnits?: number;
}
export interface GrokBillingStatus {
currency: "USD";
extraCreditsMinorUnits?: number;
autoTopUp: GrokAutoTopUpStatus;
additionalCreditsUrl: typeof GROK_BUILD_ADDITIONAL_CREDITS_URL;
}
export type GrokBillingTranslationKey =
| "grokExtraUsageCredits"
| "grokAutoTopUp"
| "grokAutoTopUpUnavailable"
| "grokAutoTopUpEnabled"
| "grokAutoTopUpDisabled"
| "grokAutoTopUpAt"
| "grokAutoTopUpAdd"
| "grokAutoTopUpMax"
| "grokAutoTopUpMonth"
| "grokAdditionalCredits";
export type GrokBillingTranslator = (key: GrokBillingTranslationKey, fallback: string) => string;
export type GrokBillingCardRow =
| { kind: "balance" | "status"; label: string; value: string }
| {
kind: "link";
label: string;
href: typeof GROK_BUILD_ADDITIONAL_CREDITS_URL;
target: "_blank";
rel: "noreferrer noopener";
};
type JsonRecord = Record<string, unknown>;
function toRecord(value: unknown): JsonRecord | null {
return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : null;
}
function minorUnits(value: unknown): number | undefined {
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : undefined;
}
export function sanitizeGrokBillingStatus(value: unknown): GrokBillingStatus | undefined {
const billing = toRecord(value);
if (!billing || billing.currency !== "USD") return undefined;
if (billing.additionalCreditsUrl !== GROK_BUILD_ADDITIONAL_CREDITS_URL) return undefined;
const rawAutoTopUp = toRecord(billing.autoTopUp);
if (!rawAutoTopUp || typeof rawAutoTopUp.available !== "boolean") return undefined;
const available = rawAutoTopUp.available;
const enabled =
available && typeof rawAutoTopUp.enabled === "boolean" ? rawAutoTopUp.enabled : undefined;
const extraCreditsMinorUnits = minorUnits(billing.extraCreditsMinorUnits);
const thresholdMinorUnits =
enabled === true ? minorUnits(rawAutoTopUp.thresholdMinorUnits) : undefined;
const amountMinorUnits = enabled === true ? minorUnits(rawAutoTopUp.amountMinorUnits) : undefined;
const maxMonthlyMinorUnits =
enabled === true ? minorUnits(rawAutoTopUp.maxMonthlyMinorUnits) : undefined;
return {
currency: "USD",
...(extraCreditsMinorUnits !== undefined ? { extraCreditsMinorUnits } : {}),
autoTopUp: {
available,
...(enabled !== undefined ? { enabled } : {}),
...(thresholdMinorUnits !== undefined ? { thresholdMinorUnits } : {}),
...(amountMinorUnits !== undefined ? { amountMinorUnits } : {}),
...(maxMonthlyMinorUnits !== undefined ? { maxMonthlyMinorUnits } : {}),
},
additionalCreditsUrl: GROK_BUILD_ADDITIONAL_CREDITS_URL,
};
}
export function formatGrokMinorUnits(
value: number | undefined,
currency: GrokBillingStatus["currency"],
locales?: Intl.LocalesArgument
): string | null {
if (value === undefined) return null;
return new Intl.NumberFormat(locales, {
style: "currency",
currency,
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}).format(value / 100);
}
const fallbackTranslation: GrokBillingTranslator = (_key, fallback) => fallback;
export function buildGrokBillingCardRows(
billing: GrokBillingStatus,
locales?: Intl.LocalesArgument,
translate: GrokBillingTranslator = fallbackTranslation
): GrokBillingCardRow[] {
const rows: GrokBillingCardRow[] = [];
const extraCredits = formatGrokMinorUnits(
billing.extraCreditsMinorUnits,
billing.currency,
locales
);
if (extraCredits !== null) {
rows.push({
kind: "balance",
label: translate("grokExtraUsageCredits", "Extra Usage Credits"),
value: extraCredits,
});
}
const autoTopUp = billing.autoTopUp;
let autoTopUpValue: string;
if (!autoTopUp.available) {
autoTopUpValue = translate("grokAutoTopUpUnavailable", "Unavailable");
} else if (!autoTopUp.enabled) {
autoTopUpValue = translate("grokAutoTopUpDisabled", "Disabled");
} else {
const threshold = formatGrokMinorUnits(
autoTopUp.thresholdMinorUnits,
billing.currency,
locales
);
const amount = formatGrokMinorUnits(autoTopUp.amountMinorUnits, billing.currency, locales);
const maximum = formatGrokMinorUnits(autoTopUp.maxMonthlyMinorUnits, billing.currency, locales);
autoTopUpValue = [
translate("grokAutoTopUpEnabled", "Enabled"),
threshold ? `${translate("grokAutoTopUpAt", "at")} ${threshold}` : null,
amount ? `${translate("grokAutoTopUpAdd", "add")} ${amount}` : null,
maximum
? `${translate("grokAutoTopUpMax", "max")} ${maximum}/${translate(
"grokAutoTopUpMonth",
"month"
)}`
: null,
]
.filter((part): part is string => part !== null)
.join(" · ");
}
rows.push({
kind: "status",
label: translate("grokAutoTopUp", "Auto Top-Up"),
value: autoTopUpValue,
});
rows.push({
kind: "link",
label: translate("grokAdditionalCredits", "Additional Credits"),
href: billing.additionalCreditsUrl,
target: "_blank",
rel: "noreferrer noopener",
});
return rows;
}

View File

@@ -0,0 +1,303 @@
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-grok-limits-ui-"));
process.env.DATA_DIR = TEST_DATA_DIR;
process.env.STORAGE_ENCRYPTION_KEY = "grok-provider-limits-ui-test-key-32-bytes-minimum";
process.env.DISABLE_SQLITE_AUTO_BACKUP = "true";
const { parseQuotaData, resolvePlanValue, buildProviderLimitsResolvedPlans, normalizePlanTier } =
await import("../../src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.tsx");
const { PROVIDER_LABEL } =
await import("../../src/app/(dashboard)/dashboard/usage/components/ProviderLimits/constants.ts");
const { USAGE_SUPPORTED_PROVIDERS } = await import("../../src/shared/constants/providers.ts");
const {
buildGrokBillingCardRows,
formatGrokMinorUnits,
GROK_BUILD_ADDITIONAL_CREDITS_URL,
sanitizeGrokBillingStatus,
} = await import("../../src/shared/utils/grokBilling.ts");
type GrokBillingTranslator =
typeof import("../../src/shared/utils/grokBilling.ts").GrokBillingTranslator;
const baseBilling = {
currency: "USD" as const,
autoTopUp: { available: false },
additionalCreditsUrl: GROK_BUILD_ADDITIONAL_CREDITS_URL,
};
test.after(() => {
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
});
test("Grok Build product aliases normalize to one stable row and preserve collisions", () => {
const parsed = parseQuotaData("grok-cli", {
quotas: {
weekly: {
used: 37.25,
total: 100,
remaining: 62.75,
remainingPercentage: 62.75,
resetAt: "2099-08-03T00:00:00.000Z",
isPercentageOnly: true,
},
product_grok_build: {
displayName: "Grok Build",
used: 12.5,
total: 100,
remaining: 87.5,
remainingPercentage: 87.5,
resetAt: "2099-08-03T00:00:00.000Z",
isPercentageOnly: true,
},
product_grok_build_2: {
displayName: "Grok Build",
used: 25,
total: 100,
remaining: 75,
remainingPercentage: 75,
resetAt: "2099-08-03T00:00:00.000Z",
isPercentageOnly: true,
},
},
});
assert.deepEqual(
parsed.map(({ name, displayName, remainingPercentage }) => ({
name,
displayName,
remainingPercentage,
})),
[
{ name: "weekly", displayName: undefined, remainingPercentage: 62.75 },
{ name: "product_grok_build", displayName: "Grok Build", remainingPercentage: 87.5 },
{ name: "product_grok_build_2", displayName: "Grok Build", remainingPercentage: 75 },
]
);
});
test("grok-cli plan display never infers persisted provider-specific tiers", () => {
assert.equal(
resolvePlanValue(
null,
{ subscriptionTier: "Persisted Secret Tier", plan: "Persisted Plan" },
"grok-cli"
),
null
);
assert.equal(
resolvePlanValue(
"Future Experimental Tier",
{ subscriptionTier: "Persisted Tier" },
"grok-cli"
),
"Future Experimental Tier"
);
});
test("page-level tier stats/filters ignore persisted Grok Free/Enterprise without live plan", () => {
const connections = [
{
id: "grok-free",
provider: "grok-cli",
providerSpecificData: {
tier: "Free",
plan: "Free",
subscriptionTier: "Free",
},
},
{
id: "grok-enterprise",
provider: "grok-cli",
providerSpecificData: {
tier: "Enterprise",
plan: "Enterprise",
subscriptionTier: "Enterprise",
},
},
{
id: "grok-live",
provider: "grok-cli",
providerSpecificData: {
tier: "Free",
plan: "Free",
subscriptionTier: "Free",
},
},
{
id: "codex-fallback",
provider: "codex",
providerSpecificData: { chatgptPlanType: "Pro" },
},
{
id: "claude-fallback",
provider: "claude",
providerSpecificData: { plan: "Pro" },
},
];
const quotaData = {
"grok-free": { plan: null },
"grok-enterprise": {},
"grok-live": { plan: "Enterprise" },
"codex-fallback": { plan: "unknown" },
"claude-fallback": { plan: null },
};
const resolvedPlans = buildProviderLimitsResolvedPlans(connections, quotaData);
assert.equal(resolvedPlans["grok-free"], null);
assert.equal(resolvedPlans["grok-enterprise"], null);
assert.equal(resolvedPlans["grok-live"], "Enterprise");
assert.equal(resolvedPlans["codex-fallback"], "Pro");
assert.equal(resolvedPlans["claude-fallback"], "Pro");
const tierByConnection = Object.fromEntries(
connections.map((conn) => [conn.id, normalizePlanTier(resolvedPlans[conn.id])])
);
assert.equal(tierByConnection["grok-free"].key, "unknown");
assert.equal(tierByConnection["grok-enterprise"].key, "unknown");
assert.equal(tierByConnection["grok-live"].key, "enterprise");
assert.equal(tierByConnection["codex-fallback"].key, "pro");
assert.equal(tierByConnection["claude-fallback"].key, "pro");
// Filter/stat bucket classification must not invent Free/Enterprise from PSD.
assert.notEqual(tierByConnection["grok-free"].key, "free");
assert.notEqual(tierByConnection["grok-enterprise"].key, "enterprise");
const tierCounts = {
free: 0,
enterprise: 0,
pro: 0,
unknown: 0,
};
for (const conn of connections) {
const key = tierByConnection[conn.id]?.key || "unknown";
if (key in tierCounts) tierCounts[key] += 1;
}
assert.equal(tierCounts.free, 0);
assert.equal(tierCounts.enterprise, 1); // only live Grok Enterprise
assert.equal(tierCounts.pro, 2); // Codex + Claude fallbacks unchanged
assert.equal(tierCounts.unknown, 2); // persisted Free + Enterprise without live plan
});
test("Grok billing rows omit a missing balance and show an explicit localized zero", () => {
const missing = buildGrokBillingCardRows(baseBilling, "en-US");
assert.equal(
missing.some((row) => row.kind === "balance"),
false
);
assert.deepEqual(missing[0], {
kind: "status",
label: "Auto Top-Up",
value: "Unavailable",
});
const zero = buildGrokBillingCardRows({ ...baseBilling, extraCreditsMinorUnits: 0 }, "de-DE");
assert.deepEqual(zero[0], {
kind: "balance",
label: "Extra Usage Credits",
value: "0,00 $",
});
});
test("Grok billing rows distinguish disabled and unavailable and translate enabled details", () => {
const translate: GrokBillingTranslator = (key, fallback) =>
({
grokExtraUsageCredits: "Credits translated",
grokAutoTopUp: "Top-up translated",
grokAutoTopUpEnabled: "On translated",
grokAutoTopUpAt: "threshold translated",
grokAutoTopUpAdd: "add translated",
grokAutoTopUpMax: "maximum translated",
grokAutoTopUpMonth: "month translated",
grokAdditionalCredits: "Buy translated",
})[key] ?? fallback;
const disabled = buildGrokBillingCardRows(
{ ...baseBilling, autoTopUp: { available: true, enabled: false } },
"en-US",
translate
);
assert.equal(disabled.find((row) => row.kind === "status")?.value, "Disabled");
const enabled = buildGrokBillingCardRows(
{
...baseBilling,
extraCreditsMinorUnits: 0,
autoTopUp: {
available: true,
enabled: true,
thresholdMinorUnits: 500,
amountMinorUnits: 2000,
maxMonthlyMinorUnits: 10000,
},
},
"en-US",
translate
);
assert.deepEqual(enabled, [
{ kind: "balance", label: "Credits translated", value: "$0.00" },
{
kind: "status",
label: "Top-up translated",
value:
"On translated · threshold translated $5.00 · add translated $20.00 · maximum translated $100.00/month translated",
},
{
kind: "link",
label: "Buy translated",
href: GROK_BUILD_ADDITIONAL_CREDITS_URL,
target: "_blank",
rel: "noreferrer noopener",
},
]);
});
test("Provider Limits exposes only the sanitized Grok billing contract", () => {
assert.ok(USAGE_SUPPORTED_PROVIDERS.includes("grok-cli"));
assert.equal(PROVIDER_LABEL["grok-cli"], "Grok Build");
const billing = sanitizeGrokBillingStatus({
currency: "USD",
extraCreditsMinorUnits: 0,
autoTopUp: {
available: true,
enabled: true,
thresholdMinorUnits: 500,
amountMinorUnits: 2000,
maxMonthlyMinorUnits: 10000,
paymentMethodId: "secret",
},
additionalCreditsUrl: GROK_BUILD_ADDITIONAL_CREDITS_URL,
rawBody: "secret",
});
assert.deepEqual(billing, {
currency: "USD",
extraCreditsMinorUnits: 0,
autoTopUp: {
available: true,
enabled: true,
thresholdMinorUnits: 500,
amountMinorUnits: 2000,
maxMonthlyMinorUnits: 10000,
},
additionalCreditsUrl: GROK_BUILD_ADDITIONAL_CREDITS_URL,
});
assert.equal(formatGrokMinorUnits(billing?.extraCreditsMinorUnits, "USD", "en-US"), "$0.00");
assert.equal(formatGrokMinorUnits(billing?.autoTopUp.amountMinorUnits, "USD", "en-US"), "$20.00");
assert.equal(
sanitizeGrokBillingStatus({
currency: "USD",
autoTopUp: { available: false },
additionalCreditsUrl: "https://attacker.invalid/credits",
}),
undefined
);
});

View File

@@ -0,0 +1,494 @@
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-grok-limits-"));
process.env.DATA_DIR = TEST_DATA_DIR;
process.env.STORAGE_ENCRYPTION_KEY = "grok-provider-limits-test-key-32-bytes-minimum";
process.env.DISABLE_SQLITE_AUTO_BACKUP = "true";
const core = await import("../../src/lib/db/core.ts");
const { getUsageForProvider, USAGE_FETCHER_PROVIDERS } =
await import("../../open-sse/services/usage.ts");
const { __testing: grokTesting } = await import("../../open-sse/services/usage/grokCli.ts");
const providerLimitsDb = await import("../../src/lib/db/providerLimits.ts");
const { mergeProviderLimitsCacheEntry } =
await import("../../src/lib/usage/providerLimitsCache.ts");
const originalFetch = globalThis.fetch;
interface FetchCall {
url: string;
init: RequestInit;
}
function response(value: unknown, init: ResponseInit = {}) {
return new Response(JSON.stringify(value), {
status: 200,
headers: { "content-type": "application/json" },
...init,
});
}
function successFixtures(
options: {
tier?: unknown;
userId?: unknown;
prepaidBalance?: Record<string, unknown> | null | undefined;
productUsage?: unknown;
} = {}
) {
const tier = "tier" in options ? options.tier : "SuperGrok Heavy";
const userId = "userId" in options ? options.userId : "canonical-user-id";
const prepaidBalance =
"prepaidBalance" in options ? options.prepaidBalance : ({ val: 1234 } as const);
const productUsage =
"productUsage" in options
? options.productUsage
: [
{ product: "API", usagePercent: 12.5 },
{ product: "Grok Code", usagePercent: 44 },
];
return async (input: string | URL | Request) => {
const url = String(input);
if (url.endsWith("/user?include=subscription")) {
return response({
...(userId === undefined ? {} : { userId }),
...(tier === undefined ? {} : { subscriptionTier: tier }),
email: "must-not-be-exposed@example.invalid",
});
}
if (url.endsWith("/billing?format=credits")) {
return response({
config: {
creditUsagePercent: 37.25,
currentPeriod: {
type: "WEEKLY",
start: "2026-07-27T00:00:00.000Z",
end: "2026-08-03T00:00:00.000Z",
},
productUsage,
...(prepaidBalance === undefined ? {} : { prepaidBalance }),
},
});
}
if (url.endsWith("/auto-topup-rule")) {
return response({
rule: {
enabled: true,
minBeforeHittingSl: { val: 500 },
topupAmount: { val: 2000 },
maxAmountPerMonth: { val: 10000 },
paymentMethodId: "must-not-be-exposed",
},
});
}
return new Response(null, { status: 404 });
};
}
interface UsageResult {
plan?: string;
message?: string;
quotas?: Record<
string,
{
displayName?: string;
used: number;
total: number;
remaining: number;
remainingPercentage: number;
resetAt: string | null;
isPercentageOnly: boolean;
}
>;
billing?: {
currency: "USD";
extraCreditsMinorUnits?: number;
autoTopUp: {
available: boolean;
enabled?: boolean;
thresholdMinorUnits?: number;
amountMinorUnits?: number;
maxMonthlyMinorUnits?: number;
};
additionalCreditsUrl: string;
};
}
async function getUsage(fetchImpl: typeof fetch): Promise<UsageResult> {
globalThis.fetch = fetchImpl;
return (await getUsageForProvider({
id: "connection-id",
provider: "grok-cli",
accessToken: "fixture-access-token",
})) as UsageResult;
}
test.afterEach(() => {
globalThis.fetch = originalFetch;
});
test.after(() => {
globalThis.fetch = originalFetch;
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
});
test("grok-cli fetches the fixed read-only surfaces with the full Grok client profile", async () => {
const calls: FetchCall[] = [];
const fixtureFetch = successFixtures();
const usage = await getUsage((async (input: string | URL | Request, init: RequestInit = {}) => {
calls.push({ url: String(input), init });
return fixtureFetch(input);
}) as typeof fetch);
assert.equal(usage.plan, "SuperGrok Heavy");
assert.deepEqual(usage.quotas?.weekly, {
used: 37.25,
total: 100,
remaining: 62.75,
remainingPercentage: 62.75,
resetAt: "2026-08-03T00:00:00.000Z",
isPercentageOnly: true,
});
assert.deepEqual(usage.quotas?.product_api, {
displayName: "API",
used: 12.5,
total: 100,
remaining: 87.5,
remainingPercentage: 87.5,
resetAt: "2026-08-03T00:00:00.000Z",
isPercentageOnly: true,
});
assert.deepEqual(usage.billing, {
currency: "USD",
extraCreditsMinorUnits: 1234,
autoTopUp: {
available: true,
enabled: true,
thresholdMinorUnits: 500,
amountMinorUnits: 2000,
maxMonthlyMinorUnits: 10000,
},
additionalCreditsUrl: "https://grok.com/build?_s=usage",
});
assert.deepEqual(
calls.map((call) => call.url),
[
"https://cli-chat-proxy.grok.com/v1/user?include=subscription",
"https://cli-chat-proxy.grok.com/v1/billing?format=credits",
"https://cli-chat-proxy.grok.com/v1/auto-topup-rule",
]
);
for (const { init } of calls) {
assert.equal(init.method, "GET");
assert.equal(init.redirect, "error");
assert.equal(init.body, undefined);
assert.ok(init.signal instanceof AbortSignal);
const headers = new Headers(init.headers);
assert.equal(headers.get("accept"), "application/json");
assert.equal(headers.get("authorization"), "Bearer fixture-access-token");
assert.equal(headers.get("x-xai-token-auth"), "xai-grok-cli");
assert.ok(headers.get("user-agent"));
assert.ok(headers.get("x-grok-client-version"));
assert.ok(headers.get("x-grok-client-identifier"));
assert.equal(headers.get("x-grok-client-mode"), "headless");
}
assert.equal(new Headers(calls[0].init.headers).has("x-userid"), false);
assert.equal(new Headers(calls[2].init.headers).get("x-userid"), "canonical-user-id");
assert.deepEqual(grokTesting.networkPolicy, {
method: "GET",
redirect: "error",
timeoutMs: 10_000,
maxResponseBytes: 256 * 1024,
});
const serialized = JSON.stringify(usage);
for (const sensitive of [
"fixture-access-token",
"canonical-user-id",
"must-not-be-exposed@example.invalid",
"paymentMethodId",
]) {
assert.equal(serialized.includes(sensitive), false);
}
});
test("grok-cli preserves unknown and missing values without fabricating billing state", async () => {
for (const tier of [undefined, null, "", " "]) {
const usage = await getUsage(successFixtures({ tier }) as typeof fetch);
assert.equal(usage.plan, undefined);
}
const future = await getUsage(
successFixtures({ tier: "Future Experimental Tier" }) as typeof fetch
);
assert.equal(future.plan, "Future Experimental Tier");
const missing = await getUsage(successFixtures({ prepaidBalance: undefined }) as typeof fetch);
assert.ok(missing.billing);
assert.equal("extraCreditsMinorUnits" in missing.billing, false);
const explicitZero = await getUsage(
successFixtures({ prepaidBalance: { val: 0 } }) as typeof fetch
);
assert.equal(explicitZero.billing?.extraCreditsMinorUnits, 0);
const calls: string[] = [];
const withoutUserId = successFixtures({ userId: undefined });
const noIdentity = await getUsage((async (input: string | URL | Request) => {
calls.push(String(input));
return withoutUserId(input);
}) as typeof fetch);
assert.ok(calls.some((url) => url.endsWith("/billing?format=credits")));
assert.equal(
calls.some((url) => url.endsWith("/auto-topup-rule")),
false
);
assert.deepEqual(noIdentity.billing?.autoTopUp, { available: false });
});
test("official Cent wrappers distinguish omission and normalize signed minor units", async () => {
for (const [prepaidBalance, expected] of [
[undefined, undefined],
[{}, 0],
[{ val: 0 }, 0],
[{ val: 1234 }, 1234],
[{ val: -1234 }, 1234],
] as const) {
const usage = await getUsage(successFixtures({ prepaidBalance }) as typeof fetch);
assert.equal(usage.billing?.extraCreditsMinorUnits, expected);
}
for (const [amount, expected] of [
[undefined, undefined],
[{}, 0],
[{ val: 0 }, 0],
[{ val: 1234 }, 1234],
[{ val: -1234 }, 1234],
] as const) {
const fixture = successFixtures();
const usage = await getUsage((async (input: string | URL | Request) => {
const url = String(input);
if (!url.endsWith("/auto-topup-rule")) return fixture(input);
return response({
rule: {
enabled: true,
...(amount === undefined
? {}
: {
minBeforeHittingSl: amount,
topupAmount: amount,
maxAmountPerMonth: amount,
}),
},
});
}) as typeof fetch);
assert.equal(usage.billing?.autoTopUp.thresholdMinorUnits, expected);
assert.equal(usage.billing?.autoTopUp.amountMinorUnits, expected);
assert.equal(usage.billing?.autoTopUp.maxMonthlyMinorUnits, expected);
}
});
test("auto top-up distinguishes disabled rules from unavailable responses", async () => {
for (const rule of [{}, { enabled: false }]) {
const fixture = successFixtures();
const usage = await getUsage((async (input: string | URL | Request) =>
String(input).endsWith("/auto-topup-rule")
? response({ rule })
: fixture(input)) as typeof fetch);
assert.deepEqual(usage.billing?.autoTopUp, { available: true, enabled: false });
}
for (const payload of [
{},
{ rule: null },
{ rule: "malformed" },
{ rule: { enabled: "malformed" } },
]) {
const fixture = successFixtures();
const usage = await getUsage((async (input: string | URL | Request) =>
String(input).endsWith("/auto-topup-rule")
? response(payload)
: fixture(input)) as typeof fetch);
assert.deepEqual(usage.billing?.autoTopUp, { available: false });
}
const fixture = successFixtures();
const failed = await getUsage((async (input: string | URL | Request) =>
String(input).endsWith("/auto-topup-rule")
? new Response(null, { status: 500 })
: fixture(input)) as typeof fetch);
assert.deepEqual(failed.billing?.autoTopUp, { available: false });
});
test("empty tiers retain the canonical user id for the auto-topup request", async () => {
for (const tier of [undefined, null, "", " "]) {
const calls: FetchCall[] = [];
const fixture = successFixtures({ tier, userId: " canonical-user-id " });
const usage = await getUsage((async (input: string | URL | Request, init: RequestInit = {}) => {
calls.push({ url: String(input), init });
return fixture(input);
}) as typeof fetch);
assert.equal(usage.plan, undefined);
const autoTopUpCall = calls.find((call) => call.url.endsWith("/auto-topup-rule"));
assert.ok(autoTopUpCall);
assert.equal(new Headers(autoTopUpCall.init.headers).get("x-userid"), "canonical-user-id");
}
});
test("Provider Limits cache merges last-known-good Grok auto top-up independently", () => {
const fetchedAt = "2026-08-02T00:00:00.000Z";
for (const previousAutoTopUp of [
{ available: true, enabled: true, amountMinorUnits: 2000 },
{ available: true, enabled: false },
] as const) {
const previous = {
quotas: null,
plan: "Previous Tier",
message: null,
fetchedAt: "2026-08-01T00:00:00.000Z",
billing: {
currency: "USD" as const,
extraCreditsMinorUnits: 100,
autoTopUp: previousAutoTopUp,
additionalCreditsUrl: "https://grok.com/build?_s=usage" as const,
},
};
const next = {
quotas: { weekly: { remainingPercentage: 80 } },
plan: "New Tier",
message: null,
fetchedAt,
billing: {
currency: "USD" as const,
extraCreditsMinorUnits: 250,
autoTopUp: { available: false },
additionalCreditsUrl: "https://grok.com/build?_s=usage" as const,
},
};
assert.deepEqual(mergeProviderLimitsCacheEntry("grok-cli", next, previous), {
...next,
billing: { ...next.billing, autoTopUp: previousAutoTopUp },
});
}
});
test("Provider Limits overall failure preservation accepts billing-only previous data", () => {
const previous = {
quotas: null,
plan: "Previous Tier",
message: null,
fetchedAt: "2026-08-01T00:00:00.000Z",
billing: {
currency: "USD" as const,
autoTopUp: { available: true, enabled: false },
additionalCreditsUrl: "https://grok.com/build?_s=usage" as const,
},
};
const failure = {
quotas: null,
plan: null,
message: "Grok Build billing status unavailable",
fetchedAt: "2026-08-02T00:00:00.000Z",
};
assert.equal(mergeProviderLimitsCacheEntry("grok-cli", failure, previous), previous);
assert.equal(
mergeProviderLimitsCacheEntry("grok-cli", failure, {
...previous,
quotas: {},
billing: undefined,
}),
failure
);
});
test("grok-cli keeps valid fields across sparse partial failures and bounded malformed responses", async () => {
const partial = await getUsage(
successFixtures({
productUsage: [
{ product: "GrokBuild", usagePercent: 25 },
{ product: "PRODUCT_GROK_BUILD", usagePercent: 50 },
{ product: "Future Product", usagePercent: 10 },
{ product: "Future Product", usagePercent: 20 },
{ product: "invalid", usagePercent: "secret-invalid-value" },
],
prepaidBalance: { val: -1 },
}) as typeof fetch
);
assert.equal(partial.quotas?.weekly.remainingPercentage, 62.75);
assert.equal(partial.quotas?.product_grok_build.displayName, "Grok Build");
assert.equal(partial.quotas?.product_grok_build.remainingPercentage, 75);
assert.equal(partial.quotas?.product_grok_build_2.displayName, "Grok Build");
assert.equal(partial.quotas?.product_grok_build_2.remainingPercentage, 50);
assert.equal(partial.quotas?.product_future_product.displayName, "Future Product");
assert.equal(partial.quotas?.product_future_product_2.displayName, "Future Product");
assert.equal(partial.quotas?.product_invalid, undefined);
assert.equal(partial.billing?.extraCreditsMinorUnits, 1);
const sensitive = "token-secret canonical-user-id secret@example.invalid raw-body";
for (const status of [401, 403, 429, 500]) {
const usage = await getUsage((async () => new Response(sensitive, { status })) as typeof fetch);
const serialized = JSON.stringify(usage);
assert.equal(usage.quotas, undefined);
assert.equal(serialized.includes(sensitive), false);
assert.equal(serialized.includes("fixture-access-token"), false);
}
const invalid = await getUsage(
(async () => new Response("{invalid", { status: 200 })) as typeof fetch
);
assert.equal(invalid.quotas, undefined);
const oversized = await getUsage(
(async () =>
new Response(JSON.stringify({ padding: "x".repeat(300_000) }), {
status: 200,
headers: { "content-type": "application/json" },
})) as typeof fetch
);
assert.equal(oversized.quotas, undefined);
});
test("Provider Limits cache persists only the public Grok billing contract", () => {
const cached = providerLimitsDb.setProviderLimitsCache("grok-connection", {
quotas: { weekly: { remainingPercentage: 62.75 } },
plan: "Future Experimental Tier",
message: null,
fetchedAt: "2026-08-02T00:00:00.000Z",
source: "manual",
billing: {
currency: "USD",
extraCreditsMinorUnits: 0,
autoTopUp: {
available: true,
enabled: true,
amountMinorUnits: 2000,
},
additionalCreditsUrl: "https://grok.com/build?_s=usage",
rawBody: "secret",
userId: "secret",
} as unknown as NonNullable<
Parameters<typeof providerLimitsDb.setProviderLimitsCache>[1]["billing"]
>,
});
assert.deepEqual(cached.billing, {
currency: "USD",
extraCreditsMinorUnits: 0,
autoTopUp: { available: true, enabled: true, amountMinorUnits: 2000 },
additionalCreditsUrl: "https://grok.com/build?_s=usage",
});
assert.deepEqual(providerLimitsDb.getProviderLimitsCache("grok-connection"), cached);
assert.equal(JSON.stringify(cached).includes("secret"), false);
});
test("grok-cli is registered on the public Provider Limits usage seam", () => {
assert.ok((USAGE_FETCHER_PROVIDERS as readonly string[]).includes("grok-cli"));
});