86 lines
2.6 KiB
TypeScript
86 lines
2.6 KiB
TypeScript
/**
|
|
* usage/quota.ts — shared usage-quota shape + builders for the usage fetchers.
|
|
*
|
|
* Extracted from services/usage.ts (god-file decomposition): the `UsageQuota` value
|
|
* type every per-provider fetcher returns, plus the two pure builders that normalize
|
|
* upstream reset timestamps and assemble a quota from used/total counts. Depends only on
|
|
* the sibling scalar leaf — no network, no DB, no module state — so usage.ts and the
|
|
* per-provider fetcher leaves (MiniMax, GLM, …) import it without a cycle.
|
|
*/
|
|
|
|
import { toNumber, clampPercentage } from "./scalars.ts";
|
|
|
|
export type UsageQuota = {
|
|
used: number;
|
|
total: number;
|
|
remaining?: number;
|
|
remainingPercentage?: number;
|
|
resetAt: string | null;
|
|
unlimited: boolean;
|
|
/**
|
|
* True when the upstream provider reported the remaining fraction. False
|
|
* means the API didn't include the field and the 0 value here is a sentinel,
|
|
* NOT a confirmed-exhausted state. Antigravity-specific.
|
|
*/
|
|
fractionReported?: boolean;
|
|
quotaSource?: "retrieveUserQuota" | "fetchAvailableModels" | "localUsageHistory";
|
|
displayName?: string;
|
|
details?: Array<{
|
|
name: string;
|
|
used: number;
|
|
}>;
|
|
currency?: string;
|
|
grantedBalance?: number;
|
|
toppedUpBalance?: number;
|
|
};
|
|
|
|
export function parseResetTime(resetValue: unknown): string | null {
|
|
if (!resetValue) return null;
|
|
|
|
try {
|
|
let date: Date;
|
|
if (resetValue instanceof Date) {
|
|
date = resetValue;
|
|
} else if (typeof resetValue === "number") {
|
|
date = new Date(resetValue < 1e12 ? resetValue * 1000 : resetValue);
|
|
} else if (typeof resetValue === "string") {
|
|
// Numeric strings are Unix timestamps too (seconds or milliseconds).
|
|
// `new Date("1700000000")` otherwise returns Invalid Date.
|
|
if (/^\d+$/.test(resetValue)) {
|
|
const ts = Number(resetValue);
|
|
date = new Date(ts < 1e12 ? ts * 1000 : ts);
|
|
} else {
|
|
date = new Date(resetValue);
|
|
}
|
|
} else {
|
|
return null;
|
|
}
|
|
|
|
// Epoch-zero (1970-01-01) means no scheduled reset — treat as null
|
|
if (date.getTime() <= 0) return null;
|
|
|
|
return date.toISOString();
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function createQuotaFromUsage(
|
|
usedValue: unknown,
|
|
totalValue: unknown,
|
|
resetValue: unknown
|
|
): UsageQuota {
|
|
const total = Math.max(0, toNumber(totalValue, 0));
|
|
const used = total > 0 ? Math.min(Math.max(0, toNumber(usedValue, 0)), total) : 0;
|
|
const remaining = total > 0 ? Math.max(total - used, 0) : 0;
|
|
|
|
return {
|
|
used,
|
|
total,
|
|
remaining,
|
|
remainingPercentage: total > 0 ? clampPercentage((remaining / total) * 100) : 0,
|
|
resetAt: parseResetTime(resetValue),
|
|
unlimited: false,
|
|
};
|
|
}
|