@pikoloo/codex-proxy 1.1.0 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,130 +0,0 @@
1
- import {
2
- markRateLimited,
3
- markInvalid,
4
- clearInvalid,
5
- isAllRateLimited,
6
- getMinWaitTimeMs,
7
- clearExpiredLimits,
8
- isAccountCoolingDown
9
- } from './rate-limits.js';
10
-
11
- const MAX_WAIT_BEFORE_ERROR_MS = 120000;
12
-
13
- export class AccountRotator {
14
- constructor(accountManager) {
15
- this.accountManager = accountManager;
16
- }
17
-
18
- selectAccount(modelId) {
19
- const { accounts } = this.accountManager.listAccounts();
20
- return selectAccount(accounts, modelId);
21
- }
22
-
23
- markRateLimited(email, resetMs, modelId) {
24
- const { accounts } = this.accountManager.listAccounts();
25
- markRateLimited(accounts, email, resetMs, modelId);
26
- this.accountManager.save();
27
- }
28
-
29
- markInvalid(email, reason) {
30
- const { accounts } = this.accountManager.listAccounts();
31
- markInvalid(accounts, email, reason);
32
- this.accountManager.save();
33
- }
34
-
35
- clearInvalid(email) {
36
- const { accounts } = this.accountManager.listAccounts();
37
- clearInvalid(accounts, email);
38
- this.accountManager.save();
39
- }
40
-
41
- isAllRateLimited(modelId) {
42
- const { accounts } = this.accountManager.listAccounts();
43
- return isAllRateLimited(accounts, modelId);
44
- }
45
-
46
- getMinWaitTimeMs(modelId) {
47
- const { accounts } = this.accountManager.listAccounts();
48
- return getMinWaitTimeMs(accounts, modelId);
49
- }
50
-
51
- notifySuccess(account, modelId) {}
52
-
53
- notifyRateLimit(account, modelId) {}
54
-
55
- notifyFailure(account, modelId) {}
56
-
57
- clearExpiredLimits() {
58
- const { accounts } = this.accountManager.listAccounts();
59
- clearExpiredLimits(accounts);
60
- this.accountManager.save();
61
- }
62
-
63
- }
64
-
65
- function selectAccount(accounts, modelId) {
66
- if (!accounts || accounts.length === 0) {
67
- return { account: null, index: 0, waitMs: 0 };
68
- }
69
-
70
- const activeIndex = accounts.findIndex((account) => account.isActive);
71
- const startIndex = activeIndex >= 0 ? activeIndex : 0;
72
-
73
- for (let offset = 0; offset < accounts.length; offset++) {
74
- const index = (startIndex + offset) % accounts.length;
75
- const account = accounts[index];
76
-
77
- if (isAccountUsable(account, modelId)) {
78
- account.lastUsed = Date.now();
79
- return { account, index, waitMs: 0 };
80
- }
81
- }
82
-
83
- const waitMs = getAccountWaitMs(accounts[startIndex], modelId);
84
- if (waitMs > 0 && waitMs <= MAX_WAIT_BEFORE_ERROR_MS) {
85
- return { account: null, index: startIndex, waitMs };
86
- }
87
-
88
- return { account: null, index: startIndex, waitMs: 0 };
89
- }
90
-
91
- function isAccountUsable(account, modelId) {
92
- if (!account) return false;
93
- if (account.isInvalid) return false;
94
- if (account.enabled === false) return false;
95
- if (isAccountCoolingDown(account)) return false;
96
-
97
- const waitMs = getModelRateLimitWaitMs(account, modelId);
98
- return waitMs === 0;
99
- }
100
-
101
- function getAccountWaitMs(account, modelId) {
102
- if (!account) return 0;
103
- if (account.isInvalid) return 0;
104
- if (account.enabled === false) return 0;
105
- if (isAccountCoolingDown(account)) return 0;
106
-
107
- return getModelRateLimitWaitMs(account, modelId);
108
- }
109
-
110
- function getModelRateLimitWaitMs(account, modelId) {
111
- if (!modelId || !account?.modelRateLimits?.[modelId]) {
112
- return 0;
113
- }
114
-
115
- const limit = account.modelRateLimits[modelId];
116
- if (!limit?.isRateLimited || !limit.resetTime || limit.resetTime <= Date.now()) {
117
- return 0;
118
- }
119
-
120
- return limit.resetTime - Date.now();
121
- }
122
-
123
- export {
124
- markRateLimited,
125
- markInvalid,
126
- clearInvalid,
127
- isAllRateLimited,
128
- getMinWaitTimeMs,
129
- clearExpiredLimits
130
- };
@@ -1,293 +0,0 @@
1
- import { logger } from '../utils/logger.js';
2
-
3
- export const CooldownReason = {
4
- RATE_LIMIT: 'RATE_LIMIT',
5
- AUTH_FAILURE: 'AUTH_FAILURE',
6
- CONSECUTIVE_FAILURES: 'CONSECUTIVE_FAILURES',
7
- SERVER_ERROR: 'SERVER_ERROR'
8
- };
9
-
10
- const DEFAULT_COOLDOWN_MS = 60000;
11
-
12
- /**
13
- * Check if all accounts are rate-limited for a specific model
14
- * @param {Array} accounts - Array of account objects
15
- * @param {string} modelId - Model identifier
16
- * @returns {boolean} True if all accounts are rate-limited
17
- */
18
- export function isAllRateLimited(accounts, modelId) {
19
- if (!accounts || accounts.length === 0) return true;
20
- return accounts.every(account => {
21
- const rateLimit = account.modelRateLimits?.[modelId];
22
- return rateLimit?.isRateLimited && rateLimit.resetTime > Date.now();
23
- });
24
- }
25
-
26
- /**
27
- * Get list of accounts that are not rate-limited for a specific model
28
- * @param {Array} accounts - Array of account objects
29
- * @param {string} modelId - Model identifier
30
- * @returns {Array} Array of available account objects
31
- */
32
- export function getAvailableAccounts(accounts, modelId) {
33
- if (!accounts || accounts.length === 0) return [];
34
- const now = Date.now();
35
- return accounts.filter(account => {
36
- if (account.isInvalid) return false;
37
- if (isAccountCoolingDown(account)) return false;
38
- const rateLimit = account.modelRateLimits?.[modelId];
39
- return !rateLimit?.isRateLimited || rateLimit.resetTime <= now;
40
- });
41
- }
42
-
43
- /**
44
- * Clear all expired rate limits across all accounts
45
- * @param {Array} accounts - Array of account objects
46
- * @returns {number} Count of cleared rate limits
47
- */
48
- export function clearExpiredLimits(accounts) {
49
- if (!accounts || accounts.length === 0) return 0;
50
- const now = Date.now();
51
- let clearedCount = 0;
52
-
53
- for (const account of accounts) {
54
- if (!account.modelRateLimits) continue;
55
- for (const modelId of Object.keys(account.modelRateLimits)) {
56
- const rateLimit = account.modelRateLimits[modelId];
57
- if (rateLimit?.isRateLimited && rateLimit.resetTime <= now) {
58
- account.modelRateLimits[modelId] = {
59
- isRateLimited: false,
60
- resetTime: null,
61
- actualResetMs: null
62
- };
63
- clearedCount++;
64
- logger.debug(`Cleared expired rate limit for ${account.email} model ${modelId}`);
65
- }
66
- }
67
- }
68
-
69
- return clearedCount;
70
- }
71
-
72
- /**
73
- * Reset all rate limits for all accounts (optimistic retry)
74
- * @param {Array} accounts - Array of account objects
75
- */
76
- export function resetAllRateLimits(accounts) {
77
- if (!accounts || accounts.length === 0) return;
78
- for (const account of accounts) {
79
- account.modelRateLimits = {};
80
- }
81
- logger.info('Reset all rate limits for all accounts (optimistic retry)');
82
- }
83
-
84
- /**
85
- * Mark an account as rate-limited for a specific model
86
- * @param {Array} accounts - Array of account objects
87
- * @param {string} email - Account email
88
- * @param {number} resetMs - Reset time in milliseconds
89
- * @param {string} modelId - Model identifier
90
- */
91
- export function markRateLimited(accounts, email, resetMs, modelId) {
92
- const account = accounts?.find(a => a.email === email);
93
- if (!account) {
94
- logger.warn(`Account not found: ${email}`);
95
- return;
96
- }
97
-
98
- if (!account.modelRateLimits) {
99
- account.modelRateLimits = {};
100
- }
101
-
102
- account.modelRateLimits[modelId] = {
103
- isRateLimited: true,
104
- resetTime: Date.now() + resetMs,
105
- actualResetMs: resetMs
106
- };
107
-
108
- logger.debug(`Rate limited ${email} for model ${modelId} for ${resetMs}ms`);
109
- }
110
-
111
- /**
112
- * Mark an account as invalid
113
- * @param {Array} accounts - Array of account objects
114
- * @param {string} email - Account email
115
- * @param {string} reason - Reason for invalid status
116
- */
117
- export function markInvalid(accounts, email, reason) {
118
- const account = accounts?.find(a => a.email === email);
119
- if (!account) {
120
- logger.warn(`Account not found: ${email}`);
121
- return;
122
- }
123
-
124
- account.isInvalid = true;
125
- account.invalidReason = reason;
126
- account.invalidAt = Date.now();
127
-
128
- logger.warn(`Marked account ${email} as invalid: ${reason}`);
129
- }
130
-
131
- /**
132
- * Clear invalid status for an account
133
- * @param {Array} accounts - Array of account objects
134
- * @param {string} email - Account email
135
- */
136
- export function clearInvalid(accounts, email) {
137
- const account = accounts?.find(a => a.email === email);
138
- if (!account) {
139
- logger.warn(`Account not found: ${email}`);
140
- return;
141
- }
142
-
143
- account.isInvalid = false;
144
- account.invalidReason = null;
145
- account.invalidAt = null;
146
-
147
- logger.info(`Cleared invalid status for account ${email}`);
148
- }
149
-
150
- /**
151
- * Get minimum wait time until any account is available for a model
152
- * @param {Array} accounts - Array of account objects
153
- * @param {string} modelId - Model identifier
154
- * @returns {number} Minimum wait time in milliseconds, 0 if any account available
155
- */
156
- export function getMinWaitTimeMs(accounts, modelId) {
157
- if (!accounts || accounts.length === 0) return 0;
158
-
159
- const available = getAvailableAccounts(accounts, modelId);
160
- if (available.length > 0) return 0;
161
-
162
- const now = Date.now();
163
- let minWait = Infinity;
164
-
165
- for (const account of accounts) {
166
- if (account.isInvalid) continue;
167
-
168
- const cooldownRemaining = getCooldownRemaining(account);
169
- if (cooldownRemaining > 0 && cooldownRemaining < minWait) {
170
- minWait = cooldownRemaining;
171
- }
172
-
173
- const rateLimit = account.modelRateLimits?.[modelId];
174
- if (rateLimit?.isRateLimited && rateLimit.resetTime > now) {
175
- const waitTime = rateLimit.resetTime - now;
176
- if (waitTime < minWait) {
177
- minWait = waitTime;
178
- }
179
- }
180
- }
181
-
182
- return minWait === Infinity ? 0 : minWait;
183
- }
184
-
185
- /**
186
- * Get rate limit info for a specific account and model
187
- * @param {Array} accounts - Array of account objects
188
- * @param {string} email - Account email
189
- * @param {string} modelId - Model identifier
190
- * @returns {Object|null} Rate limit info object or null
191
- */
192
- export function getRateLimitInfo(accounts, email, modelId) {
193
- const account = accounts?.find(a => a.email === email);
194
- if (!account) return null;
195
-
196
- return account.modelRateLimits?.[modelId] || null;
197
- }
198
-
199
- /**
200
- * Check if an account is currently cooling down
201
- * @param {Object} account - Account object
202
- * @returns {boolean} True if account is cooling down
203
- */
204
- export function isAccountCoolingDown(account) {
205
- if (!account?.cooldownUntil) return false;
206
- return account.cooldownUntil > Date.now();
207
- }
208
-
209
- /**
210
- * Mark an account as cooling down
211
- * @param {Array} accounts - Array of account objects
212
- * @param {string} email - Account email
213
- * @param {number} cooldownMs - Cooldown duration in milliseconds
214
- * @param {string} reason - Reason for cooldown (from CooldownReason)
215
- */
216
- export function markAccountCoolingDown(accounts, email, cooldownMs = DEFAULT_COOLDOWN_MS, reason) {
217
- const account = accounts?.find(a => a.email === email);
218
- if (!account) {
219
- logger.warn(`Account not found: ${email}`);
220
- return;
221
- }
222
-
223
- account.cooldownUntil = Date.now() + cooldownMs;
224
- account.cooldownReason = reason;
225
-
226
- logger.debug(`Account ${email} cooling down for ${cooldownMs}ms: ${reason}`);
227
- }
228
-
229
- /**
230
- * Clear cooldown status for an account
231
- * @param {Object} account - Account object
232
- */
233
- export function clearAccountCooldown(account) {
234
- if (!account) return;
235
- account.cooldownUntil = null;
236
- account.cooldownReason = null;
237
- }
238
-
239
- /**
240
- * Get remaining cooldown time for an account
241
- * @param {Object} account - Account object
242
- * @returns {number} Remaining cooldown time in milliseconds, 0 if not cooling down
243
- */
244
- export function getCooldownRemaining(account) {
245
- if (!account?.cooldownUntil) return 0;
246
- const remaining = account.cooldownUntil - Date.now();
247
- return remaining > 0 ? remaining : 0;
248
- }
249
-
250
- /**
251
- * Get consecutive failure count for an account
252
- * @param {Array} accounts - Array of account objects
253
- * @param {string} email - Account email
254
- * @returns {number} Consecutive failure count
255
- */
256
- export function getConsecutiveFailures(accounts, email) {
257
- const account = accounts?.find(a => a.email === email);
258
- return account?.consecutiveFailures || 0;
259
- }
260
-
261
- /**
262
- * Reset consecutive failure count for an account
263
- * @param {Array} accounts - Array of account objects
264
- * @param {string} email - Account email
265
- */
266
- export function resetConsecutiveFailures(accounts, email) {
267
- const account = accounts?.find(a => a.email === email);
268
- if (account) {
269
- account.consecutiveFailures = 0;
270
- }
271
- }
272
-
273
- /**
274
- * Increment consecutive failure count for an account
275
- * @param {Array} accounts - Array of account objects
276
- * @param {string} email - Account email
277
- * @returns {number} New failure count
278
- */
279
- export function incrementConsecutiveFailures(accounts, email) {
280
- const account = accounts?.find(a => a.email === email);
281
- if (!account) {
282
- logger.warn(`Account not found: ${email}`);
283
- return 0;
284
- }
285
-
286
- if (typeof account.consecutiveFailures !== 'number') {
287
- account.consecutiveFailures = 0;
288
- }
289
-
290
- account.consecutiveFailures++;
291
- logger.debug(`Account ${email} consecutive failures: ${account.consecutiveFailures}`);
292
- return account.consecutiveFailures;
293
- }