openclaw/extensions/xai/usage.ts
Bobby Bones 7f0ea8e546
fix: SuperGrok usage shows No usage data when xAI omits creditUsagePercent (#155790)
<!--
Related: #155782

Required PR title:
fix: SuperGrok usage shows No usage data when xAI omits creditUsagePercent
-->

Related: #155782

## What Problem This Solves

Fixes: SuperGrok usage cards and `openclaw status --usage` show "No usage data" when xAI returns a valid weekly billing period that omits `creditUsagePercent`.

## User Impact

User impact: OAuth SuperGrok accounts now get a specific "Included usage omitted" snapshot, plus plan and prepaid facts, instead of a generic missing-telemetry error. Grok inference was already working; only quota display was wrong.

## Why This Change Was Made

xAI can omit a default-zero included-usage scalar on an otherwise valid weekly or monthly billing period. The parser now treats that as omitted included usage rather than inventing a percentage or reading on-demand pay-as-you-go counters as SuperGrok subscription quota. Legacy monthly `used` / `monthlyLimit` parsing and explicit `creditUsagePercent: 0` windows stay unchanged.

## Evidence

Exact head: `19aae5939ec429b30333e8db836bf6276d583895`. No code change in this proof pass.

Live usage-only billing probe against `GET https://cli-chat-proxy.grok.com/v1/billing?format=credits` (HTTP 200, 413 bytes). Isolated disposable Gateway on `127.0.0.1:63012` (`openclaw gateway run --dev`). Live operator Gateway on `127.0.0.1:18789` (pid 22298) was not restarted, modified, or used for this proof. No inference requests.

Sanitized live billing payload: valid weekly period, `creditUsagePercent` omitted, no legacy `used` / `monthlyLimit`:

```json
{
  "config": {
    "currentPeriod": {
      "type": "USAGE_PERIOD_TYPE_WEEKLY",
      "start": "2026-09-21T12:01:29.688516+00:00",
      "end": "2026-09-28T12:01:29.688516+00:00"
    },
    "onDemandCap": { "val": 0 },
    "onDemandUsed": { "val": 0 },
    "isUnifiedBillingUser": true,
    "prepaidBalance": { "val": 0 },
    "billingPeriodStart": "2026-09-21T12:01:29.688516+00:00",
    "billingPeriodEnd": "2026-09-28T12:01:29.688516+00:00"
  }
}
```

`has_creditUsagePercent`: false. Period recognized as weekly.

PR-head CLI after the same live account, isolated config/port/session store (exit 0):

```text
pnpm openclaw status --usage --timeout 30000
Usage:
  SuperGrok (SuperGrok)
    Included usage omitted
    Prepaid balance: $0.00
```

```text
pnpm openclaw models status
OAuth/token status
- xai usage: Included usage omitted
```

Before (same payload on current main / pre-fix adapter): `SuperGrok: No usage data`.

Focused tests: `node scripts/run-vitest.mjs extensions/xai/usage.test.ts --maxWorkers=1` — 12 passed. File wall 12.38s on one worker; individual cases 1–9ms.

Inspected, sanitized Provider Plans billing-card pair from exact PR-head Control UI (`19aae5939ec429b30333e8db836bf6276d583895`) with a local mocked `usage.status` renderer. Isolated Vite loopback `http://127.0.0.1:51133/`; live operator Gateway `127.0.0.1:18789` (pid 22298) was not used, restarted, or modified. No inference requests.

**Before** (pre-fix SuperGrok snapshot: `error: "No usage data"`):

![Before: SuperGrok Provider Plans card showing No usage data](https://github.com/user-attachments/assets/e315c018-edd5-4e58-99af-f6cbdebe6b66)

**After** (PR-head SuperGrok snapshot: plan + prepaid `$0.00` + `Included usage omitted`):

![After: SuperGrok Provider Plans card showing Included usage omitted](https://github.com/user-attachments/assets/b6157fb5-1cb8-4876-8e00-2b8321b29a13)

No emails, tokens, account identifiers, or private endpoints. Uploaded via the fork `user-attachments` endpoint after upstream `repository_id` returned 404 (no push on `openclaw/openclaw`).

Remaining semantic uncertainty: omitted `creditUsagePercent` is reported as omitted, not proven 0%. Cross-client reports (including [stablyai/orca#20826](https://github.com/stablyai/orca/issues/20826)) suggest xAI drops default-zero credit fields, but that is not a documented xAI contract.


Made with [Cursor](https://cursor.com)

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-09-23 10:06:33 +05:30

245 lines
7.1 KiB
TypeScript

// xAI plugin module implements SuperGrok provider usage behavior.
import { parseDateStringTimestampMs } from "openclaw/plugin-sdk/number-runtime";
import { readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http";
import {
buildUsageHttpErrorSnapshot,
clampPercent,
fetchJson,
type ProviderUsageBilling,
type ProviderUsageSnapshot,
type UsageWindow,
} from "openclaw/plugin-sdk/provider-usage";
import {
asOptionalRecord,
normalizeOptionalString,
} from "openclaw/plugin-sdk/string-coerce-runtime";
const XAI_PROVIDER_ID = "xai";
const SUPERGROK_BILLING_URL = "https://cli-chat-proxy.grok.com/v1/billing?format=credits";
const SUPERGROK_CLIENT_MODE = "cli";
const SUPERGROK_CLIENT_VERSION = "1.0.4";
const MAX_PLAN_CHARS = 128;
const MAX_EXACT_INTEGER = 9_007_199_254_740_991;
type BillingConfig = Record<string, unknown>;
function parseCentValue(value: unknown): number | undefined {
const raw = asOptionalRecord(value)?.val;
if (raw === undefined || raw === null) {
return 0;
}
if (typeof raw === "number" && Number.isInteger(raw)) {
return raw;
}
if (typeof raw === "string" && /^-?\d+$/.test(raw.trim())) {
return Number.parseInt(raw.trim(), 10);
}
return undefined;
}
function parseMoneyValue(value: unknown): number | undefined {
const cents = parseCentValue(value);
if (cents === undefined || cents < 0 || cents > MAX_EXACT_INTEGER) {
return undefined;
}
return cents / 100;
}
function parsePlan(value: unknown): string | undefined {
const plan = normalizeOptionalString(value);
if (!plan || plan.length > MAX_PLAN_CHARS || hasControlCharacter(plan)) {
return undefined;
}
return plan;
}
function hasControlCharacter(value: string): boolean {
for (let index = 0; index < value.length; index += 1) {
if (value.charCodeAt(index) < 32) {
return true;
}
}
return false;
}
function parsePercent(value: unknown): number | undefined {
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
return undefined;
}
return clampPercent(value);
}
function readCurrentPeriod(config: BillingConfig) {
return asOptionalRecord(config["currentPeriod"] ?? config["current_period"]);
}
function readPeriodType(currentPeriod: Record<string, unknown> | undefined): string {
return normalizeOptionalString(currentPeriod?.type) ?? "";
}
function readPeriodBoundMs(
config: BillingConfig,
currentPeriod: Record<string, unknown> | undefined,
bound: "start" | "end",
): number | undefined {
const periodKey = bound === "start" ? "start" : "end";
const billingKey = bound === "start" ? "billingPeriodStart" : "billingPeriodEnd";
const billingSnakeKey = bound === "start" ? "billing_period_start" : "billing_period_end";
return parseDateStringTimestampMs(
currentPeriod?.[periodKey] ?? config[billingKey] ?? config[billingSnakeKey],
);
}
function hasRecognizedUsagePeriod(config: BillingConfig): boolean {
const currentPeriod = readCurrentPeriod(config);
const periodType = readPeriodType(currentPeriod);
if (!periodType.endsWith("WEEKLY") && !periodType.endsWith("MONTHLY")) {
return false;
}
return (
readPeriodBoundMs(config, currentPeriod, "start") !== undefined ||
readPeriodBoundMs(config, currentPeriod, "end") !== undefined
);
}
function hasIncludedUsagePercentField(config: BillingConfig): boolean {
return (config["creditUsagePercent"] ?? config["credit_usage_percent"]) !== undefined;
}
function resolveUsageWindow(config: BillingConfig): UsageWindow | undefined {
const currentPeriod = readCurrentPeriod(config);
const explicitPercent = parsePercent(
config["creditUsagePercent"] ?? config["credit_usage_percent"],
);
const used = parseCentValue(config["used"]);
const monthlyLimit = parseCentValue(config["monthlyLimit"] ?? config["monthly_limit"]);
const legacyPercent =
used !== undefined && monthlyLimit !== undefined && monthlyLimit > 0 && used >= 0
? parsePercent((used / monthlyLimit) * 100)
: undefined;
const percent = explicitPercent ?? legacyPercent;
if (percent === undefined) {
return undefined;
}
const periodType = readPeriodType(currentPeriod);
const label = periodType.endsWith("WEEKLY")
? "Weekly"
: periodType.endsWith("MONTHLY") ||
monthlyLimit !== undefined ||
config["billingPeriodEnd"] !== undefined ||
config["billing_period_end"] !== undefined
? "Monthly"
: "Usage";
const resetAt = readPeriodBoundMs(config, currentPeriod, "end");
return {
label,
usedPercent: percent,
...(resetAt !== undefined ? { resetAt } : {}),
};
}
function resolveBilling(config: BillingConfig): ProviderUsageBilling[] | undefined {
const prepaid = parseMoneyValue(config["prepaidBalance"] ?? config["prepaid_balance"]);
if (prepaid === undefined) {
return undefined;
}
return [
{
type: "balance",
label: "Prepaid balance",
amount: prepaid,
unit: "USD",
},
];
}
function buildSuperGrokUsageSnapshot(data: unknown): ProviderUsageSnapshot {
const payload = asOptionalRecord(data);
const config = asOptionalRecord(payload?.["config"]);
if (!config) {
return {
provider: XAI_PROVIDER_ID,
displayName: "SuperGrok",
windows: [],
error: "Malformed billing response",
};
}
const window = resolveUsageWindow(config);
const billing = resolveBilling(config);
const plan =
parsePlan(payload?.["subscription_tier"] ?? payload?.["subscriptionTier"]) ?? "SuperGrok";
if (window) {
return {
provider: XAI_PROVIDER_ID,
displayName: "SuperGrok",
windows: [window],
billing,
plan,
};
}
// xAI omits default-zero included-usage scalars on valid weekly/monthly
// billing responses. Do not invent a percent, and do not read on-demand
// pay-as-you-go counters as SuperGrok subscription quota.
if (!hasIncludedUsagePercentField(config) && hasRecognizedUsagePeriod(config)) {
return {
provider: XAI_PROVIDER_ID,
displayName: "SuperGrok",
windows: [],
billing,
plan,
summary: "Included usage omitted",
};
}
return {
provider: XAI_PROVIDER_ID,
displayName: "SuperGrok",
windows: [],
error: "No usage data",
};
}
export async function fetchXaiUsage(
token: string,
timeoutMs: number,
fetchFn: typeof fetch,
): Promise<ProviderUsageSnapshot> {
const response = await fetchJson(
SUPERGROK_BILLING_URL,
{
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
Accept: "application/json",
"x-grok-client-mode": SUPERGROK_CLIENT_MODE,
"x-grok-client-version": SUPERGROK_CLIENT_VERSION,
},
},
timeoutMs,
fetchFn,
);
if (!response.ok) {
await response.body?.cancel().catch(() => undefined);
return buildUsageHttpErrorSnapshot({
provider: XAI_PROVIDER_ID,
status: response.status,
tokenExpiredStatuses: [401, 403],
});
}
try {
return buildSuperGrokUsageSnapshot(
await readProviderJsonResponse<unknown>(response, "xai-usage"),
);
} catch {
return {
provider: XAI_PROVIDER_ID,
displayName: "SuperGrok",
windows: [],
error: "Malformed billing response",
};
}
}