mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-12 22:14:22 +00:00
fix(codex): align quota display to 5h and weekly windows
This commit is contained in:
1 parent
6c2083949f
commit
f8af5a8c3c
6 files changed
+335
-23
No files matched your search
@@ -78,6 +78,93 @@ function formatResetTimeISO(isoTime: string): string {
|
|||||||
return formatResetTime(seconds);
|
return formatResetTime(seconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CodexWindowKind =
|
||||||
|
| 'usage-5h'
|
||||||
|
| 'usage-weekly'
|
||||||
|
| 'code-review-5h'
|
||||||
|
| 'code-review-weekly'
|
||||||
|
| 'code-review'
|
||||||
|
| 'unknown';
|
||||||
|
|
||||||
|
function getCodexWindowKind(label: string): CodexWindowKind {
|
||||||
|
const lower = (label || '').toLowerCase();
|
||||||
|
const isCodeReview = lower.includes('code review') || lower.includes('code_review');
|
||||||
|
const isPrimary = lower.includes('primary');
|
||||||
|
const isSecondary = lower.includes('secondary');
|
||||||
|
|
||||||
|
if (isCodeReview) {
|
||||||
|
if (isPrimary) return 'code-review-5h';
|
||||||
|
if (isSecondary) return 'code-review-weekly';
|
||||||
|
return 'code-review';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPrimary) return 'usage-5h';
|
||||||
|
if (isSecondary) return 'usage-weekly';
|
||||||
|
return 'unknown';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCodexWindowDisplayLabel(label: string): string {
|
||||||
|
switch (getCodexWindowKind(label)) {
|
||||||
|
case 'usage-5h':
|
||||||
|
return '5h usage limit';
|
||||||
|
case 'usage-weekly':
|
||||||
|
return 'Weekly usage limit';
|
||||||
|
case 'code-review-5h':
|
||||||
|
return 'Code review (5h)';
|
||||||
|
case 'code-review-weekly':
|
||||||
|
return 'Code review (weekly)';
|
||||||
|
case 'code-review':
|
||||||
|
return 'Code review';
|
||||||
|
case 'unknown':
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCodexCoreUsageWindows(windows: CodexQuotaResult['windows']): {
|
||||||
|
fiveHourWindow: CodexQuotaResult['windows'][number] | null;
|
||||||
|
weeklyWindow: CodexQuotaResult['windows'][number] | null;
|
||||||
|
} {
|
||||||
|
let fiveHourWindow: CodexQuotaResult['windows'][number] | null = null;
|
||||||
|
let weeklyWindow: CodexQuotaResult['windows'][number] | null = null;
|
||||||
|
const nonCodeReviewWindows: CodexQuotaResult['windows'] = [];
|
||||||
|
|
||||||
|
for (const window of windows) {
|
||||||
|
const kind = getCodexWindowKind(window.label);
|
||||||
|
if (kind === 'usage-5h') {
|
||||||
|
if (!fiveHourWindow) fiveHourWindow = window;
|
||||||
|
nonCodeReviewWindows.push(window);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (kind === 'usage-weekly') {
|
||||||
|
if (!weeklyWindow) weeklyWindow = window;
|
||||||
|
nonCodeReviewWindows.push(window);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (kind === 'unknown') {
|
||||||
|
nonCodeReviewWindows.push(window);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((!fiveHourWindow || !weeklyWindow) && nonCodeReviewWindows.length > 0) {
|
||||||
|
const withReset = nonCodeReviewWindows
|
||||||
|
.filter((w) => typeof w.resetAfterSeconds === 'number' && w.resetAfterSeconds >= 0)
|
||||||
|
.sort((a, b) => (a.resetAfterSeconds || 0) - (b.resetAfterSeconds || 0));
|
||||||
|
|
||||||
|
if (!fiveHourWindow) {
|
||||||
|
fiveHourWindow = withReset[0] || nonCodeReviewWindows[0] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!weeklyWindow) {
|
||||||
|
weeklyWindow =
|
||||||
|
withReset.length > 1
|
||||||
|
? withReset[withReset.length - 1]
|
||||||
|
: nonCodeReviewWindows.find((w) => w !== fiveHourWindow) || null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { fiveHourWindow, weeklyWindow };
|
||||||
|
}
|
||||||
|
|
||||||
function displayAntigravityQuotaSection(
|
function displayAntigravityQuotaSection(
|
||||||
quotaResult: Awaited<ReturnType<typeof fetchAllProviderQuotas>>
|
quotaResult: Awaited<ReturnType<typeof fetchAllProviderQuotas>>
|
||||||
): void {
|
): void {
|
||||||
@@ -143,22 +230,32 @@ function displayCodexQuotaSection(results: { account: string; quota: CodexQuotaR
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { fiveHourWindow, weeklyWindow } = getCodexCoreUsageWindows(quota.windows);
|
||||||
|
const coreUsageWindows = [fiveHourWindow, weeklyWindow].filter(
|
||||||
|
(w, index, arr): w is NonNullable<typeof w> => !!w && arr.indexOf(w) === index
|
||||||
|
);
|
||||||
|
const statusWindows = coreUsageWindows.length > 0 ? coreUsageWindows : quota.windows;
|
||||||
|
|
||||||
const avgQuota =
|
const avgQuota =
|
||||||
quota.windows.length > 0
|
statusWindows.length > 0
|
||||||
? quota.windows.reduce((sum, w) => sum + w.remainingPercent, 0) / quota.windows.length
|
? statusWindows.reduce((sum, w) => sum + w.remainingPercent, 0) / statusWindows.length
|
||||||
: 0;
|
: 0;
|
||||||
const statusIcon = avgQuota > 50 ? ok('') : avgQuota > 10 ? warn('') : fail('');
|
const statusIcon = avgQuota > 50 ? ok('') : avgQuota > 10 ? warn('') : fail('');
|
||||||
const planBadge = quota.planType ? color(` [${quota.planType}]`, 'info') : '';
|
const planBadge = quota.planType ? color(` [${quota.planType}]`, 'info') : '';
|
||||||
|
|
||||||
console.log(` ${statusIcon}${account}${defaultMark}${planBadge}`);
|
console.log(` ${statusIcon}${account}${defaultMark}${planBadge}`);
|
||||||
|
|
||||||
for (const window of quota.windows) {
|
const orderedWindows = [fiveHourWindow, weeklyWindow, ...quota.windows].filter(
|
||||||
|
(w, index, arr): w is NonNullable<typeof w> => !!w && arr.indexOf(w) === index
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const window of orderedWindows) {
|
||||||
const bar = formatQuotaBar(window.remainingPercent);
|
const bar = formatQuotaBar(window.remainingPercent);
|
||||||
const resetLabel = window.resetAfterSeconds
|
const resetLabel = window.resetAfterSeconds
|
||||||
? dim(` Resets ${formatResetTime(window.resetAfterSeconds)}`)
|
? dim(` Resets ${formatResetTime(window.resetAfterSeconds)}`)
|
||||||
: '';
|
: '';
|
||||||
console.log(
|
console.log(
|
||||||
` ${window.label.padEnd(24)} ${bar} ${window.remainingPercent.toFixed(0)}%${resetLabel}`
|
` ${getCodexWindowDisplayLabel(window.label).padEnd(24)} ${bar} ${window.remainingPercent.toFixed(0)}%${resetLabel}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
console.log('');
|
console.log('');
|
||||||
|
|||||||
@@ -2,7 +2,13 @@
|
|||||||
* Account Card Component for Flow Visualization
|
* Account Card Component for Flow Visualization
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { cn, getProviderMinQuota, getProviderResetTime } from '@/lib/utils';
|
import {
|
||||||
|
cn,
|
||||||
|
getCodexQuotaBreakdown,
|
||||||
|
getProviderMinQuota,
|
||||||
|
getProviderResetTime,
|
||||||
|
isCodexQuotaResult,
|
||||||
|
} from '@/lib/utils';
|
||||||
import { PRIVACY_BLUR_CLASS } from '@/contexts/privacy-context';
|
import { PRIVACY_BLUR_CLASS } from '@/contexts/privacy-context';
|
||||||
import { GripVertical, Loader2, Pause, Play, KeyRound } from 'lucide-react';
|
import { GripVertical, Loader2, Pause, Play, KeyRound } from 'lucide-react';
|
||||||
import { useAccountQuota, QUOTA_SUPPORTED_PROVIDERS } from '@/hooks/use-cliproxy-stats';
|
import { useAccountQuota, QUOTA_SUPPORTED_PROVIDERS } from '@/hooks/use-cliproxy-stats';
|
||||||
@@ -96,6 +102,14 @@ export function AccountCard({
|
|||||||
// Use shared helper for provider-specific minimum quota
|
// Use shared helper for provider-specific minimum quota
|
||||||
const minQuota = getProviderMinQuota(account.provider, quota);
|
const minQuota = getProviderMinQuota(account.provider, quota);
|
||||||
const resetTime = getProviderResetTime(account.provider, quota);
|
const resetTime = getProviderResetTime(account.provider, quota);
|
||||||
|
const codexBreakdown =
|
||||||
|
account.provider === 'codex' && quota && isCodexQuotaResult(quota)
|
||||||
|
? getCodexQuotaBreakdown(quota.windows)
|
||||||
|
: null;
|
||||||
|
const codexQuotaRows = [
|
||||||
|
{ label: '5h', value: codexBreakdown?.fiveHourWindow?.remainingPercent ?? null },
|
||||||
|
{ label: 'Wk', value: codexBreakdown?.weeklyWindow?.remainingPercent ?? null },
|
||||||
|
].filter((row): row is { label: string; value: number } => row.value !== null);
|
||||||
|
|
||||||
// Tier badge (AGY only) - show P for Pro, U for Ultra
|
// Tier badge (AGY only) - show P for Pro, U for Ultra
|
||||||
const showTierBadge =
|
const showTierBadge =
|
||||||
@@ -229,6 +243,15 @@ export function AccountCard({
|
|||||||
{minQuota}%
|
{minQuota}%
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
{account.provider === 'codex' && codexQuotaRows.length > 0 && (
|
||||||
|
<div className="flex items-center justify-between text-[7px] text-muted-foreground/70">
|
||||||
|
{codexQuotaRows.map((row) => (
|
||||||
|
<span key={row.label}>
|
||||||
|
{row.label} {row.value}%
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="w-full bg-muted dark:bg-zinc-800/50 h-1 rounded-full overflow-hidden">
|
<div className="w-full bg-muted dark:bg-zinc-800/50 h-1 rounded-full overflow-hidden">
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|||||||
@@ -30,7 +30,13 @@ import {
|
|||||||
Check,
|
Check,
|
||||||
KeyRound,
|
KeyRound,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { cn, getProviderMinQuota, getProviderResetTime } from '@/lib/utils';
|
import {
|
||||||
|
cn,
|
||||||
|
getCodexQuotaBreakdown,
|
||||||
|
getProviderMinQuota,
|
||||||
|
getProviderResetTime,
|
||||||
|
isCodexQuotaResult,
|
||||||
|
} from '@/lib/utils';
|
||||||
import { PRIVACY_BLUR_CLASS } from '@/contexts/privacy-context';
|
import { PRIVACY_BLUR_CLASS } from '@/contexts/privacy-context';
|
||||||
import { useAccountQuota, useCliproxyStats } from '@/hooks/use-cliproxy-stats';
|
import { useAccountQuota, useCliproxyStats } from '@/hooks/use-cliproxy-stats';
|
||||||
import { QuotaTooltipContent } from '@/components/shared/quota-tooltip-content';
|
import { QuotaTooltipContent } from '@/components/shared/quota-tooltip-content';
|
||||||
@@ -115,6 +121,14 @@ export function AccountItem({
|
|||||||
// Use shared utility functions for provider-specific quota handling
|
// Use shared utility functions for provider-specific quota handling
|
||||||
const minQuota = getProviderMinQuota(account.provider, quota);
|
const minQuota = getProviderMinQuota(account.provider, quota);
|
||||||
const nextReset = getProviderResetTime(account.provider, quota);
|
const nextReset = getProviderResetTime(account.provider, quota);
|
||||||
|
const codexBreakdown =
|
||||||
|
account.provider === 'codex' && quota && isCodexQuotaResult(quota)
|
||||||
|
? getCodexQuotaBreakdown(quota.windows)
|
||||||
|
: null;
|
||||||
|
const codexQuotaRows = [
|
||||||
|
{ label: '5h', value: codexBreakdown?.fiveHourWindow?.remainingPercent ?? null },
|
||||||
|
{ label: 'Weekly', value: codexBreakdown?.weeklyWindow?.remainingPercent ?? null },
|
||||||
|
].filter((row): row is { label: string; value: number } => row.value !== null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -337,14 +351,34 @@ export function AccountItem({
|
|||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<div className="flex items-center gap-2">
|
{account.provider === 'codex' && codexQuotaRows.length > 0 ? (
|
||||||
<Progress
|
<div className="space-y-1.5">
|
||||||
value={Math.max(0, Math.min(100, minQuota))}
|
{codexQuotaRows.map((row) => (
|
||||||
className="h-2 flex-1"
|
<div key={row.label} className="flex items-center gap-2">
|
||||||
indicatorClassName={getQuotaColor(minQuota)}
|
<span className="w-10 text-[10px] text-muted-foreground">
|
||||||
/>
|
{row.label}
|
||||||
<span className="text-xs font-medium w-10 text-right">{minQuota}%</span>
|
</span>
|
||||||
</div>
|
<Progress
|
||||||
|
value={Math.max(0, Math.min(100, row.value))}
|
||||||
|
className="h-2 flex-1"
|
||||||
|
indicatorClassName={getQuotaColor(row.value)}
|
||||||
|
/>
|
||||||
|
<span className="text-xs font-medium w-10 text-right">
|
||||||
|
{row.value}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Progress
|
||||||
|
value={Math.max(0, Math.min(100, minQuota))}
|
||||||
|
className="h-2 flex-1"
|
||||||
|
indicatorClassName={getQuotaColor(minQuota)}
|
||||||
|
/>
|
||||||
|
<span className="text-xs font-medium w-10 text-right">{minQuota}%</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="bottom" className="max-w-xs">
|
<TooltipContent side="bottom" className="max-w-xs">
|
||||||
{quota && <QuotaTooltipContent quota={quota} resetTime={nextReset} />}
|
{quota && <QuotaTooltipContent quota={quota} resetTime={nextReset} />}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import { Clock } from 'lucide-react';
|
|||||||
import {
|
import {
|
||||||
cn,
|
cn,
|
||||||
formatResetTime,
|
formatResetTime,
|
||||||
|
getCodexQuotaBreakdown,
|
||||||
|
getCodexWindowDisplayLabel,
|
||||||
getModelsWithTiers,
|
getModelsWithTiers,
|
||||||
groupModelsByTier,
|
groupModelsByTier,
|
||||||
isAgyQuotaResult,
|
isAgyQuotaResult,
|
||||||
@@ -64,13 +66,29 @@ export function QuotaTooltipContent({ quota, resetTime }: QuotaTooltipContentPro
|
|||||||
|
|
||||||
// Codex provider tooltip
|
// Codex provider tooltip
|
||||||
if (isCodexQuotaResult(quota)) {
|
if (isCodexQuotaResult(quota)) {
|
||||||
|
const { fiveHourWindow, weeklyWindow, codeReviewWindows, unknownWindows } =
|
||||||
|
getCodexQuotaBreakdown(quota.windows);
|
||||||
|
const orderedWindows = [fiveHourWindow, weeklyWindow, ...codeReviewWindows, ...unknownWindows]
|
||||||
|
.filter((w): w is NonNullable<typeof w> => !!w)
|
||||||
|
.filter(
|
||||||
|
(w, index, arr) =>
|
||||||
|
arr.findIndex(
|
||||||
|
(candidate) => candidate.label === w.label && candidate.resetAt === w.resetAt
|
||||||
|
) === index
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="text-xs space-y-1">
|
<div className="text-xs space-y-1">
|
||||||
<p className="font-medium">Rate Limits:</p>
|
<p className="font-medium">Rate Limits:</p>
|
||||||
{quota.planType && <p className="text-muted-foreground">Plan: {quota.planType}</p>}
|
{quota.planType && <p className="text-muted-foreground">Plan: {quota.planType}</p>}
|
||||||
{quota.windows.map((w) => (
|
{orderedWindows.map((w, index) => (
|
||||||
<div key={w.label} className="flex justify-between gap-4">
|
<div
|
||||||
<span className={cn(w.remainingPercent < 20 && 'text-red-500')}>{w.label}</span>
|
key={`${w.label}-${w.resetAt ?? 'no-reset'}-${index}`}
|
||||||
|
className="flex justify-between gap-4"
|
||||||
|
>
|
||||||
|
<span className={cn(w.remainingPercent < 20 && 'text-red-500')}>
|
||||||
|
{getCodexWindowDisplayLabel(w.label)}
|
||||||
|
</span>
|
||||||
<span className="font-mono">{w.remainingPercent}%</span>
|
<span className="font-mono">{w.remainingPercent}%</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
+140
-2
@@ -306,12 +306,144 @@ export function groupModelsByTier(models: TieredModel[]): Map<ModelTier, TieredM
|
|||||||
return groups;
|
return groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type CodexWindowKind =
|
||||||
|
| 'usage-5h'
|
||||||
|
| 'usage-weekly'
|
||||||
|
| 'code-review-5h'
|
||||||
|
| 'code-review-weekly'
|
||||||
|
| 'code-review'
|
||||||
|
| 'unknown';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map raw Codex API window labels into semantic buckets.
|
||||||
|
*/
|
||||||
|
export function getCodexWindowKind(label: string): CodexWindowKind {
|
||||||
|
const lower = (label || '').toLowerCase();
|
||||||
|
const isCodeReview = lower.includes('code review') || lower.includes('code_review');
|
||||||
|
const isPrimary = lower.includes('primary');
|
||||||
|
const isSecondary = lower.includes('secondary');
|
||||||
|
|
||||||
|
if (isCodeReview) {
|
||||||
|
if (isPrimary) return 'code-review-5h';
|
||||||
|
if (isSecondary) return 'code-review-weekly';
|
||||||
|
return 'code-review';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPrimary) return 'usage-5h';
|
||||||
|
if (isSecondary) return 'usage-weekly';
|
||||||
|
return 'unknown';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert raw Codex window labels into user-facing labels.
|
||||||
|
*/
|
||||||
|
export function getCodexWindowDisplayLabel(label: string): string {
|
||||||
|
switch (getCodexWindowKind(label)) {
|
||||||
|
case 'usage-5h':
|
||||||
|
return '5h usage limit';
|
||||||
|
case 'usage-weekly':
|
||||||
|
return 'Weekly usage limit';
|
||||||
|
case 'code-review-5h':
|
||||||
|
return 'Code review (5h)';
|
||||||
|
case 'code-review-weekly':
|
||||||
|
return 'Code review (weekly)';
|
||||||
|
case 'code-review':
|
||||||
|
return 'Code review';
|
||||||
|
case 'unknown':
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CodexQuotaBreakdown {
|
||||||
|
fiveHourWindow: CodexQuotaWindow | null;
|
||||||
|
weeklyWindow: CodexQuotaWindow | null;
|
||||||
|
codeReviewWindows: CodexQuotaWindow[];
|
||||||
|
unknownWindows: CodexQuotaWindow[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Break down Codex windows into core usage windows (5h + weekly) and auxiliary windows.
|
||||||
|
*/
|
||||||
|
export function getCodexQuotaBreakdown(windows: CodexQuotaWindow[]): CodexQuotaBreakdown {
|
||||||
|
if (!windows || windows.length === 0) {
|
||||||
|
return {
|
||||||
|
fiveHourWindow: null,
|
||||||
|
weeklyWindow: null,
|
||||||
|
codeReviewWindows: [],
|
||||||
|
unknownWindows: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let fiveHourWindow: CodexQuotaWindow | null = null;
|
||||||
|
let weeklyWindow: CodexQuotaWindow | null = null;
|
||||||
|
const codeReviewWindows: CodexQuotaWindow[] = [];
|
||||||
|
const unknownWindows: CodexQuotaWindow[] = [];
|
||||||
|
const nonCodeReviewWindows: CodexQuotaWindow[] = [];
|
||||||
|
|
||||||
|
for (const window of windows) {
|
||||||
|
const kind = getCodexWindowKind(window.label);
|
||||||
|
|
||||||
|
switch (kind) {
|
||||||
|
case 'usage-5h':
|
||||||
|
if (!fiveHourWindow) fiveHourWindow = window;
|
||||||
|
nonCodeReviewWindows.push(window);
|
||||||
|
break;
|
||||||
|
case 'usage-weekly':
|
||||||
|
if (!weeklyWindow) weeklyWindow = window;
|
||||||
|
nonCodeReviewWindows.push(window);
|
||||||
|
break;
|
||||||
|
case 'code-review-5h':
|
||||||
|
case 'code-review-weekly':
|
||||||
|
case 'code-review':
|
||||||
|
codeReviewWindows.push(window);
|
||||||
|
break;
|
||||||
|
case 'unknown':
|
||||||
|
unknownWindows.push(window);
|
||||||
|
nonCodeReviewWindows.push(window);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback for API label changes: infer 5h/weekly from reset horizon when explicit labels are absent.
|
||||||
|
if ((!fiveHourWindow || !weeklyWindow) && nonCodeReviewWindows.length > 0) {
|
||||||
|
const withReset = nonCodeReviewWindows
|
||||||
|
.filter((w) => typeof w.resetAfterSeconds === 'number' && w.resetAfterSeconds >= 0)
|
||||||
|
.sort((a, b) => (a.resetAfterSeconds || 0) - (b.resetAfterSeconds || 0));
|
||||||
|
|
||||||
|
if (!fiveHourWindow) {
|
||||||
|
fiveHourWindow = withReset[0] || nonCodeReviewWindows[0] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!weeklyWindow) {
|
||||||
|
weeklyWindow =
|
||||||
|
withReset.length > 1
|
||||||
|
? withReset[withReset.length - 1]
|
||||||
|
: nonCodeReviewWindows.find((w) => w !== fiveHourWindow) || null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
fiveHourWindow,
|
||||||
|
weeklyWindow,
|
||||||
|
codeReviewWindows,
|
||||||
|
unknownWindows,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get minimum remaining percentage across Codex rate limit windows
|
* Get minimum remaining percentage across Codex rate limit windows
|
||||||
*/
|
*/
|
||||||
export function getMinCodexQuota(windows: CodexQuotaWindow[]): number | null {
|
export function getMinCodexQuota(windows: CodexQuotaWindow[]): number | null {
|
||||||
if (!windows || windows.length === 0) return null;
|
if (!windows || windows.length === 0) return null;
|
||||||
const percentages = windows.map((w) => w.remainingPercent);
|
|
||||||
|
const { fiveHourWindow, weeklyWindow } = getCodexQuotaBreakdown(windows);
|
||||||
|
const usageWindows = [fiveHourWindow, weeklyWindow].filter(
|
||||||
|
(w, index, arr): w is CodexQuotaWindow => !!w && arr.indexOf(w) === index
|
||||||
|
);
|
||||||
|
|
||||||
|
// Primary account quota should be driven by core usage windows, not code-review windows.
|
||||||
|
const sourceWindows = usageWindows.length > 0 ? usageWindows : windows;
|
||||||
|
const percentages = sourceWindows.map((w) => w.remainingPercent);
|
||||||
return Math.min(...percentages);
|
return Math.min(...percentages);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,7 +452,13 @@ export function getMinCodexQuota(windows: CodexQuotaWindow[]): number | null {
|
|||||||
*/
|
*/
|
||||||
export function getCodexResetTime(windows: CodexQuotaWindow[]): string | null {
|
export function getCodexResetTime(windows: CodexQuotaWindow[]): string | null {
|
||||||
if (!windows || windows.length === 0) return null;
|
if (!windows || windows.length === 0) return null;
|
||||||
const resets = windows.map((w) => w.resetAt).filter((t): t is string => t !== null);
|
|
||||||
|
const { fiveHourWindow, weeklyWindow } = getCodexQuotaBreakdown(windows);
|
||||||
|
const usageWindows = [fiveHourWindow, weeklyWindow].filter(
|
||||||
|
(w, index, arr): w is CodexQuotaWindow => !!w && arr.indexOf(w) === index
|
||||||
|
);
|
||||||
|
const sourceWindows = usageWindows.length > 0 ? usageWindows : windows;
|
||||||
|
const resets = sourceWindows.map((w) => w.resetAt).filter((t): t is string => t !== null);
|
||||||
if (resets.length === 0) return null;
|
if (resets.length === 0) return null;
|
||||||
return resets.sort()[0];
|
return resets.sort()[0];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -386,7 +386,8 @@ describe('getMinCodexQuota', () => {
|
|||||||
resetAt: '2026-01-30T14:30:32Z',
|
resetAt: '2026-01-30T14:30:32Z',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
expect(getMinCodexQuota(windows)).toBe(8.7);
|
// Core account quota should ignore code-review windows.
|
||||||
|
expect(getMinCodexQuota(windows)).toBe(21.1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -439,10 +440,10 @@ describe('getCodexResetTime', () => {
|
|||||||
usedPercent: 50,
|
usedPercent: 50,
|
||||||
remainingPercent: 50,
|
remainingPercent: 50,
|
||||||
resetAfterSeconds: 1800,
|
resetAfterSeconds: 1800,
|
||||||
resetAt: '2026-01-30T16:00:00Z',
|
resetAt: '2026-01-30T09:00:00Z',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
// Should return earliest (alphabetically sorted)
|
// Should ignore code-review reset when choosing main account reset.
|
||||||
expect(getCodexResetTime(windows)).toBe('2026-01-30T10:00:00Z');
|
expect(getCodexResetTime(windows)).toBe('2026-01-30T10:00:00Z');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -516,9 +517,10 @@ describe('getCodexResetTime', () => {
|
|||||||
usedPercent: 75,
|
usedPercent: 75,
|
||||||
remainingPercent: 25,
|
remainingPercent: 25,
|
||||||
resetAfterSeconds: 5400,
|
resetAfterSeconds: 5400,
|
||||||
resetAt: '2026-01-30T18:45:00Z',
|
resetAt: '2026-01-30T08:15:00Z',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
// Code-review windows should not drive the main account reset.
|
||||||
expect(getCodexResetTime(windows)).toBe('2026-01-30T09:30:00Z');
|
expect(getCodexResetTime(windows)).toBe('2026-01-30T09:30:00Z');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in new issue
Block a user