diff --git a/packages/ai/src/usage/xai-oauth.ts b/packages/ai/src/usage/xai-oauth.ts index 40aaa08ed..c024f2fea 100644 --- a/packages/ai/src/usage/xai-oauth.ts +++ b/packages/ai/src/usage/xai-oauth.ts @@ -50,6 +50,7 @@ interface XaiWeeklyBillingConfig { productUsage: XaiProductUsage[]; onDemandCap?: number; onDemandUsed?: number; + inferredPercent?: boolean; } /** @@ -140,8 +141,9 @@ function parseWeeklyBillingConfig(raw: Record): XaiWeeklyBillin // Fresh weekly periods (or accounts with 0 usage) omit creditUsagePercent; // default to 0 only when the weekly period is active (end > now). // Expired periods without explicit usage data are rejected to retain last good cache. + const inferredPercent = raw.creditUsagePercent === undefined || raw.creditUsagePercent === null; let creditUsagePercent: number | undefined; - if (raw.creditUsagePercent === undefined || raw.creditUsagePercent === null) { + if (inferredPercent) { creditUsagePercent = end > Date.now() ? 0 : undefined; } else { creditUsagePercent = parsePercent(raw.creditUsagePercent); @@ -172,6 +174,7 @@ function parseWeeklyBillingConfig(raw: Record): XaiWeeklyBillin productUsage, onDemandCap: parseOnDemandAmount(raw.onDemandCap), onDemandUsed: parseOnDemandAmount(raw.onDemandUsed), + inferredPercent, }; } @@ -370,10 +373,14 @@ export const xaiOauthUsageProvider: UsageProvider = { : null; } - if (!weekly && !monthly) return null; + // When an account has an explicit monthly included quota and weekly credits + // were only inferred from an omitted percentage field, prefer the monthly quota + // so pure monthly unified accounts do not render a phantom weekly credit. + const effectiveWeekly = weekly && (!monthly || !weekly.inferredPercent) ? weekly : null; + if (!effectiveWeekly && !monthly) return null; const limits: UsageLimit[] = []; - if (weekly) limits.push(...buildLimits(weekly, accountId)); + if (effectiveWeekly) limits.push(...buildLimits(effectiveWeekly, accountId)); if (monthly) limits.push(...buildLimits(monthly, accountId)); // Deduplicate on-demand if both shapes carried the same cap (keep first). const seen = new Set(); @@ -384,12 +391,13 @@ export const xaiOauthUsageProvider: UsageProvider = { }); if (deduped.length === 0) return null; - const billingKind = weekly && monthly ? "unified" : weekly ? "weekly" : "monthly"; - const endpoint = weekly && monthly ? `${creditsUrl} + ${monthlyUrl}` : weekly ? creditsUrl : monthlyUrl; + const billingKind = effectiveWeekly && monthly ? "unified" : effectiveWeekly ? "weekly" : "monthly"; + const endpoint = + effectiveWeekly && monthly ? `${creditsUrl} + ${monthlyUrl}` : effectiveWeekly ? creditsUrl : monthlyUrl; const raw = - weekly && monthly + effectiveWeekly && monthly ? { credits: creditsPayload, monthly: monthlyPayload } - : weekly + : effectiveWeekly ? creditsPayload : monthlyPayload; diff --git a/packages/ai/test/xai-oauth-usage.test.ts b/packages/ai/test/xai-oauth-usage.test.ts index 1330f2128..ca809972a 100644 --- a/packages/ai/test/xai-oauth-usage.test.ts +++ b/packages/ai/test/xai-oauth-usage.test.ts @@ -278,9 +278,8 @@ describe("xai-oauth usage provider", () => { expect(credits?.window?.resetsAt).toBe(Date.parse(periodEnd)); }); - it("falls back to monthly included quota when credits has no weekly period", async () => { - const creditsNoWeekly = { config: { isUnifiedBillingUser: true, onDemandCap: { val: 0 } } }; - const { fetch, calls } = dualBillingFetch(creditsNoWeekly, makeUnifiedMonthlyPayload()); + it("falls back to monthly included quota when credits has no percent fields", async () => { + const { fetch, calls } = dualBillingFetch(makeUnifiedCreditsPayload(), makeUnifiedMonthlyPayload()); const report = await xaiOauthUsageProvider.fetchUsage( { provider: "xai-oauth", @@ -335,17 +334,15 @@ describe("xai-oauth usage provider", () => { }); it("maps unified monthly on-demand when the included quota payload carries a positive cap", async () => { - const creditsNoWeekly = { config: { isUnifiedBillingUser: true, onDemandCap: { val: 0 } } }; const report = await xaiOauthUsageProvider.fetchUsage( { provider: "xai-oauth", credential: makeCredential() }, { fetch: dualBillingFetch( - creditsNoWeekly, + makeUnifiedCreditsPayload(), makeUnifiedMonthlyPayload({ onDemandCap: { val: 100 }, onDemandUsed: { val: 25 } }), ).fetch, }, ); - expect(report?.limits.map(limit => limit.id)).toEqual(["xai-oauth:included:1mo", "xai-oauth:on-demand"]); const onDemand = report?.limits.find(limit => limit.id === "xai-oauth:on-demand"); expect(onDemand?.amount.used).toBe(25);