Files
EJClaw/src/token-rotation-base.ts
Eyejoker 906a3dfadb config: add auto-compact settings for Claude and Codex agents
Claude: CLAUDE_AUTOCOMPACT_PCT_OVERRIDE=50 (~500K on 1M context)
Codex: model_auto_compact_token_limit=258000 (matching CLI default)
2026-03-25 03:56:03 +09:00

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