@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.
- package/CHANGELOG.md +76 -0
- package/README.md +28 -11
- package/bin/cli.js +15 -15
- package/docs/ACCOUNT.md +104 -0
- package/docs/API.md +26 -19
- package/docs/ARCHITECTURE.md +9 -9
- package/docs/CLAUDE_INTEGRATION.md +3 -3
- package/docs/OAUTH.md +13 -13
- package/docs/OPENCLAW.md +1 -1
- package/docs/legal.md +6 -0
- package/package.json +10 -8
- package/public/css/style.css +4 -34
- package/public/index.html +105 -166
- package/public/js/app.js +23 -58
- package/src/account-manager.js +210 -292
- package/src/cli/account.js +236 -0
- package/src/direct-api.js +7 -9
- package/src/index.js +7 -7
- package/src/middleware/credentials.js +6 -47
- package/src/oauth.js +2 -1
- package/src/routes/{accounts-route.js → account-route.js} +25 -109
- package/src/routes/api-routes.js +18 -26
- package/src/routes/chat-route.js +2 -2
- package/src/routes/messages-route.js +29 -189
- package/src/routes/models-route.js +11 -21
- package/src/security.js +1 -1
- package/src/server-settings.js +1 -8
- package/docs/ACCOUNTS.md +0 -202
- package/src/account-rotation/index.js +0 -130
- package/src/account-rotation/rate-limits.js +0 -293
- package/src/cli/accounts.js +0 -557
|
@@ -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
|
-
}
|