Selective fixes from PR #35: Model-specific rate limits & robustness improvements (#37)

* feat: apply local user changes and fixes

* ;D

* Implement OpenAI support, model-specific rate limiting, and robustness fixes

* docs: update pr title

* feat: ensure unique openai models endpoint

* fix: startup banner alignment and removed duplicates

* feat: add model fallback system with --fallback flag

* fix: accounts cli hanging after completion

* feat: add exit option to accounts cli menu

* fix: remove circular dependency warning for fallback flag

* feat: show active modes in banner and hide their flags

* Remove OpenAI compatibility and fallback features from PR #35

Cherry-picked selective fixes from PR #35 while removing:
- OpenAI-compatible API endpoints (/openai/v1/*)
- Model fallback system (fallback-config.js)
- Thinking block skip for Gemini models
- Unnecessary files (pullrequest.md, test-fix.js, test-openai.js)

Retained improvements:
- Network error handling with retry logic
- Model-specific rate limiting
- Enhanced health check with quota info
- CLI fixes (exit option, process.exit)
- Startup banner alignment (debug mode only)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* banner alignment fix

* Refactor: Model-specific rate limits and cleanup deprecated code

- Remove global rate limit fields (isRateLimited, rateLimitResetTime) in favor of model-specific limits (modelRateLimits[modelId])
- Remove deprecated wrapper functions (is429Error, isAuthInvalidError) from handlers
- Filter fetchAvailableModels to only return Claude and Gemini models
- Fix getCurrentStickyAccount() to pass model param after waiting
- Update /account-limits endpoint to show model-specific limits
- Remove multi-account OAuth flow to avoid state mismatch errors

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: show (x/y) limited status in account-limits table

- Status is now "ok" only when all models are available
- Shows "(x/y) limited" when x out of y models are exhausted
- Provides better visibility into partial rate limiting

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: update CLAUDE.md with model-specific rate limiting

- Document modelRateLimits[modelId] for per-model rate tracking
- Add isNetworkError() helper to utilities section

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: M1noa <minoa@minoa.cat>
Co-authored-by: Minoa <altgithub@minoa.cat>
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Badri Narayanan S
2026-01-03 15:33:49 +05:30
committed by GitHub
parent 2d05dd5b62
commit 9c4a712a9a
15 changed files with 474 additions and 194 deletions

View File

@@ -138,8 +138,7 @@ function saveAccounts(accounts, settings = {}) {
projectId: acc.projectId,
addedAt: acc.addedAt || new Date().toISOString(),
lastUsed: acc.lastUsed || null,
isRateLimited: acc.isRateLimited || false,
rateLimitResetTime: acc.rateLimitResetTime || null
modelRateLimits: acc.modelRateLimits || {}
})),
settings: {
cooldownDurationMs: 60000,
@@ -168,7 +167,11 @@ function displayAccounts(accounts) {
console.log(`\n${accounts.length} account(s) saved:`);
accounts.forEach((acc, i) => {
const status = acc.isRateLimited ? ' (rate-limited)' : '';
// Check for any active model-specific rate limits
const hasActiveLimit = Object.values(acc.modelRateLimits || {}).some(
limit => limit.isRateLimited && limit.resetTime > Date.now()
);
const status = hasActiveLimit ? ' (rate-limited)' : '';
console.log(` ${i + 1}. ${acc.email}${status}`);
});
}
@@ -218,8 +221,7 @@ async function addAccount(existingAccounts) {
refreshToken: result.refreshToken,
projectId: result.projectId,
addedAt: new Date().toISOString(),
isRateLimited: false,
rateLimitResetTime: null
modelRateLimits: {}
};
} catch (error) {
console.error(`\n✗ Authentication failed: ${error.message}`);
@@ -280,7 +282,7 @@ async function interactiveAdd(rl) {
if (accounts.length > 0) {
displayAccounts(accounts);
const choice = await rl.question('\n(a)dd new, (r)emove existing, or (f)resh start? [a/r/f]: ');
const choice = await rl.question('\n(a)dd new, (r)emove existing, (f)resh start, or (e)xit? [a/r/f/e]: ');
const c = choice.toLowerCase();
if (c === 'r') {
@@ -291,36 +293,32 @@ async function interactiveAdd(rl) {
accounts.length = 0;
} else if (c === 'a') {
console.log('\nAdding to existing accounts.');
} else if (c === 'e') {
console.log('\nExiting...');
return; // Exit cleanly
} else {
console.log('\nInvalid choice, defaulting to add.');
}
}
// Add accounts loop
while (accounts.length < MAX_ACCOUNTS) {
const newAccount = await addAccount(accounts);
if (newAccount) {
accounts.push(newAccount);
// Auto-save after each successful add to prevent data loss
saveAccounts(accounts);
} else if (accounts.length > 0) {
// Even if newAccount is null (duplicate update), save the updated accounts
saveAccounts(accounts);
}
// Add single account
if (accounts.length >= MAX_ACCOUNTS) {
console.log(`\nMaximum of ${MAX_ACCOUNTS} accounts reached.`);
return;
}
if (accounts.length >= MAX_ACCOUNTS) {
console.log(`\nMaximum of ${MAX_ACCOUNTS} accounts reached.`);
break;
}
const addMore = await rl.question('\nAdd another account? [y/N]: ');
if (addMore.toLowerCase() !== 'y') {
break;
}
const newAccount = await addAccount(accounts);
if (newAccount) {
accounts.push(newAccount);
saveAccounts(accounts);
} else if (accounts.length > 0) {
// Even if newAccount is null (duplicate update), save the updated accounts
saveAccounts(accounts);
}
if (accounts.length > 0) {
displayAccounts(accounts);
console.log('\nTo add more accounts, run this command again.');
} else {
console.log('\nNo accounts to save.');
}
@@ -431,6 +429,8 @@ async function main() {
}
} finally {
rl.close();
// Force exit to prevent hanging
process.exit(0);
}
}