refactor(ai): deduplicated usage provider coercion helpers

- Deleted usage/shared.ts's duplicate toNumber; every consumer in the directory
  now resolves the single catalog implementation.
- Promoted HOUR_MS, DAY_MS, WEEK_MS, parseIsoTimestamp, parsePositiveTimestamp
  and usageStatus into usage/shared.ts and dropped the local copies.
- Left the epoch coercers, status thresholds and base-url normalizers alone:
  they disagree on the 1e12 boundary, non-positive values and warn levels, so
  merging them would change reported usage.
This commit is contained in:
can1357
2026-08-08 06:32:00 +02:00
parent 5fa578fa3b
commit 2efbdeaab9
16 changed files with 114 additions and 173 deletions
+8 -15
View File
@@ -1,3 +1,4 @@
import { toNumber } from "@oh-my-pi/pi-catalog/utils";
import { parseAlibabaTokenPlanCredential } from "@oh-my-pi/pi-catalog/wire/alibaba-token-plan"; import { parseAlibabaTokenPlanCredential } from "@oh-my-pi/pi-catalog/wire/alibaba-token-plan";
import type { import type {
CredentialRankingStrategy, CredentialRankingStrategy,
@@ -8,7 +9,7 @@ import type {
UsageReport, UsageReport,
} from "../usage"; } from "../usage";
import { isRecord } from "../utils"; import { isRecord } from "../utils";
import { toNumber } from "./shared"; import { HOUR_MS, parsePositiveTimestamp, WEEK_MS } from "./shared";
const PROVIDER = "alibaba-token-plan"; const PROVIDER = "alibaba-token-plan";
const CONSOLE_ORIGIN = "https://home.qwencloud.com"; const CONSOLE_ORIGIN = "https://home.qwencloud.com";
@@ -19,8 +20,6 @@ const USAGE_API = "zeldaHttp.apikeyMgr./tokenplan/personal/api/v2/usage";
const USAGE_URL = `https://cs-data.qwencloud.com/data/api.json?product=sfm_bailian&action=${GATEWAY_ACTION}&api=${encodeURIComponent(USAGE_API)}`; const USAGE_URL = `https://cs-data.qwencloud.com/data/api.json?product=sfm_bailian&action=${GATEWAY_ACTION}&api=${encodeURIComponent(USAGE_API)}`;
const BROWSER_USER_AGENT = const BROWSER_USER_AGENT =
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36"; "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36";
const FIVE_HOURS_MS = 5 * 60 * 60 * 1000;
const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000;
const CONSOLE_CORNERSTONE_PARAM = { const CONSOLE_CORNERSTONE_PARAM = {
domain: "home.qwencloud.com", domain: "home.qwencloud.com",
consoleSite: "QWENCLOUD", consoleSite: "QWENCLOUD",
@@ -55,12 +54,6 @@ function unwrapGatewayData(value: Record<string, unknown>): Record<string, unkno
return current; return current;
} }
function parseResetTime(value: unknown): number | undefined {
const parsed = toNumber(value);
if (parsed === undefined || parsed <= 0) return undefined;
return parsed < 1_000_000_000_000 ? parsed * 1000 : parsed;
}
function parseUsedFraction(value: unknown): number | undefined { function parseUsedFraction(value: unknown): number | undefined {
const parsed = toNumber(value); const parsed = toNumber(value);
if (parsed === undefined || parsed < 0) return undefined; if (parsed === undefined || parsed < 0) return undefined;
@@ -178,17 +171,17 @@ async function fetchAlibabaTokenPlanUsage(
buildLimit( buildLimit(
"5h", "5h",
"5 Hour Credits", "5 Hour Credits",
FIVE_HOURS_MS, 5 * HOUR_MS,
parseUsedFraction(responseData.per5HourPercentage), parseUsedFraction(responseData.per5HourPercentage),
parseResetTime(responseData.per5HourResetTime), parsePositiveTimestamp(responseData.per5HourResetTime),
accountId, accountId,
), ),
buildLimit( buildLimit(
"7d", "7d",
"7 Day Credits", "7 Day Credits",
SEVEN_DAYS_MS, WEEK_MS,
parseUsedFraction(responseData.per1WeekPercentage), parseUsedFraction(responseData.per1WeekPercentage),
parseResetTime(responseData.per1WeekResetTime), parsePositiveTimestamp(responseData.per1WeekResetTime),
accountId, accountId,
), ),
].filter((limit): limit is UsageLimit => limit !== undefined); ].filter((limit): limit is UsageLimit => limit !== undefined);
@@ -224,7 +217,7 @@ export const alibabaTokenPlanRankingStrategy: CredentialRankingStrategy = {
secondary: report.limits.find(limit => limit.id === "credits:7d"), secondary: report.limits.find(limit => limit.id === "credits:7d"),
}), }),
windowDefaults: { windowDefaults: {
primaryMs: FIVE_HOURS_MS, primaryMs: 5 * HOUR_MS,
secondaryMs: SEVEN_DAYS_MS, secondaryMs: WEEK_MS,
}, },
}; };
+12 -19
View File
@@ -17,10 +17,9 @@ import {
type UsageWindow, type UsageWindow,
} from "../usage"; } from "../usage";
import { isRecord } from "../utils"; import { isRecord } from "../utils";
import { HOUR_MS, parseIsoTimestamp, WEEK_MS } from "./shared";
const DEFAULT_ENDPOINT = "https://api.anthropic.com/api/oauth"; const DEFAULT_ENDPOINT = "https://api.anthropic.com/api/oauth";
const FIVE_HOURS_MS = 5 * 60 * 60 * 1000;
const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000;
const MAX_ATTEMPTS = 3; const MAX_ATTEMPTS = 3;
const BASE_RETRY_DELAY_MS = 500; const BASE_RETRY_DELAY_MS = 500;
@@ -123,16 +122,10 @@ interface ParsedApiLimitEntry {
displayName?: string; displayName?: string;
} }
function parseIsoTime(value: string | undefined): number | undefined {
if (!value) return undefined;
const parsed = Date.parse(value);
return Number.isFinite(parsed) ? parsed : undefined;
}
function parseBucket(bucket: unknown): ParsedUsageBucket | undefined { function parseBucket(bucket: unknown): ParsedUsageBucket | undefined {
if (!isRecord(bucket)) return undefined; if (!isRecord(bucket)) return undefined;
const utilization = toNumber(bucket.utilization); const utilization = toNumber(bucket.utilization);
const resetsAt = parseIsoTime(typeof bucket.resets_at === "string" ? bucket.resets_at : undefined); const resetsAt = parseIsoTimestamp(typeof bucket.resets_at === "string" ? bucket.resets_at : undefined);
if (utilization === undefined && resetsAt === undefined) { if (utilization === undefined && resetsAt === undefined) {
return undefined; return undefined;
} }
@@ -168,7 +161,7 @@ function parseApiLimitEntries(raw: unknown): ParsedApiLimitEntry[] {
const entry = rawEntry as ClaudeApiLimitEntry; const entry = rawEntry as ClaudeApiLimitEntry;
if (typeof entry.kind !== "string") continue; if (typeof entry.kind !== "string") continue;
const utilization = toNumber(entry.percent); const utilization = toNumber(entry.percent);
const resetsAt = parseIsoTime(typeof entry.resets_at === "string" ? entry.resets_at : undefined); const resetsAt = parseIsoTimestamp(typeof entry.resets_at === "string" ? entry.resets_at : undefined);
if (utilization === undefined && resetsAt === undefined) continue; if (utilization === undefined && resetsAt === undefined) continue;
const displayName = getApiLimitDisplayName(entry.scope); const displayName = getApiLimitDisplayName(entry.scope);
entries.push({ entries.push({
@@ -554,7 +547,7 @@ function buildScopedWeeklyUsageLimits(entries: readonly ParsedApiLimitEntry[]):
label: `Claude 7 Day (${entry.displayName})`, label: `Claude 7 Day (${entry.displayName})`,
windowId: "7d", windowId: "7d",
windowLabel: "7 Day", windowLabel: "7 Day",
durationMs: SEVEN_DAYS_MS, durationMs: WEEK_MS,
bucket: entry.bucket, bucket: entry.bucket,
provider: "anthropic", provider: "anthropic",
tier: slug, tier: slug,
@@ -574,7 +567,7 @@ export function parseClaudeRateLimitHeaders(headers: Record<string, string>, now
label: "Claude 5 Hour", label: "Claude 5 Hour",
windowId: "5h", windowId: "5h",
windowLabel: "5 Hour", windowLabel: "5 Hour",
durationMs: FIVE_HOURS_MS, durationMs: 5 * HOUR_MS,
bucket: fiveHour, bucket: fiveHour,
provider: "anthropic", provider: "anthropic",
shared: true, shared: true,
@@ -584,7 +577,7 @@ export function parseClaudeRateLimitHeaders(headers: Record<string, string>, now
label: "Claude 7 Day", label: "Claude 7 Day",
windowId: "7d", windowId: "7d",
windowLabel: "7 Day", windowLabel: "7 Day",
durationMs: SEVEN_DAYS_MS, durationMs: WEEK_MS,
bucket: sevenDay, bucket: sevenDay,
provider: "anthropic", provider: "anthropic",
shared: true, shared: true,
@@ -594,7 +587,7 @@ export function parseClaudeRateLimitHeaders(headers: Record<string, string>, now
label: "Claude 7 Day (Fable)", label: "Claude 7 Day (Fable)",
windowId: "7d", windowId: "7d",
windowLabel: "7 Day", windowLabel: "7 Day",
durationMs: SEVEN_DAYS_MS, durationMs: WEEK_MS,
bucket: modelScopedSevenDay, bucket: modelScopedSevenDay,
provider: "anthropic", provider: "anthropic",
tier: "fable", tier: "fable",
@@ -638,7 +631,7 @@ async function fetchClaudeUsage(params: UsageFetchParams, ctx: UsageFetchContext
label: "Claude 5 Hour", label: "Claude 5 Hour",
windowId: "5h", windowId: "5h",
windowLabel: "5 Hour", windowLabel: "5 Hour",
durationMs: FIVE_HOURS_MS, durationMs: 5 * HOUR_MS,
bucket: fiveHour, bucket: fiveHour,
provider: "anthropic", provider: "anthropic",
shared: true, shared: true,
@@ -648,7 +641,7 @@ async function fetchClaudeUsage(params: UsageFetchParams, ctx: UsageFetchContext
label: "Claude 7 Day", label: "Claude 7 Day",
windowId: "7d", windowId: "7d",
windowLabel: "7 Day", windowLabel: "7 Day",
durationMs: SEVEN_DAYS_MS, durationMs: WEEK_MS,
bucket: sevenDay, bucket: sevenDay,
provider: "anthropic", provider: "anthropic",
shared: true, shared: true,
@@ -658,7 +651,7 @@ async function fetchClaudeUsage(params: UsageFetchParams, ctx: UsageFetchContext
label: "Claude 7 Day (Opus)", label: "Claude 7 Day (Opus)",
windowId: "7d", windowId: "7d",
windowLabel: "7 Day", windowLabel: "7 Day",
durationMs: SEVEN_DAYS_MS, durationMs: WEEK_MS,
bucket: sevenDayOpus, bucket: sevenDayOpus,
provider: "anthropic", provider: "anthropic",
tier: "opus", tier: "opus",
@@ -668,7 +661,7 @@ async function fetchClaudeUsage(params: UsageFetchParams, ctx: UsageFetchContext
label: "Claude 7 Day (Sonnet)", label: "Claude 7 Day (Sonnet)",
windowId: "7d", windowId: "7d",
windowLabel: "7 Day", windowLabel: "7 Day",
durationMs: SEVEN_DAYS_MS, durationMs: WEEK_MS,
bucket: sevenDaySonnet, bucket: sevenDaySonnet,
provider: "anthropic", provider: "anthropic",
tier: "sonnet", tier: "sonnet",
@@ -774,7 +767,7 @@ function rankingUsedFraction(limit: UsageLimit): number {
function rankingDrainRate(limit: UsageLimit, nowMs: number): number { function rankingDrainRate(limit: UsageLimit, nowMs: number): number {
const usedFraction = rankingUsedFraction(limit); const usedFraction = rankingUsedFraction(limit);
const durationMs = limit.window?.durationMs ?? SEVEN_DAYS_MS; const durationMs = limit.window?.durationMs ?? WEEK_MS;
if (!Number.isFinite(durationMs) || durationMs <= 0) return usedFraction; if (!Number.isFinite(durationMs) || durationMs <= 0) return usedFraction;
const resetAt = limit.window?.resetsAt; const resetAt = limit.window?.resetsAt;
if (typeof resetAt !== "number" || !Number.isFinite(resetAt)) return usedFraction; if (typeof resetAt !== "number" || !Number.isFinite(resetAt)) return usedFraction;
+6 -18
View File
@@ -1,3 +1,4 @@
import { toNumber } from "@oh-my-pi/pi-catalog/utils";
import { extractCursorAccessTokenUserId } from "../registry/oauth/cursor"; import { extractCursorAccessTokenUserId } from "../registry/oauth/cursor";
import type { import type {
UsageAmount, UsageAmount,
@@ -6,21 +7,15 @@ import type {
UsageLimit, UsageLimit,
UsageProvider, UsageProvider,
UsageReport, UsageReport,
UsageStatus,
UsageWindow, UsageWindow,
} from "../usage"; } from "../usage";
import { toNumber } from "./shared"; import { isRecord } from "../utils";
import { parseIsoTimestamp, usageStatus } from "./shared";
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function parseTimestamp(value: unknown): number | undefined { function parseTimestamp(value: unknown): number | undefined {
const numeric = toNumber(value); const numeric = toNumber(value);
if (numeric !== undefined) return numeric < 1_000_000_000_000 ? numeric * 1000 : numeric; if (numeric !== undefined) return numeric < 1_000_000_000_000 ? numeric * 1000 : numeric;
if (typeof value !== "string" || !value.trim()) return undefined; return parseIsoTimestamp(value);
const parsed = Date.parse(value);
return Number.isFinite(parsed) ? parsed : undefined;
} }
const DEFAULT_CURSOR_BASE_URL = "https://api2.cursor.sh"; const DEFAULT_CURSOR_BASE_URL = "https://api2.cursor.sh";
@@ -78,13 +73,6 @@ function deriveResetsAt(payload: Record<string, unknown>): number | undefined {
return undefined; return undefined;
} }
function resolveCursorStatus(usedFraction: number | undefined): UsageStatus {
if (usedFraction === undefined) return "unknown";
if (usedFraction >= 1) return "exhausted";
if (usedFraction >= 0.9) return "warning";
return "ok";
}
export function parseCursorIndividualUsage(payload: unknown, fetchedAt = Date.now()): UsageReport | null { export function parseCursorIndividualUsage(payload: unknown, fetchedAt = Date.now()): UsageReport | null {
if (!isRecord(payload) || !isRecord(payload.individualUsage) || !isRecord(payload.individualUsage.overall)) { if (!isRecord(payload) || !isRecord(payload.individualUsage) || !isRecord(payload.individualUsage.overall)) {
return null; return null;
@@ -140,7 +128,7 @@ export function parseCursorIndividualUsage(payload: unknown, fetchedAt = Date.no
}, },
window, window,
amount, amount,
...(amount.usedFraction !== undefined ? { status: resolveCursorStatus(amount.usedFraction) } : {}), ...(amount.usedFraction !== undefined ? { status: usageStatus(amount.usedFraction) } : {}),
}; };
return { return {
provider: "cursor", provider: "cursor",
@@ -197,7 +185,7 @@ export function parseCursorUsage(payload: unknown, fetchedAt = Date.now()): Usag
unit, unit,
}; };
const status = resolveCursorStatus(amount.usedFraction); const status = usageStatus(amount.usedFraction);
limits.push({ limits.push({
id: limitId, id: limitId,
+3 -2
View File
@@ -8,6 +8,7 @@ import type {
UsageReport, UsageReport,
UsageWindow, UsageWindow,
} from "../usage"; } from "../usage";
import { parseIsoTimestamp } from "./shared";
// (Refresh is the sole responsibility of AuthStorage; no provider-direct refresh here.) // (Refresh is the sole responsibility of AuthStorage; no provider-direct refresh here.)
@@ -79,8 +80,8 @@ function parseWindow(resetTime: string | undefined): UsageWindow {
label: "Quota window", label: "Quota window",
}; };
} }
const resetsAt = Date.parse(resetTime); const resetsAt = parseIsoTimestamp(resetTime);
if (Number.isNaN(resetsAt)) { if (resetsAt === undefined) {
return { return {
id: "quota", id: "quota",
label: "Quota window", label: "Quota window",
+3 -2
View File
@@ -18,6 +18,7 @@ import type {
UsageWindow, UsageWindow,
} from "../usage"; } from "../usage";
import { isRecord } from "../utils"; import { isRecord } from "../utils";
import { parseIsoTimestamp } from "./shared";
type CopilotQuotaDetail = { type CopilotQuotaDetail = {
entitlement: number; entitlement: number;
@@ -74,8 +75,8 @@ function resolveGitHubApiBaseUrl(params: UsageFetchParams): string {
function buildWindow(resetDate: string | undefined): UsageWindow | undefined { function buildWindow(resetDate: string | undefined): UsageWindow | undefined {
if (!resetDate) return undefined; if (!resetDate) return undefined;
const resetAt = Date.parse(resetDate); const resetAt = parseIsoTimestamp(resetDate);
if (!Number.isFinite(resetAt)) return undefined; if (resetAt === undefined) return undefined;
return { return {
id: "monthly", id: "monthly",
label: "Monthly", label: "Monthly",
+10 -17
View File
@@ -12,6 +12,7 @@ import type {
UsageStatus, UsageStatus,
UsageWindow, UsageWindow,
} from "../usage"; } from "../usage";
import { DAY_MS, parseIsoTimestamp, WEEK_MS } from "./shared";
// (Refresh is the sole responsibility of AuthStorage; no provider-direct refresh here.) // (Refresh is the sole responsibility of AuthStorage; no provider-direct refresh here.)
@@ -47,9 +48,6 @@ interface AntigravityUsageResponse {
const DEFAULT_ENDPOINT = "https://daily-cloudcode-pa.googleapis.com"; const DEFAULT_ENDPOINT = "https://daily-cloudcode-pa.googleapis.com";
const FETCH_AVAILABLE_MODELS_PATH = "/v1internal:fetchAvailableModels"; const FETCH_AVAILABLE_MODELS_PATH = "/v1internal:fetchAvailableModels";
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
const ONE_WEEK_MS = 7 * ONE_DAY_MS;
interface AntigravityWindowDescriptor { interface AntigravityWindowDescriptor {
id: string; id: string;
label: string; label: string;
@@ -59,25 +57,20 @@ interface AntigravityWindowDescriptor {
function classifyWindow(id: string | undefined, label: string | undefined): AntigravityWindowDescriptor | undefined { function classifyWindow(id: string | undefined, label: string | undefined): AntigravityWindowDescriptor | undefined {
const source = `${id ?? ""} ${label ?? ""}`.toLowerCase(); const source = `${id ?? ""} ${label ?? ""}`.toLowerCase();
if (source.includes("week") || source.includes("7d") || /7[\s_-]*day/.test(source)) { if (source.includes("week") || source.includes("7d") || /7[\s_-]*day/.test(source)) {
return { id: "weekly", label: "Weekly", durationMs: ONE_WEEK_MS }; return { id: "weekly", label: "Weekly", durationMs: WEEK_MS };
} }
if (source.includes("day") || source.includes("daily") || source.includes("24h")) { if (source.includes("day") || source.includes("daily") || source.includes("24h")) {
return { id: "daily", label: "Daily", durationMs: ONE_DAY_MS }; return { id: "daily", label: "Daily", durationMs: DAY_MS };
} }
if (id || label) return { id: id ?? label ?? "default", label: label ?? id ?? "Default" }; if (id || label) return { id: id ?? label ?? "default", label: label ?? id ?? "Default" };
return undefined; return undefined;
} }
function parseResetTime(info: AntigravityQuotaInfo): number | undefined {
const resetAt = info.resetTime ? Date.parse(info.resetTime) : undefined;
return resetAt !== undefined && Number.isFinite(resetAt) ? resetAt : undefined;
}
function inferWindowFromReset(resetAt: number | undefined, nowMs: number): AntigravityWindowDescriptor { function inferWindowFromReset(resetAt: number | undefined, nowMs: number): AntigravityWindowDescriptor {
if (resetAt !== undefined && resetAt - nowMs > ONE_DAY_MS) { if (resetAt !== undefined && resetAt - nowMs > DAY_MS) {
return { id: "weekly", label: "Weekly", durationMs: ONE_WEEK_MS }; return { id: "weekly", label: "Weekly", durationMs: WEEK_MS };
} }
return { id: "daily", label: "Daily", durationMs: ONE_DAY_MS }; return { id: "daily", label: "Daily", durationMs: DAY_MS };
} }
function quotaInferenceKey(info: AntigravityQuotaInfo): string { function quotaInferenceKey(info: AntigravityQuotaInfo): string {
@@ -98,7 +91,7 @@ function inferWindowDescriptors(
continue; continue;
} }
const group = groups.get(quotaInferenceKey(info)) ?? []; const group = groups.get(quotaInferenceKey(info)) ?? [];
group.push({ info, resetAt: parseResetTime(info) }); group.push({ info, resetAt: parseIsoTimestamp(info.resetTime) });
groups.set(quotaInferenceKey(info), group); groups.set(quotaInferenceKey(info), group);
} }
@@ -110,7 +103,7 @@ function inferWindowDescriptors(
for (const entry of group) { for (const entry of group) {
const descriptor = const descriptor =
latestReset !== undefined && entry.resetAt === latestReset latestReset !== undefined && entry.resetAt === latestReset
? { id: "weekly", label: "Weekly", durationMs: ONE_WEEK_MS } ? { id: "weekly", label: "Weekly", durationMs: WEEK_MS }
: inferWindowFromReset(entry.resetAt, nowMs); : inferWindowFromReset(entry.resetAt, nowMs);
descriptors.set(entry.info, descriptor); descriptors.set(entry.info, descriptor);
} }
@@ -149,7 +142,7 @@ function parseWindow(
info: AntigravityQuotaInfo, info: AntigravityQuotaInfo,
descriptor: AntigravityWindowDescriptor | undefined, descriptor: AntigravityWindowDescriptor | undefined,
): UsageWindow | undefined { ): UsageWindow | undefined {
const resetAt = parseResetTime(info); const resetAt = parseIsoTimestamp(info.resetTime);
const hasResetAt = resetAt !== undefined; const hasResetAt = resetAt !== undefined;
if (!descriptor && !hasResetAt) return undefined; if (!descriptor && !hasResetAt) return undefined;
return { return {
@@ -493,5 +486,5 @@ export const antigravityRankingStrategy: CredentialRankingStrategy = {
// Antigravity windows carry `durationMs` when the response identifies them // Antigravity windows carry `durationMs` when the response identifies them
// as daily/weekly. Fall back to daily for legacy unlabelled quotaInfo // as daily/weekly. Fall back to daily for legacy unlabelled quotaInfo
// entries from `daily-cloudcode-pa.googleapis.com`. // entries from `daily-cloudcode-pa.googleapis.com`.
windowDefaults: { primaryMs: ONE_DAY_MS, secondaryMs: ONE_DAY_MS }, windowDefaults: { primaryMs: DAY_MS, secondaryMs: DAY_MS },
}; };
+6 -12
View File
@@ -1,3 +1,4 @@
import { toNumber } from "@oh-my-pi/pi-catalog/utils";
import { $env } from "@oh-my-pi/pi-utils"; import { $env } from "@oh-my-pi/pi-utils";
import { getKimiCommonHeaders } from "../registry/oauth/kimi"; import { getKimiCommonHeaders } from "../registry/oauth/kimi";
import type { import type {
@@ -7,12 +8,12 @@ import type {
UsageLimit, UsageLimit,
UsageProvider, UsageProvider,
UsageReport, UsageReport,
UsageStatus,
UsageWindow, UsageWindow,
} from "../usage"; } from "../usage";
import { isRecord } from "../utils"; import { isRecord } from "../utils";
import { parseIsoTimestamp, usageStatus } from "./shared";
// (Refresh is the sole responsibility of AuthStorage; no provider-direct refresh here.) // (Refresh is the sole responsibility of AuthStorage; no provider-direct refresh here.)
import { toNumber } from "./shared";
const DEFAULT_BASE_URL = "https://api.kimi.com/coding/v1"; const DEFAULT_BASE_URL = "https://api.kimi.com/coding/v1";
const USAGE_PATH = "usages"; const USAGE_PATH = "usages";
@@ -47,8 +48,8 @@ function parseResetTime(data: Record<string, unknown>, nowMs: number): number |
for (const key of timeKeys) { for (const key of timeKeys) {
const value = data[key]; const value = data[key];
if (typeof value === "string" && value.trim()) { if (typeof value === "string" && value.trim()) {
const parsed = Date.parse(value); const parsed = parseIsoTimestamp(value);
if (Number.isFinite(parsed)) return parsed; if (parsed !== undefined) return parsed;
} }
if (typeof value === "number" && Number.isFinite(value)) { if (typeof value === "number" && Number.isFinite(value)) {
return value > 1_000_000_000_000 ? value : value * 1000; return value > 1_000_000_000_000 ? value : value * 1000;
@@ -136,13 +137,6 @@ function buildUsageAmount(row: KimiUsageRow): UsageAmount {
return amount; return amount;
} }
function buildUsageStatus(amount: UsageAmount): UsageStatus {
if (amount.usedFraction === undefined) return "unknown";
if (amount.usedFraction >= 1) return "exhausted";
if (amount.usedFraction >= 0.9) return "warning";
return "ok";
}
function toUsageLimit(row: KimiUsageRow, provider: string, index: number, accountId?: string): UsageLimit { function toUsageLimit(row: KimiUsageRow, provider: string, index: number, accountId?: string): UsageLimit {
// Kimi puts `resetTime` on the limit `detail`, not on `window`, so a // Kimi puts `resetTime` on the limit `detail`, not on `window`, so a
// window built from `duration`/`timeUnit` alone carries no resetsAt. // window built from `duration`/`timeUnit` alone carries no resetsAt.
@@ -172,7 +166,7 @@ function toUsageLimit(row: KimiUsageRow, provider: string, index: number, accoun
}, },
window, window,
amount, amount,
status: buildUsageStatus(amount), status: usageStatus(amount.usedFraction),
}; };
} }
+7 -27
View File
@@ -1,18 +1,11 @@
import type { import { toNumber } from "@oh-my-pi/pi-catalog/utils";
UsageFetchContext, import type { UsageFetchContext, UsageFetchParams, UsageLimit, UsageProvider, UsageReport } from "../usage";
UsageFetchParams,
UsageLimit,
UsageProvider,
UsageReport,
UsageStatus,
} from "../usage";
import { isRecord } from "../utils"; import { isRecord } from "../utils";
import { toNumber } from "./shared"; import { HOUR_MS, parsePositiveTimestamp, usageStatus } from "./shared";
const INTL_PROVIDER = "minimax-code"; const INTL_PROVIDER = "minimax-code";
const INTL_BASE_URL = "https://api.minimax.io"; const INTL_BASE_URL = "https://api.minimax.io";
const REMAINS_PATH = "/v1/token_plan/remains"; const REMAINS_PATH = "/v1/token_plan/remains";
const HOUR_MS = 60 * 60 * 1000;
/** `current_*_status` enum reported per window: 1 normal, 2 exhausted, 3 unlimited. */ /** `current_*_status` enum reported per window: 1 normal, 2 exhausted, 3 unlimited. */
const STATUS_EXHAUSTED = 2; const STATUS_EXHAUSTED = 2;
const STATUS_UNLIMITED = 3; const STATUS_UNLIMITED = 3;
@@ -42,13 +35,6 @@ interface TokenPlanBucket {
weeklyStatus?: number; weeklyStatus?: number;
} }
/** MiniMax reports epoch milliseconds; tolerate seconds in case a deployment differs. */
function parseTimestamp(value: unknown): number | undefined {
const parsed = toNumber(value);
if (parsed === undefined || parsed <= 0) return undefined;
return parsed < 1_000_000_000_000 ? parsed * 1000 : parsed;
}
/** `current_*_remaining_percent` is 0..100 remaining; usage fractions are 0..1 used. */ /** `current_*_remaining_percent` is 0..100 remaining; usage fractions are 0..1 used. */
function usedFractionFromRemainingPercent(value: unknown): number | undefined { function usedFractionFromRemainingPercent(value: unknown): number | undefined {
const parsed = toNumber(value); const parsed = toNumber(value);
@@ -57,26 +43,20 @@ function usedFractionFromRemainingPercent(value: unknown): number | undefined {
return Math.min(1, Math.max(0, (100 - parsed) / 100)); return Math.min(1, Math.max(0, (100 - parsed) / 100));
} }
function usageStatus(usedFraction: number): UsageStatus {
if (usedFraction >= 1) return "exhausted";
if (usedFraction >= 0.9) return "warning";
return "ok";
}
function parseBucket(value: unknown): TokenPlanBucket | null { function parseBucket(value: unknown): TokenPlanBucket | null {
if (!isRecord(value)) return null; if (!isRecord(value)) return null;
const modelName = typeof value.model_name === "string" ? value.model_name.trim() : ""; const modelName = typeof value.model_name === "string" ? value.model_name.trim() : "";
if (!modelName) return null; if (!modelName) return null;
return { return {
modelName, modelName,
intervalStart: parseTimestamp(value.start_time), intervalStart: parsePositiveTimestamp(value.start_time),
intervalEnd: parseTimestamp(value.end_time), intervalEnd: parsePositiveTimestamp(value.end_time),
intervalRemainingPercent: toNumber(value.current_interval_remaining_percent), intervalRemainingPercent: toNumber(value.current_interval_remaining_percent),
intervalTotalCount: toNumber(value.current_interval_total_count), intervalTotalCount: toNumber(value.current_interval_total_count),
intervalUsageCount: toNumber(value.current_interval_usage_count), intervalUsageCount: toNumber(value.current_interval_usage_count),
intervalStatus: toNumber(value.current_interval_status), intervalStatus: toNumber(value.current_interval_status),
weeklyStart: parseTimestamp(value.weekly_start_time), weeklyStart: parsePositiveTimestamp(value.weekly_start_time),
weeklyEnd: parseTimestamp(value.weekly_end_time), weeklyEnd: parsePositiveTimestamp(value.weekly_end_time),
weeklyRemainingPercent: toNumber(value.current_weekly_remaining_percent), weeklyRemainingPercent: toNumber(value.current_weekly_remaining_percent),
weeklyTotalCount: toNumber(value.current_weekly_total_count), weeklyTotalCount: toNumber(value.current_weekly_total_count),
weeklyUsageCount: toNumber(value.current_weekly_usage_count), weeklyUsageCount: toNumber(value.current_weekly_usage_count),
+1 -1
View File
@@ -18,10 +18,10 @@
* (the `/usage reset` command + auto-redeem) and any out-of-band tooling can * (the `/usage reset` command + auto-redeem) and any out-of-band tooling can
* share one wire contract. * share one wire contract.
*/ */
import { toNumber } from "@oh-my-pi/pi-catalog/utils";
import type { FetchImpl } from "../types"; import type { FetchImpl } from "../types";
import { isRecord } from "../utils"; import { isRecord } from "../utils";
import { normalizeCodexBaseUrl } from "./openai-codex-base-url"; import { normalizeCodexBaseUrl } from "./openai-codex-base-url";
import { toNumber } from "./shared";
const RESET_CREDITS_PATH = "wham/rate-limit-reset-credits"; const RESET_CREDITS_PATH = "wham/rate-limit-reset-credits";
const RESET_CREDITS_CONSUME_PATH = "wham/rate-limit-reset-credits/consume"; const RESET_CREDITS_CONSUME_PATH = "wham/rate-limit-reset-credits/consume";
+3 -4
View File
@@ -1,4 +1,5 @@
import { Buffer } from "node:buffer"; import { Buffer } from "node:buffer";
import { toNumber } from "@oh-my-pi/pi-catalog/utils";
import type { import type {
CredentialRankingContext, CredentialRankingContext,
CredentialRankingStrategy, CredentialRankingStrategy,
@@ -14,7 +15,7 @@ import type {
import { isRecord } from "../utils"; import { isRecord } from "../utils";
import { normalizeCodexBaseUrl } from "./openai-codex-base-url"; import { normalizeCodexBaseUrl } from "./openai-codex-base-url";
import { listCodexResetCredits } from "./openai-codex-reset"; import { listCodexResetCredits } from "./openai-codex-reset";
import { toNumber } from "./shared"; import { HOUR_MS } from "./shared";
const CODEX_USAGE_PATH = "wham/usage"; const CODEX_USAGE_PATH = "wham/usage";
const JWT_AUTH_CLAIM = "https://api.openai.com/auth"; const JWT_AUTH_CLAIM = "https://api.openai.com/auth";
@@ -558,8 +559,6 @@ export const openaiCodexUsageProvider: UsageProvider = {
}, },
}; };
const FIVE_HOUR_MS = 5 * 60 * 60 * 1000;
// A Codex request gates only on the chat windows it actually consumes. A // A Codex request gates only on the chat windows it actually consumes. A
// "-spark" model spends the separate Spark meter; every other Codex model spends // "-spark" model spends the separate Spark meter; every other Codex model spends
// the 5h/weekly chat windows. Scoping the gating set this way keeps an exhausted // the 5h/weekly chat windows. Scoping the gating set this way keeps an exhausted
@@ -619,7 +618,7 @@ export const codexRankingStrategy: CredentialRankingStrategy = {
windowId === "5h" || windowId === "5h" ||
(typeof durationMs === "number" && (typeof durationMs === "number" &&
Number.isFinite(durationMs) && Number.isFinite(durationMs) &&
Math.abs(durationMs - FIVE_HOUR_MS) <= 60_000); Math.abs(durationMs - 5 * HOUR_MS) <= 60_000);
if (!isFiveHourWindow) return false; if (!isFiveHourWindow) return false;
const usedFraction = primary.amount.usedFraction; const usedFraction = primary.amount.usedFraction;
return typeof usedFraction === "number" && Number.isFinite(usedFraction) && usedFraction === 0; return typeof usedFraction === "number" && Number.isFinite(usedFraction) && usedFraction === 0;
+1 -2
View File
@@ -1,8 +1,7 @@
import type { UsageCostHistoryEntry, UsageLimit, UsageProvider, UsageWindow } from "../usage"; import type { UsageCostHistoryEntry, UsageLimit, UsageProvider, UsageWindow } from "../usage";
import { DAY_MS, HOUR_MS } from "./shared";
const OPENCODE_GO_PROVIDER = "opencode-go"; const OPENCODE_GO_PROVIDER = "opencode-go";
const HOUR_MS = 60 * 60 * 1000;
const DAY_MS = 24 * HOUR_MS;
const OPENCODE_GO_LIMITS = [ const OPENCODE_GO_LIMITS = [
{ id: "rolling-5h", label: "5 Hour", durationMs: 5 * HOUR_MS, limitUsd: 12 }, { id: "rolling-5h", label: "5 Hour", durationMs: 5 * HOUR_MS, limitUsd: 12 },
{ id: "weekly", label: "Weekly", durationMs: 7 * DAY_MS, limitUsd: 30 }, { id: "weekly", label: "Weekly", durationMs: 7 * DAY_MS, limitUsd: 30 },
+33 -10
View File
@@ -1,10 +1,33 @@
export const toNumber = (value: unknown): number | undefined => { import { toNumber } from "@oh-my-pi/pi-catalog/utils";
if (typeof value === "number" && Number.isFinite(value)) return value; import type { UsageStatus } from "../usage";
if (typeof value === "string") {
const trimmed = value.trim(); /** Milliseconds in one hour. */
if (!trimmed) return undefined; export const HOUR_MS = 60 * 60 * 1000;
const parsed = Number(trimmed);
if (Number.isFinite(parsed)) return parsed; /** Milliseconds in one day. */
} export const DAY_MS = 24 * HOUR_MS;
return undefined;
}; /** Milliseconds in one seven-day week. */
export const WEEK_MS = 7 * DAY_MS;
/** Parses a finite positive epoch timestamp, tolerating seconds or milliseconds. */
export function parsePositiveTimestamp(value: unknown): number | undefined {
const parsed = toNumber(value);
if (parsed === undefined || parsed <= 0) return undefined;
return parsed < 1_000_000_000_000 ? parsed * 1000 : parsed;
}
/** Parses an ISO timestamp into epoch milliseconds. */
export function parseIsoTimestamp(value: unknown): number | undefined {
if (typeof value !== "string" || !value) return undefined;
const parsed = Date.parse(value);
return Number.isFinite(parsed) ? parsed : undefined;
}
/** Maps a used fraction to the standard quota status thresholds. */
export function usageStatus(usedFraction: number | undefined): UsageStatus {
if (usedFraction === undefined) return "unknown";
if (usedFraction >= 1) return "exhausted";
if (usedFraction >= 0.9) return "warning";
return "ok";
}
+4 -11
View File
@@ -9,10 +9,9 @@ import type {
UsageStatus, UsageStatus,
UsageWindow, UsageWindow,
} from "../usage"; } from "../usage";
import { HOUR_MS, parseIsoTimestamp, WEEK_MS } from "./shared";
const QUOTAS_URL = "https://api.synthetic.new/v2/quotas"; const QUOTAS_URL = "https://api.synthetic.new/v2/quotas";
const FIVE_HOUR_MS = 5 * 60 * 60 * 1000;
const WEEK_MS = 7 * 24 * 60 * 60 * 1000;
function parseDollarAmount(value: unknown): number | undefined { function parseDollarAmount(value: unknown): number | undefined {
if (typeof value !== "string") return undefined; if (typeof value !== "string") return undefined;
@@ -21,12 +20,6 @@ function parseDollarAmount(value: unknown): number | undefined {
return Number.isFinite(parsed) ? parsed : undefined; return Number.isFinite(parsed) ? parsed : undefined;
} }
function parseIsoMs(value: unknown): number | undefined {
if (typeof value !== "string" || !value) return undefined;
const ms = Date.parse(value);
return Number.isFinite(ms) ? ms : undefined;
}
function buildUsageAmount(args: { function buildUsageAmount(args: {
used: number | undefined; used: number | undefined;
limit: number | undefined; limit: number | undefined;
@@ -61,7 +54,7 @@ function parseRollingFiveHourLimit(raw: unknown, provider: UsageFetchParams["pro
const remaining = typeof raw.remaining === "number" ? raw.remaining : undefined; const remaining = typeof raw.remaining === "number" ? raw.remaining : undefined;
const max = typeof raw.max === "number" ? raw.max : undefined; const max = typeof raw.max === "number" ? raw.max : undefined;
const limited = raw.limited === true; const limited = raw.limited === true;
const nextTickAt = parseIsoMs(raw.nextTickAt); const nextTickAt = parseIsoTimestamp(raw.nextTickAt);
const tickPercent = typeof raw.tickPercent === "number" ? raw.tickPercent : undefined; const tickPercent = typeof raw.tickPercent === "number" ? raw.tickPercent : undefined;
if (remaining === undefined && max === undefined) return null; if (remaining === undefined && max === undefined) return null;
@@ -71,7 +64,7 @@ function parseRollingFiveHourLimit(raw: unknown, provider: UsageFetchParams["pro
const window: UsageWindow = { const window: UsageWindow = {
id: "5h", id: "5h",
label: regenPercent !== undefined ? `5h · regen ${regenPercent}%/tick` : "5h", label: regenPercent !== undefined ? `5h · regen ${regenPercent}%/tick` : "5h",
durationMs: FIVE_HOUR_MS, durationMs: 5 * HOUR_MS,
...(nextTickAt !== undefined ? { resetsAt: nextTickAt, resetLabel: "tick" } : {}), ...(nextTickAt !== undefined ? { resetsAt: nextTickAt, resetLabel: "tick" } : {}),
}; };
const amount = buildUsageAmount({ const amount = buildUsageAmount({
@@ -97,7 +90,7 @@ function parseWeeklyTokenLimit(raw: unknown, provider: UsageFetchParams["provide
const remainingCredits = parseDollarAmount(raw.remainingCredits); const remainingCredits = parseDollarAmount(raw.remainingCredits);
const maxCredits = parseDollarAmount(raw.maxCredits); const maxCredits = parseDollarAmount(raw.maxCredits);
const percentRemaining = typeof raw.percentRemaining === "number" ? raw.percentRemaining : undefined; const percentRemaining = typeof raw.percentRemaining === "number" ? raw.percentRemaining : undefined;
const nextRegenAt = parseIsoMs(raw.nextRegenAt); const nextRegenAt = parseIsoTimestamp(raw.nextRegenAt);
if (remainingCredits === undefined && maxCredits === undefined) return null; if (remainingCredits === undefined && maxCredits === undefined) return null;
+2 -2
View File
@@ -10,11 +10,11 @@ import type {
UsageWindow, UsageWindow,
} from "../usage"; } from "../usage";
import { isRecord } from "../utils"; import { isRecord } from "../utils";
import { HOUR_MS } from "./shared";
const UMANS_PROVIDER = "umans"; const UMANS_PROVIDER = "umans";
const DEFAULT_ENDPOINT = "https://api.code.umans.ai"; const DEFAULT_ENDPOINT = "https://api.code.umans.ai";
const USAGE_PATH = "/v1/usage"; const USAGE_PATH = "/v1/usage";
const FIVE_HOURS_MS = 5 * 60 * 60 * 1000;
/** Umans `GET /v1/usage` response (subset; extras ignored). */ /** Umans `GET /v1/usage` response (subset; extras ignored). */
interface UmansUsagePayload { interface UmansUsagePayload {
@@ -89,7 +89,7 @@ function buildRequestsLimit(payload: UmansUsagePayload, provider: string): Usage
const window: UsageWindow = { const window: UsageWindow = {
id: "5h", id: "5h",
label: "rolling 5h", label: "rolling 5h",
durationMs: windowSeconds ? windowSeconds * 1000 : FIVE_HOURS_MS, durationMs: windowSeconds ? windowSeconds * 1000 : 5 * HOUR_MS,
}; };
return { return {
id: "umans:requests", id: "umans:requests",
+13 -26
View File
@@ -9,6 +9,7 @@
* product and must never be sent here. * product and must never be sent here.
*/ */
import { toNumber } from "@oh-my-pi/pi-catalog/utils";
import { import {
buildXAICliBillingUrl, buildXAICliBillingUrl,
extractXAIAccessTokenSubject, extractXAIAccessTokenSubject,
@@ -22,15 +23,12 @@ import type {
UsageLimit, UsageLimit,
UsageProvider, UsageProvider,
UsageReport, UsageReport,
UsageStatus,
UsageWindow, UsageWindow,
} from "../usage"; } from "../usage";
import { isRecord } from "../utils"; import { isRecord } from "../utils";
import { toNumber } from "./shared"; import { DAY_MS, parseIsoTimestamp, usageStatus, WEEK_MS } from "./shared";
const PROVIDER_ID = "xai-oauth"; const PROVIDER_ID = "xai-oauth";
const WEEK_MS = 7 * 24 * 60 * 60 * 1000;
const DAY_MS = 24 * 60 * 60 * 1000;
const BILLING_SOURCE = "cli-chat-proxy.grok.com/v1/billing"; const BILLING_SOURCE = "cli-chat-proxy.grok.com/v1/billing";
interface XaiBillingPeriod { interface XaiBillingPeriod {
@@ -71,11 +69,6 @@ interface XaiMonthlyBillingConfig {
type XaiBillingConfig = XaiWeeklyBillingConfig | XaiMonthlyBillingConfig; type XaiBillingConfig = XaiWeeklyBillingConfig | XaiMonthlyBillingConfig;
function parseIsoMs(value: string): number | undefined {
const parsed = Date.parse(value);
return Number.isFinite(parsed) ? parsed : undefined;
}
function parsePercent(value: unknown): number | undefined { function parsePercent(value: unknown): number | undefined {
const percent = toNumber(value); const percent = toNumber(value);
return percent !== undefined && percent >= 0 && percent <= 100 ? percent : undefined; return percent !== undefined && percent >= 0 && percent <= 100 ? percent : undefined;
@@ -99,12 +92,6 @@ function buildPercentAmount(usagePercent: number): UsageAmount {
}; };
} }
function buildUsageStatus(usedFraction: number): UsageStatus {
if (usedFraction >= 1) return "exhausted";
if (usedFraction >= 0.9) return "warning";
return "ok";
}
function slugifyProduct(product: string): string { function slugifyProduct(product: string): string {
return product return product
.trim() .trim()
@@ -118,13 +105,13 @@ function buildPeriodWindow(period: XaiBillingPeriod): UsageWindow {
id: "1w", id: "1w",
label: "Weekly", label: "Weekly",
durationMs: WEEK_MS, durationMs: WEEK_MS,
resetsAt: parseIsoMs(period.end), resetsAt: parseIsoTimestamp(period.end),
}; };
} }
function buildMonthlyWindow(periodStart: string, periodEnd: string): UsageWindow | undefined { function buildMonthlyWindow(periodStart: string, periodEnd: string): UsageWindow | undefined {
const startMs = parseIsoMs(periodStart); const startMs = parseIsoTimestamp(periodStart);
const endMs = parseIsoMs(periodEnd); const endMs = parseIsoTimestamp(periodEnd);
if (startMs === undefined || endMs === undefined || endMs <= startMs) return undefined; if (startMs === undefined || endMs === undefined || endMs <= startMs) return undefined;
// Real calendar months vary; use the observed period length from the API. // Real calendar months vary; use the observed period length from the API.
const durationMs = endMs - startMs; const durationMs = endMs - startMs;
@@ -140,8 +127,8 @@ function buildMonthlyWindow(periodStart: string, periodEnd: string): UsageWindow
function parseWeeklyBillingConfig(raw: Record<string, unknown>): XaiWeeklyBillingConfig | null { function parseWeeklyBillingConfig(raw: Record<string, unknown>): XaiWeeklyBillingConfig | null {
if (!isRecord(raw.currentPeriod)) return null; if (!isRecord(raw.currentPeriod)) return null;
const start = typeof raw.currentPeriod.start === "string" ? parseIsoMs(raw.currentPeriod.start) : undefined; const start = typeof raw.currentPeriod.start === "string" ? parseIsoTimestamp(raw.currentPeriod.start) : undefined;
const end = typeof raw.currentPeriod.end === "string" ? parseIsoMs(raw.currentPeriod.end) : undefined; const end = typeof raw.currentPeriod.end === "string" ? parseIsoTimestamp(raw.currentPeriod.end) : undefined;
const type = typeof raw.currentPeriod.type === "string" ? raw.currentPeriod.type : ""; const type = typeof raw.currentPeriod.type === "string" ? raw.currentPeriod.type : "";
// Keep recently-ended weekly windows so /usage still renders across period // Keep recently-ended weekly windows so /usage still renders across period
// rollover while the billing API is mid-refresh. Reject only inverted ranges // rollover while the billing API is mid-refresh. Reject only inverted ranges
@@ -182,8 +169,8 @@ function parseWeeklyBillingConfig(raw: Record<string, unknown>): XaiWeeklyBillin
function parseMonthlyBillingConfig(raw: Record<string, unknown>): XaiMonthlyBillingConfig | null { function parseMonthlyBillingConfig(raw: Record<string, unknown>): XaiMonthlyBillingConfig | null {
const periodStart = typeof raw.billingPeriodStart === "string" ? raw.billingPeriodStart : ""; const periodStart = typeof raw.billingPeriodStart === "string" ? raw.billingPeriodStart : "";
const periodEnd = typeof raw.billingPeriodEnd === "string" ? raw.billingPeriodEnd : ""; const periodEnd = typeof raw.billingPeriodEnd === "string" ? raw.billingPeriodEnd : "";
const startMs = parseIsoMs(periodStart); const startMs = parseIsoTimestamp(periodStart);
const endMs = parseIsoMs(periodEnd); const endMs = parseIsoTimestamp(periodEnd);
if (!periodStart || !periodEnd || startMs === undefined || endMs === undefined || endMs <= startMs) { if (!periodStart || !periodEnd || startMs === undefined || endMs === undefined || endMs <= startMs) {
return null; return null;
} }
@@ -227,7 +214,7 @@ function buildOnDemandLimit(
remainingFraction: 1 - usedFraction, remainingFraction: 1 - usedFraction,
unit: "unknown", unit: "unknown",
}, },
status: buildUsageStatus(usedFraction), status: usageStatus(usedFraction),
}; };
} }
@@ -248,7 +235,7 @@ function buildLimits(config: XaiBillingConfig, accountId: string | undefined): U
scope, scope,
window, window,
amount: overall, amount: overall,
status: buildUsageStatus(overall.usedFraction ?? 0), status: usageStatus(overall.usedFraction ?? 0),
}, },
]; ];
@@ -262,7 +249,7 @@ function buildLimits(config: XaiBillingConfig, accountId: string | undefined): U
scope, scope,
window, window,
amount, amount,
status: buildUsageStatus(amount.usedFraction ?? 0), status: usageStatus(amount.usedFraction ?? 0),
}); });
} }
const onDemand = buildOnDemandLimit(config.onDemandCap, config.onDemandUsed, accountId); const onDemand = buildOnDemandLimit(config.onDemandCap, config.onDemandUsed, accountId);
@@ -293,7 +280,7 @@ function buildLimits(config: XaiBillingConfig, accountId: string | undefined): U
// xAI does not label the unit; amounts match the dashboard quota points. // xAI does not label the unit; amounts match the dashboard quota points.
unit: "unknown", unit: "unknown",
}, },
status: buildUsageStatus(usedFraction), status: usageStatus(usedFraction),
}, },
]; ];
const onDemand = buildOnDemandLimit(config.onDemandCap, config.onDemandUsed, accountId); const onDemand = buildOnDemandLimit(config.onDemandCap, config.onDemandUsed, accountId);
+2 -5
View File
@@ -11,14 +11,11 @@ import type {
UsageWindow, UsageWindow,
} from "../usage"; } from "../usage";
import { isRecord } from "../utils"; import { isRecord } from "../utils";
import { DAY_MS, HOUR_MS, WEEK_MS } from "./shared";
const DEFAULT_ENDPOINT = "https://api.z.ai"; const DEFAULT_ENDPOINT = "https://api.z.ai";
const QUOTA_PATH = "/api/monitor/usage/quota/limit"; const QUOTA_PATH = "/api/monitor/usage/quota/limit";
const MODEL_USAGE_PATH = "/api/monitor/usage/model-usage"; const MODEL_USAGE_PATH = "/api/monitor/usage/model-usage";
const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000;
const HOUR_MS = 60 * 60 * 1000;
const DAY_MS = 24 * HOUR_MS;
const WEEK_MS = 7 * DAY_MS;
const MONTH_MS = 30 * DAY_MS; const MONTH_MS = 30 * DAY_MS;
interface ZaiUsageDetail { interface ZaiUsageDetail {
@@ -188,7 +185,7 @@ function requestQuotaLabel(parsed: ZaiUsageLimitItem): string {
} }
function buildModelUsageUrl(baseUrl: string, now: Date): string { function buildModelUsageUrl(baseUrl: string, now: Date): string {
const start = new Date(now.getTime() - SEVEN_DAYS_MS); const start = new Date(now.getTime() - WEEK_MS);
const startTime = formatDate(start); const startTime = formatDate(start);
const endTime = formatDate(now); const endTime = formatDate(now);
return `${baseUrl}${MODEL_USAGE_PATH}?startTime=${encodeURIComponent(startTime)}&endTime=${encodeURIComponent(endTime)}`; return `${baseUrl}${MODEL_USAGE_PATH}?startTime=${encodeURIComponent(startTime)}&endTime=${encodeURIComponent(endTime)}`;