fix: Claude token rotation, usage-exhausted fallback, and usage API caching

- Fix token rotation not taking effect: getCurrentToken() was shadowed
  by static .env CLAUDE_CODE_OAUTH_TOKEN value in agent environment
- Don't fall back to Kimi on usage-exhausted (only on transient 429/network)
- Add disk cache for Claude usage API data (survives restarts, 429s)
- Rate-limit usage API calls to 1 per token per 5 minutes
- Add ignoreRateLimits option to rotateToken() for exhausted recovery
- Rotate to next token in getActiveProvider() when current is exhausted
- Add isUsageExhausted() helper to provider-fallback
This commit is contained in:
Eyejoker
2026-03-24 03:40:51 +09:00
parent 2ab6c25f0e
commit 120f16e9e1
12 changed files with 1633 additions and 258 deletions

View File

@@ -142,21 +142,29 @@ export function getCurrentToken(): string | undefined {
* Try to rotate to the next available (non-rate-limited) token.
* Returns true if a fresh token was found, false if all are exhausted.
*/
export function rotateToken(errorMessage?: string): boolean {
export function rotateToken(
errorMessage?: string,
opts?: { ignoreRateLimits?: boolean },
): boolean {
if (tokens.length <= 1) return false;
const now = Date.now();
tokens[currentIndex].rateLimitedUntil = computeCooldownUntil(errorMessage);
const ignoreRL = opts?.ignoreRateLimits ?? false;
// Find next available token
for (let i = 1; i < tokens.length; i++) {
const idx = (currentIndex + i) % tokens.length;
const state = tokens[idx];
if (!state.rateLimitedUntil || state.rateLimitedUntil <= now) {
if (
ignoreRL ||
!state.rateLimitedUntil ||
state.rateLimitedUntil <= now
) {
state.rateLimitedUntil = null;
currentIndex = idx;
logger.info(
{ tokenIndex: currentIndex, totalTokens: tokens.length },
{ tokenIndex: currentIndex, totalTokens: tokens.length, ignoreRL },
`Rotated to token #${currentIndex + 1}/${tokens.length}`,
);
saveState();