FN-8629: prevent false Grok usage percentages
Prevent omitted Grok billing percentages from being displayed as fully consumed credits. - Require a finite CLI-supplied credit usage percentage before creating a billing window. - Cover omitted, zero, invalid, and non-weekly Grok billing responses. - Add a patch changeset for the corrected usage display. Files changed: .changeset/fn-8629-grok-usage-percent.md | 7 +++ packages/dashboard/src/__tests__/usage.test.ts | 71 ++++++++++++++++++++++++-- packages/dashboard/src/usage.ts | 13 ++--- 3 files changed, 77 insertions(+), 14 deletions(-) Fusion-Task-Id: FN-8629 Fusion-Task-Lineage: b5b7c83b-e34f-43d9-a31a-d1fd769c4eb8 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-8629-grok-usage-percent.md
Normal file
7
.changeset/fn-8629-grok-usage-percent.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Prevent false 100% Grok usage when the CLI omits a credit percentage.
|
||||
category: fix
|
||||
dev: Grok billing windows now require a finite API-supplied creditUsagePercent value.
|
||||
@@ -3819,7 +3819,8 @@ describe("usage", () => {
|
||||
expect(mockRequest).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("treats an omitted exhausted percentage as 100% used for a valid weekly CLI billing period", async () => {
|
||||
it("does not fabricate a weekly credit window when the CLI billing percentage is omitted", async () => {
|
||||
vi.stubEnv("GROK_API_KEY", "env-grok-key");
|
||||
mockReadFile.mockImplementation(async (filePath: string) => {
|
||||
if (String(filePath).includes(".grok/auth.json")) return GROK_CLI_AUTH_JSON;
|
||||
return Promise.reject(new Error("File not found"));
|
||||
@@ -3843,14 +3844,74 @@ describe("usage", () => {
|
||||
const grok = providers.find((provider) => provider.name === "Grok")!;
|
||||
|
||||
expect(grok.status).toBe("ok");
|
||||
expect(grok.windows).toEqual([]);
|
||||
expect(grok.windows.some((window) => window.percentUsed === 100)).toBe(false);
|
||||
expect(mockRequest).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("renders a zero-percent weekly CLI credit window", async () => {
|
||||
mockReadFile.mockImplementation(async (filePath: string) => {
|
||||
if (String(filePath).includes(".grok/auth.json")) return GROK_CLI_AUTH_JSON;
|
||||
return Promise.reject(new Error("File not found"));
|
||||
});
|
||||
const periodEnd = new Date(Date.now() + 3 * 24 * 60 * 60 * 1000).toISOString();
|
||||
mockGrokBillingResponse(200, {
|
||||
config: {
|
||||
currentPeriod: { type: "USAGE_PERIOD_TYPE_WEEKLY", end: periodEnd },
|
||||
billingPeriodEnd: periodEnd,
|
||||
creditUsagePercent: 0,
|
||||
},
|
||||
});
|
||||
|
||||
const providers = await fetchAllProviderUsage();
|
||||
const grok = providers.find((provider) => provider.name === "Grok")!;
|
||||
|
||||
expect(grok.windows).toHaveLength(1);
|
||||
expect(grok.windows[0]).toMatchObject({
|
||||
label: "Weekly (credits)",
|
||||
percentUsed: 100,
|
||||
percentLeft: 0,
|
||||
percentUsed: 0,
|
||||
percentLeft: 100,
|
||||
});
|
||||
});
|
||||
|
||||
it.each(["not-a-number", null])("falls back without a CLI credit window for a non-numeric percentage: %j", async (creditUsagePercent) => {
|
||||
mockReadFile.mockImplementation(async (filePath: string) => {
|
||||
if (String(filePath).includes(".grok/auth.json")) return GROK_CLI_AUTH_JSON;
|
||||
return Promise.reject(new Error("File not found"));
|
||||
});
|
||||
mockGrokBillingResponse(200, {
|
||||
config: { creditUsagePercent },
|
||||
});
|
||||
|
||||
const providers = await fetchAllProviderUsage();
|
||||
const grok = providers.find((provider) => provider.name === "Grok")!;
|
||||
|
||||
expect(grok.status).toBe("error");
|
||||
expect(grok.windows).toEqual([]);
|
||||
expect(grok.error).toContain("grok login");
|
||||
});
|
||||
|
||||
it("retains the Credits label for a non-weekly numeric CLI percentage", async () => {
|
||||
mockReadFile.mockImplementation(async (filePath: string) => {
|
||||
if (String(filePath).includes(".grok/auth.json")) return GROK_CLI_AUTH_JSON;
|
||||
return Promise.reject(new Error("File not found"));
|
||||
});
|
||||
mockGrokBillingResponse(200, {
|
||||
config: {
|
||||
currentPeriod: { type: "USAGE_PERIOD_TYPE_MONTHLY" },
|
||||
creditUsagePercent: 25,
|
||||
},
|
||||
});
|
||||
|
||||
const providers = await fetchAllProviderUsage();
|
||||
const grok = providers.find((provider) => provider.name === "Grok")!;
|
||||
|
||||
expect(grok.windows).toHaveLength(1);
|
||||
expect(grok.windows[0]).toMatchObject({
|
||||
label: "Credits",
|
||||
percentUsed: 25,
|
||||
percentLeft: 75,
|
||||
});
|
||||
expect(grok.windows[0].resetText).toContain("resets in");
|
||||
expect(mockRequest).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("falls back to the xAI API-key validity card when CLI billing fails", async () => {
|
||||
|
||||
@@ -1718,16 +1718,11 @@ async function fetchGrokCliBillingUsage(token: string, usage: ProviderUsage): Pr
|
||||
const parsedReset = _parseResetTimestamp(config.billingPeriodEnd ?? config.currentPeriod?.end);
|
||||
const isWeekly = config.currentPeriod?.type === "USAGE_PERIOD_TYPE_WEEKLY";
|
||||
/*
|
||||
FNXC:UsageProviders 2026-07-14-14:47:
|
||||
Grok's billing endpoint omits `creditUsagePercent` when the weekly allowance is exhausted. Grok Build renders that valid reduced config as “Weekly limit: 0%” (zero allowance remaining), while Fusion's usage model stores percent consumed. Therefore the omitted exhausted value maps to 100% used—not 0% used. Only infer exhaustion when the response still proves a weekly billing period and reset boundary, so malformed payloads continue to fail closed.
|
||||
FNXC:UsageProviders 2026-07-31-20:31:
|
||||
A real account reported zero Grok credit usage while its billing response omitted `creditUsagePercent`, disproving the former omitted-field-to-100% inference. Emit a credits window only for a finite API-supplied percentage; field absence must fall through to the API-key validity or CLI-auth error card rather than fabricate consumption.
|
||||
*/
|
||||
const rawPercentUsed = config.creditUsagePercent;
|
||||
const pctUsed = typeof rawPercentUsed === "number" && Number.isFinite(rawPercentUsed)
|
||||
? rawPercentUsed
|
||||
: isWeekly && parsedReset
|
||||
? 100
|
||||
: undefined;
|
||||
if (pctUsed === undefined) return false;
|
||||
const pctUsed = config.creditUsagePercent;
|
||||
if (typeof pctUsed !== "number" || !Number.isFinite(pctUsed)) return false;
|
||||
|
||||
usage.windows.push({
|
||||
label: isWeekly ? "Weekly (credits)" : "Credits",
|
||||
|
||||
Reference in New Issue
Block a user