fix(quota): restore Grok Settings usage for current CLI auth/billing (#2353)
* fix(quota): restore Grok Settings usage for current CLI auth/billing Grok CLI no longer stores a top-level access_token or usage.creditUsage. Read nested auth key tokens and config.used.val so Settings → Usage shows monthly credits again. Keep legacy shapes and env tokens working. Fixes #2352 * fix(quota): make Grok auth-file tests work on Windows Inject homeDir into GrokQuotaProvider like Kimi so nested ~/.grok/auth.json tests do not depend on os.homedir() which ignores $HOME on Windows (USERPROFILE).
This commit is contained in:
@@ -51,6 +51,11 @@ function writeKimiCredentials(dir: string, accessToken: string): void {
|
||||
);
|
||||
}
|
||||
|
||||
function writeGrokAuth(home: string, auth: Record<string, unknown>): void {
|
||||
mkdirSync(join(home, ".grok"), { recursive: true });
|
||||
writeFileSync(join(home, ".grok", "auth.json"), JSON.stringify(auth));
|
||||
}
|
||||
|
||||
function writeMiniMaxConfig(dir: string, payload: Record<string, unknown>): void {
|
||||
mkdirSync(join(dir, ".mmx"), { recursive: true });
|
||||
writeFileSync(join(dir, ".mmx", "config.json"), JSON.stringify(payload));
|
||||
@@ -382,7 +387,13 @@ describe("real provider usage fetchers", () => {
|
||||
new CopilotQuotaProvider({ logger, fetch: fetchThroughTestDouble }),
|
||||
new CursorQuotaProvider({ logger, fetch: fetchThroughTestDouble }),
|
||||
new ZaiQuotaProvider({ logger, fetch: fetchThroughTestDouble }),
|
||||
new GrokQuotaProvider({ logger, fetch: fetchThroughTestDouble }),
|
||||
new GrokQuotaProvider({
|
||||
logger,
|
||||
fetch: fetchThroughTestDouble,
|
||||
// Match Kimi: inject temp HOME so nested auth-file tests work on Windows
|
||||
// (os.homedir() uses USERPROFILE there and ignores process.env.HOME).
|
||||
homeDir,
|
||||
}),
|
||||
new KimiQuotaProvider({
|
||||
logger,
|
||||
fetch: fetchThroughTestDouble,
|
||||
@@ -720,8 +731,7 @@ describe("real provider usage fetchers", () => {
|
||||
"https://cli-chat-proxy.grok.com/v1/billing",
|
||||
() =>
|
||||
jsonResponse({
|
||||
config: { monthlyLimit: { val: 0 } },
|
||||
usage: { creditUsage: 0 },
|
||||
config: { monthlyLimit: { val: 0 }, used: { val: 0 } },
|
||||
}),
|
||||
],
|
||||
]),
|
||||
@@ -742,6 +752,109 @@ describe("real provider usage fetchers", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("fetches Grok usage from live billing shape (config.used.val)", async () => {
|
||||
process.env["GROK_API_KEY"] = "grok_test_token";
|
||||
fetchApi = mockFetch(
|
||||
new Map([
|
||||
[
|
||||
"https://cli-chat-proxy.grok.com/v1/billing",
|
||||
() =>
|
||||
jsonResponse({
|
||||
config: {
|
||||
monthlyLimit: { val: 150000 },
|
||||
used: { val: 37886 },
|
||||
billingPeriodStart: "2026-07-01T00:00:00+00:00",
|
||||
billingPeriodEnd: "2026-08-01T00:00:00+00:00",
|
||||
},
|
||||
}),
|
||||
],
|
||||
]),
|
||||
);
|
||||
|
||||
const grok = findProvider(await service().listUsage(), "grok");
|
||||
|
||||
expect(grok).toMatchObject({
|
||||
status: "available",
|
||||
balances: [
|
||||
expect.objectContaining({
|
||||
id: "monthly_credits",
|
||||
used: 37886,
|
||||
remaining: 112114,
|
||||
limit: 150000,
|
||||
unit: "credits",
|
||||
}),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("fetches Grok usage with nested ~/.grok/auth.json key token", async () => {
|
||||
writeGrokAuth(homeDir, {
|
||||
"https://auth.x.ai::test-user-id": {
|
||||
key: "nested_jwt_token",
|
||||
refresh_token: "rt_nested",
|
||||
expires_at: "2026-08-01T00:00:00Z",
|
||||
user_id: "test-user-id",
|
||||
email: "user@example.com",
|
||||
},
|
||||
});
|
||||
|
||||
let authorization: string | null = null;
|
||||
fetchApi = (async (_url: RequestInfo | URL, init?: RequestInit) => {
|
||||
authorization = (init?.headers as Record<string, string> | undefined)?.Authorization ?? null;
|
||||
return jsonResponse({
|
||||
config: {
|
||||
monthlyLimit: { val: 100 },
|
||||
used: { val: 25 },
|
||||
},
|
||||
});
|
||||
}) as typeof fetch;
|
||||
|
||||
const grok = findProvider(await service().listUsage(), "grok");
|
||||
|
||||
expect(authorization).toBe("Bearer nested_jwt_token");
|
||||
expect(grok).toMatchObject({
|
||||
status: "available",
|
||||
balances: [
|
||||
expect.objectContaining({
|
||||
id: "monthly_credits",
|
||||
used: 25,
|
||||
remaining: 75,
|
||||
limit: 100,
|
||||
}),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("still accepts legacy Grok usage.creditUsage when config.used is absent", async () => {
|
||||
process.env["GROK_API_KEY"] = "grok_test_token";
|
||||
fetchApi = mockFetch(
|
||||
new Map([
|
||||
[
|
||||
"https://cli-chat-proxy.grok.com/v1/billing",
|
||||
() =>
|
||||
jsonResponse({
|
||||
config: { monthlyLimit: { val: 50 } },
|
||||
usage: { creditUsage: 10 },
|
||||
}),
|
||||
],
|
||||
]),
|
||||
);
|
||||
|
||||
const grok = findProvider(await service().listUsage(), "grok");
|
||||
|
||||
expect(grok).toMatchObject({
|
||||
status: "available",
|
||||
balances: [
|
||||
expect.objectContaining({
|
||||
id: "monthly_credits",
|
||||
used: 10,
|
||||
remaining: 40,
|
||||
limit: 50,
|
||||
}),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("fetches Kimi usage from KIMI_TOKEN", async () => {
|
||||
process.env["KIMI_TOKEN"] = "kimi_test_token";
|
||||
fetchApi = mockFetch(
|
||||
|
||||
Reference in New Issue
Block a user