vibe-proxy/frontend/src/codexQuota.ts
2026-08-27 15:02:32 +02:00

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,
};
}