Claude: CLAUDE_AUTOCOMPACT_PCT_OVERRIDE=50 (~500K on 1M context) Codex: model_auto_compact_token_limit=258000 (matching CLI default)
72 lines
2.2 KiB
TypeScript
72 lines
2.2 KiB
TypeScript
/**
|
|
* Token Rotation Base Utilities (SSOT)
|
|
*
|
|
* Shared algorithms for Claude and Codex token rotation.
|
|
* Extracted from token-rotation.ts and codex-token-rotation.ts
|
|
* to eliminate duplicate implementations.
|
|
*/
|
|
|
|
export const BUFFER_MS = 3 * 60_000; // 3 min buffer after reset time
|
|
export const DEFAULT_COOLDOWN_MS = 3_600_000; // 1 hour fallback
|
|
|
|
/**
|
|
* Parse "try again at Mar 26th, 2026 9:00 AM" or "resets at ..." from
|
|
* an error message. Returns timestamp in ms, or null if not found.
|
|
*
|
|
* Uses the wider regex from token-rotation.ts that also matches
|
|
* "resets at" patterns (codex-token-rotation only matched "try again at").
|
|
*/
|
|
export function parseRetryAfterFromError(error?: string): number | null {
|
|
if (!error) return null;
|
|
const match = error.match(
|
|
/(?:try again at|resets?\s+(?:at\s+)?)\s*(\w+ \d{1,2}(?:st|nd|rd|th)?,?\s+\d{4}\s+\d{1,2}:\d{2}\s*(?:AM|PM)?)/i,
|
|
);
|
|
if (!match) return null;
|
|
try {
|
|
const cleaned = match[1].replace(/(\d+)(?:st|nd|rd|th)/i, '$1');
|
|
const ts = new Date(cleaned).getTime();
|
|
if (Number.isNaN(ts)) return null;
|
|
return ts;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Compute when a rate-limited token should become available again.
|
|
* Parses retry-after from error message, falling back to DEFAULT_COOLDOWN_MS.
|
|
*/
|
|
export function computeCooldownUntil(error?: string): number {
|
|
const retryAt = parseRetryAfterFromError(error);
|
|
if (retryAt) return retryAt + BUFFER_MS;
|
|
return Date.now() + DEFAULT_COOLDOWN_MS;
|
|
}
|
|
|
|
/**
|
|
* Find the next available (non-rate-limited) item in a rotation pool.
|
|
* Returns the index, or null if all are exhausted.
|
|
*
|
|
* This is the shared rotation algorithm used by both Claude token rotation
|
|
* and Codex account rotation.
|
|
*/
|
|
export function findNextAvailable<
|
|
T extends { rateLimitedUntil: number | null },
|
|
>(
|
|
items: T[],
|
|
currentIndex: number,
|
|
opts?: { ignoreRateLimits?: boolean },
|
|
): number | null {
|
|
const now = Date.now();
|
|
const ignoreRL = opts?.ignoreRateLimits ?? false;
|
|
|
|
for (let i = 1; i < items.length; i++) {
|
|
const idx = (currentIndex + i) % items.length;
|
|
const item = items[idx];
|
|
if (ignoreRL || !item.rateLimitedUntil || item.rateLimitedUntil <= now) {
|
|
return idx;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|