Merge PR #8325: fix(ai): handle omitted creditUsagePercent in fresh xAI weekly periods (@bubua12)
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
- Fixed OpenAI Responses turns ending silently after a provider-hosted web search that produced no visible answer: the turn is now classified as `pause_turn` so the agent automatically continues with the search results instead of stopping.
|
||||
- Fixed completed model streams retaining their provider concurrency permit until after completion became observable, without replacing provider results when lease cleanup fails ([#8284](https://github.com/can1357/oh-my-pi/pull/8284) by [@ethancawse](https://github.com/ethancawse)).
|
||||
- Fixed the DashScope compatible-mode text-only Qwen override (issue #1859) stripping images from `qwen3.8-max`, which became multimodal in the bundled catalog (image input, #8019). The `-max` guard now only vetoes image content for pre-3.8 SKUs, so `qwen3.8-max`/`qwen3.8-max-preview` and later flagships send `image_url` content — restoring `inspect_image` on those models configured against `dashscope.aliyuncs.com/compatible-mode/v1` ([#8305](https://github.com/can1357/oh-my-pi/issues/8305)).
|
||||
- Fixed xAI (`xai-oauth`) usage reporting falling back to a stale exhausted cache when a fresh weekly cycle with 0% consumed credits omits the `creditUsagePercent` field ([#8325](https://github.com/can1357/oh-my-pi/pull/8325) by [@bubua12](https://github.com/bubua12)).
|
||||
|
||||
## [17.2.15] - 2026-08-12
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ interface XaiWeeklyBillingConfig {
|
||||
productUsage: XaiProductUsage[];
|
||||
onDemandCap?: number;
|
||||
onDemandUsed?: number;
|
||||
inferredPercent?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -137,7 +138,16 @@ function parseWeeklyBillingConfig(raw: Record<string, unknown>): XaiWeeklyBillin
|
||||
return null;
|
||||
}
|
||||
|
||||
const creditUsagePercent = parsePercent(raw.creditUsagePercent);
|
||||
// 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 (inferredPercent) {
|
||||
creditUsagePercent = end > Date.now() ? 0 : undefined;
|
||||
} else {
|
||||
creditUsagePercent = parsePercent(raw.creditUsagePercent);
|
||||
}
|
||||
if (creditUsagePercent === undefined) return null;
|
||||
|
||||
const productUsage: XaiProductUsage[] = [];
|
||||
@@ -146,7 +156,8 @@ function parseWeeklyBillingConfig(raw: Record<string, unknown>): XaiWeeklyBillin
|
||||
for (const item of raw.productUsage) {
|
||||
if (!isRecord(item)) continue;
|
||||
const product = typeof item.product === "string" ? item.product.trim() : "";
|
||||
const usagePercent = parsePercent(item.usagePercent);
|
||||
const usagePercent =
|
||||
item.usagePercent === undefined || item.usagePercent === null ? 0 : parsePercent(item.usagePercent);
|
||||
if (!product || usagePercent === undefined) continue;
|
||||
productUsage.push({ product, usagePercent });
|
||||
}
|
||||
@@ -163,6 +174,7 @@ function parseWeeklyBillingConfig(raw: Record<string, unknown>): XaiWeeklyBillin
|
||||
productUsage,
|
||||
onDemandCap: parseOnDemandAmount(raw.onDemandCap),
|
||||
onDemandUsed: parseOnDemandAmount(raw.onDemandUsed),
|
||||
inferredPercent,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -190,6 +202,10 @@ function parseMonthlyBillingConfig(raw: Record<string, unknown>): XaiMonthlyBill
|
||||
onDemandUsed: parseOnDemandAmount(raw.onDemandUsed),
|
||||
};
|
||||
}
|
||||
function hasPositiveMonthlyLimit(raw: Record<string, unknown>): boolean {
|
||||
const limit = parseOnDemandAmount(raw.monthlyLimit);
|
||||
return limit !== undefined && limit > 0;
|
||||
}
|
||||
|
||||
function buildOnDemandLimit(
|
||||
onDemandCap: number | undefined,
|
||||
@@ -361,10 +377,31 @@ export const xaiOauthUsageProvider: UsageProvider = {
|
||||
: null;
|
||||
}
|
||||
|
||||
if (!weekly && !monthly) return null;
|
||||
// When an account is marked unified billing and weekly credits were only inferred
|
||||
// from an omitted percentage field:
|
||||
// - If a positive monthly quota is returned, use the monthly quota alone.
|
||||
// - If the monthly endpoint returned a valid config without positive monthly quota,
|
||||
// confirm that this account relies on the weekly reset cycle and use weekly.
|
||||
// - If the monthly fetch failed (transient network error), reject inferred weekly
|
||||
// so AuthStorage's retain-last-good cache preserves the previous valid snapshot.
|
||||
let effectiveWeekly = weekly;
|
||||
if (weekly?.inferredPercent && creditsLooksUnified) {
|
||||
if (monthly) {
|
||||
effectiveWeekly = null;
|
||||
} else {
|
||||
const monthlyConfig =
|
||||
monthlyPayload && isRecord(monthlyPayload) && isRecord(monthlyPayload.config)
|
||||
? monthlyPayload.config
|
||||
: null;
|
||||
if (!monthlyConfig || hasPositiveMonthlyLimit(monthlyConfig)) {
|
||||
effectiveWeekly = 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<string>();
|
||||
@@ -375,12 +412,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;
|
||||
|
||||
|
||||
@@ -125,6 +125,9 @@ function dualBillingFetch(
|
||||
});
|
||||
}
|
||||
const payload = url.includes("format=credits") ? creditsPayload : monthlyPayload;
|
||||
if (payload === null) {
|
||||
return new Response("internal error", { status: 500 });
|
||||
}
|
||||
return new Response(JSON.stringify(payload), {
|
||||
status: 200,
|
||||
headers: { "content-type": "application/json" },
|
||||
@@ -224,6 +227,114 @@ describe("xai-oauth usage provider", () => {
|
||||
expect(report?.limits[0]?.id).toBe("xai-oauth:credits:1w");
|
||||
expect(report?.limits[0]?.window?.resetsAt).toBe(Date.parse(periodEnd));
|
||||
});
|
||||
it("rejects an expired weekly period when creditUsagePercent is omitted", async () => {
|
||||
const periodEnd = new Date(Date.now() - 60_000).toISOString();
|
||||
const periodStart = new Date(Date.now() - 8 * 24 * 60 * 60 * 1000).toISOString();
|
||||
const report = await xaiOauthUsageProvider.fetchUsage(
|
||||
{ provider: "xai-oauth", credential: makeCredential() },
|
||||
{
|
||||
fetch: capturingFetch({
|
||||
config: {
|
||||
currentPeriod: {
|
||||
end: periodEnd,
|
||||
start: periodStart,
|
||||
type: "USAGE_PERIOD_TYPE_WEEKLY",
|
||||
},
|
||||
},
|
||||
}).fetch,
|
||||
},
|
||||
);
|
||||
|
||||
expect(report).toBeNull();
|
||||
});
|
||||
|
||||
it("reports zero usage when weekly period is active but creditUsagePercent is omitted (fresh reset)", async () => {
|
||||
const periodEnd = new Date(Date.now() + 6 * 24 * 60 * 60 * 1000).toISOString();
|
||||
const periodStart = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
|
||||
const report = await xaiOauthUsageProvider.fetchUsage(
|
||||
{ provider: "xai-oauth", credential: makeCredential() },
|
||||
{
|
||||
fetch: capturingFetch({
|
||||
config: {
|
||||
currentPeriod: {
|
||||
end: periodEnd,
|
||||
start: periodStart,
|
||||
type: "USAGE_PERIOD_TYPE_WEEKLY",
|
||||
},
|
||||
isUnifiedBillingUser: true,
|
||||
onDemandCap: { val: 0 },
|
||||
onDemandUsed: { val: 0 },
|
||||
},
|
||||
}).fetch,
|
||||
},
|
||||
);
|
||||
|
||||
expect(report?.metadata?.billingKind).toBe("weekly");
|
||||
expect(report?.limits.map(limit => limit.id)).toEqual(["xai-oauth:credits:1w"]);
|
||||
const credits = report?.limits[0];
|
||||
expect(credits?.amount.used).toBe(0);
|
||||
expect(credits?.amount.limit).toBe(100);
|
||||
expect(credits?.amount.remaining).toBe(100);
|
||||
expect(credits?.amount.usedFraction).toBe(0);
|
||||
expect(credits?.amount.remainingFraction).toBe(1);
|
||||
expect(credits?.status).toBe("ok");
|
||||
expect(credits?.window?.resetsAt).toBe(Date.parse(periodEnd));
|
||||
});
|
||||
|
||||
it("rejects inferred unified weekly usage when the monthly probe encounters a network error", async () => {
|
||||
const periodEnd = new Date(Date.now() + 6 * 24 * 60 * 60 * 1000).toISOString();
|
||||
const periodStart = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
|
||||
const report = await xaiOauthUsageProvider.fetchUsage(
|
||||
{ provider: "xai-oauth", credential: makeCredential() },
|
||||
{
|
||||
fetch: dualBillingFetch(
|
||||
{
|
||||
config: {
|
||||
currentPeriod: {
|
||||
end: periodEnd,
|
||||
start: periodStart,
|
||||
type: "USAGE_PERIOD_TYPE_WEEKLY",
|
||||
},
|
||||
isUnifiedBillingUser: true,
|
||||
},
|
||||
},
|
||||
null,
|
||||
).fetch,
|
||||
},
|
||||
);
|
||||
|
||||
expect(report).toBeNull();
|
||||
});
|
||||
it("rejects inferred unified weekly usage when monthly config has a positive limit but malformed fields", async () => {
|
||||
const periodEnd = new Date(Date.now() + 6 * 24 * 60 * 60 * 1000).toISOString();
|
||||
const periodStart = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
|
||||
const report = await xaiOauthUsageProvider.fetchUsage(
|
||||
{ provider: "xai-oauth", credential: makeCredential() },
|
||||
{
|
||||
fetch: dualBillingFetch(
|
||||
{
|
||||
config: {
|
||||
currentPeriod: {
|
||||
end: periodEnd,
|
||||
start: periodStart,
|
||||
type: "USAGE_PERIOD_TYPE_WEEKLY",
|
||||
},
|
||||
isUnifiedBillingUser: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
config: {
|
||||
isUnifiedBillingUser: true,
|
||||
monthlyLimit: { val: 15000 },
|
||||
// Missing 'used' and billingPeriod dates
|
||||
},
|
||||
},
|
||||
).fetch,
|
||||
},
|
||||
);
|
||||
|
||||
expect(report).toBeNull();
|
||||
});
|
||||
|
||||
it("falls back to monthly included quota when credits has no percent fields", async () => {
|
||||
const { fetch, calls } = dualBillingFetch(makeUnifiedCreditsPayload(), makeUnifiedMonthlyPayload());
|
||||
@@ -290,7 +401,6 @@ describe("xai-oauth usage provider", () => {
|
||||
).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);
|
||||
@@ -299,10 +409,11 @@ describe("xai-oauth usage provider", () => {
|
||||
});
|
||||
|
||||
it("returns null when both credits and monthly billing shapes are unusable", async () => {
|
||||
const creditsUnusable = { config: { isUnifiedBillingUser: true, onDemandCap: { val: 0 } } };
|
||||
const report = await xaiOauthUsageProvider.fetchUsage(
|
||||
{ provider: "xai-oauth", credential: makeCredential() },
|
||||
{
|
||||
fetch: dualBillingFetch(makeUnifiedCreditsPayload(), {
|
||||
fetch: dualBillingFetch(creditsUnusable, {
|
||||
config: { isUnifiedBillingUser: true, monthlyLimit: { val: 0 }, used: { val: 0 } },
|
||||
}).fetch,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user