Big update
This commit is contained in:
parent
8b607dd700
commit
2e6afc460b
474 changed files with 934 additions and 86159 deletions
122
frontend/src/codexQuota.ts
Normal file
122
frontend/src/codexQuota.ts
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
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,
|
||||
};
|
||||
}
|
||||
Loading…
Reference in a new issue