122 lines
3.9 KiB
TypeScript
122 lines
3.9 KiB
TypeScript
import { api, type CodexAccount } from './api';
|
|
|
|
interface UsageWindow {
|
|
used_percent?: unknown;
|
|
usedPercent?: unknown;
|
|
reset_at?: unknown;
|
|
resetAt?: unknown;
|
|
reset_after_seconds?: unknown;
|
|
resetAfterSeconds?: unknown;
|
|
}
|
|
|
|
interface RateLimit {
|
|
primary_window?: UsageWindow | null;
|
|
primaryWindow?: UsageWindow | null;
|
|
secondary_window?: UsageWindow | null;
|
|
secondaryWindow?: UsageWindow | null;
|
|
}
|
|
|
|
function normalizeAuthIndex(value: unknown): string | null {
|
|
if (typeof value === 'number' && Number.isFinite(value)) return value.toString();
|
|
if (typeof value === 'string') return value.trim() || null;
|
|
return null;
|
|
}
|
|
|
|
function normalizeNumberValue(value: unknown): number | null {
|
|
if (typeof value === 'number' && Number.isFinite(value)) return value;
|
|
if (typeof value === 'string' && value.trim()) {
|
|
const parsed = Number(value.trim());
|
|
return Number.isFinite(parsed) ? parsed : null;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function parseCodexUsagePayload(payload: unknown): Record<string, unknown> | null {
|
|
if (typeof payload === 'string' && payload.trim()) {
|
|
try {
|
|
return JSON.parse(payload) as Record<string, unknown>;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
return payload !== null && typeof payload === 'object'
|
|
? (payload as Record<string, unknown>)
|
|
: null;
|
|
}
|
|
|
|
export interface CodexQuota {
|
|
planType: string | null;
|
|
windows: Array<{ id: string; label: string; remaining: number | null; resetAt: number | null }>;
|
|
}
|
|
|
|
function resetAt(window: UsageWindow): number | null {
|
|
const absolute = normalizeNumberValue(window.reset_at ?? window.resetAt);
|
|
if (absolute !== null) return absolute < 1e12 ? absolute * 1000 : absolute;
|
|
const offset = normalizeNumberValue(window.reset_after_seconds ?? window.resetAfterSeconds);
|
|
return offset === null ? null : Date.now() + offset * 1000;
|
|
}
|
|
|
|
function addRateLimit(
|
|
result: CodexQuota['windows'],
|
|
limit: RateLimit | null | undefined,
|
|
prefix: string,
|
|
labels: [string, string],
|
|
) {
|
|
const windows = [limit?.primary_window ?? limit?.primaryWindow, limit?.secondary_window ?? limit?.secondaryWindow];
|
|
windows.forEach((window, index) => {
|
|
if (!window) return;
|
|
const used = normalizeNumberValue(window.used_percent ?? window.usedPercent);
|
|
result.push({
|
|
id: `${prefix}-${index}`,
|
|
label: labels[index],
|
|
remaining: used === null ? null : Math.max(0, Math.min(100, 100 - used)),
|
|
resetAt: resetAt(window),
|
|
});
|
|
});
|
|
}
|
|
|
|
export async function fetchCodexQuota(
|
|
account: CodexAccount,
|
|
managementKey: string,
|
|
): Promise<CodexQuota> {
|
|
const authIndex = normalizeAuthIndex(account.auth_index ?? account.authIndex);
|
|
if (!authIndex) throw new Error('This account has no auth index, so quota cannot be queried.');
|
|
|
|
const response = await api.getCodexQuota(authIndex, managementKey);
|
|
const statusCode = Number(response.status_code || 0);
|
|
if (statusCode < 200 || statusCode >= 300) {
|
|
throw new Error(`Quota request failed with HTTP ${statusCode || 'unknown'}`);
|
|
}
|
|
|
|
const payload = parseCodexUsagePayload(response.body);
|
|
if (!payload) throw new Error('The quota response was empty or invalid.');
|
|
|
|
const source = payload;
|
|
const windows: CodexQuota['windows'] = [];
|
|
addRateLimit(
|
|
windows,
|
|
(source.rate_limit ?? source.rateLimit) as RateLimit | undefined,
|
|
'codex',
|
|
['5-hour limit', 'Weekly limit'],
|
|
);
|
|
addRateLimit(
|
|
windows,
|
|
(source.code_review_rate_limit ?? source.codeReviewRateLimit) as RateLimit | undefined,
|
|
'review',
|
|
['Code review 5-hour limit', 'Code review weekly limit'],
|
|
);
|
|
|
|
return {
|
|
planType:
|
|
typeof source.plan_type === 'string'
|
|
? source.plan_type
|
|
: typeof source.planType === 'string'
|
|
? source.planType
|
|
: typeof account.plan_type === 'string'
|
|
? account.plan_type
|
|
: typeof account.planType === 'string'
|
|
? account.planType
|
|
: null,
|
|
windows,
|
|
};
|
|
}
|