File size: 4,411 Bytes
1dbc34b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | import type { ClaudeUsage } from '../types/usage-types';
/**
* Calculate the expected weekly usage percentage based on how far through the week we are.
* Claude's weekly usage resets every Thursday. Given the reset time (when the NEXT reset occurs),
* we can determine how much of the week has elapsed and therefore what percentage of the budget
* should have been used if usage were evenly distributed.
*
* @param weeklyResetTime - ISO date string for when the weekly usage next resets
* @returns The expected usage percentage (0-100), or null if the reset time is invalid
*/
export function getExpectedWeeklyPacePercentage(
weeklyResetTime: string | undefined
): number | null {
if (!weeklyResetTime) return null;
try {
const resetDate = new Date(weeklyResetTime);
if (isNaN(resetDate.getTime())) return null;
const now = new Date();
const WEEK_MS = 7 * 24 * 60 * 60 * 1000;
// The week started 7 days before the reset
const weekStartDate = new Date(resetDate.getTime() - WEEK_MS);
// How far through the week are we?
const elapsed = now.getTime() - weekStartDate.getTime();
const fractionElapsed = elapsed / WEEK_MS;
// Clamp to 0-1 range
const clamped = Math.max(0, Math.min(1, fractionElapsed));
return clamped * 100;
} catch {
return null;
}
}
/**
* Get a human-readable label for the pace status (ahead or behind expected usage).
*
* @param actualPercentage - The actual usage percentage (0-100)
* @param expectedPercentage - The expected usage percentage (0-100)
* @returns A string like "5% ahead of pace" or "10% behind pace", or null
*/
export function getPaceStatusLabel(
actualPercentage: number,
expectedPercentage: number | null
): string | null {
if (expectedPercentage === null) return null;
const diff = Math.round(actualPercentage - expectedPercentage);
if (diff === 0) return 'On pace';
// Using more than expected = behind pace (bad)
if (diff > 0) return `${Math.abs(diff)}% behind pace`;
// Using less than expected = ahead of pace (good)
return `${Math.abs(diff)}% ahead of pace`;
}
/**
* Calculate the expected pace percentage for a Codex rate limit window based on how far
* through the window we are. This is a generic version of getExpectedWeeklyPacePercentage
* that works with any window duration.
*
* Only returns a value for windows >= 1 day (1440 minutes) since pace tracking isn't
* meaningful for short windows.
*
* @param resetsAt - Unix timestamp in seconds for when the window resets
* @param windowDurationMins - Window duration in minutes
* @returns The expected usage percentage (0-100), or null if not applicable
*/
export function getExpectedCodexPacePercentage(
resetsAt: number | undefined | null,
windowDurationMins: number | undefined | null
): number | null {
// Only show pace for windows >= 1 day (1440 minutes)
if (!resetsAt || !windowDurationMins || windowDurationMins < 1440) return null;
try {
const resetDate = new Date(resetsAt * 1000);
if (isNaN(resetDate.getTime())) return null;
const now = new Date();
const windowMs = windowDurationMins * 60 * 1000;
// The window started windowDurationMins before the reset
const windowStartDate = new Date(resetDate.getTime() - windowMs);
// How far through the window are we?
const elapsed = now.getTime() - windowStartDate.getTime();
const fractionElapsed = elapsed / windowMs;
// Clamp to 0-1 range
const clamped = Math.max(0, Math.min(1, fractionElapsed));
return clamped * 100;
} catch {
return null;
}
}
/**
* Check if Claude usage is at its limit (any of: session >= 100%, weekly >= 100%, OR cost >= limit)
* Returns true if any limit is reached, meaning auto mode should pause feature pickup.
*/
export function isClaudeUsageAtLimit(claudeUsage: ClaudeUsage | null): boolean {
if (!claudeUsage) {
// No usage data available - don't block
return false;
}
// Check session limit (5-hour window)
if (claudeUsage.sessionPercentage >= 100) {
return true;
}
// Check weekly limit
if (claudeUsage.weeklyPercentage >= 100) {
return true;
}
// Check cost limit (if configured)
if (
claudeUsage.costLimit !== null &&
claudeUsage.costLimit > 0 &&
claudeUsage.costUsed !== null &&
claudeUsage.costUsed >= claudeUsage.costLimit
) {
return true;
}
return false;
}
|