aicodeswitch 1.9.0 → 1.10.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/.github/workflows/publish-to-npm.yaml +34 -0
- package/CHANGELOG.md +17 -0
- package/dist/server/database.js +455 -31
- package/dist/server/main.js +60 -0
- package/dist/server/proxy-server.js +225 -39
- package/dist/ui/assets/index-D6RrKKB5.js +391 -0
- package/dist/ui/assets/index-DU8EG0kT.css +1 -0
- package/dist/ui/index.html +2 -2
- package/package.json +5 -2
- package/dist/ui/assets/index-CvBDcTGi.js +0 -391
- package/dist/ui/assets/index-vy5mPtJs.css +0 -1
package/dist/server/main.js
CHANGED
|
@@ -283,6 +283,54 @@ const registerRoutes = (dbManager, proxyServer) => {
|
|
|
283
283
|
app.put('/api/rules/:id', (req, res) => res.json(dbManager.updateRule(req.params.id, req.body)));
|
|
284
284
|
app.delete('/api/rules/:id', (req, res) => res.json(dbManager.deleteRule(req.params.id)));
|
|
285
285
|
app.put('/api/rules/:id/reset-tokens', (req, res) => res.json(dbManager.resetRuleTokenUsage(req.params.id)));
|
|
286
|
+
app.put('/api/rules/:id/reset-requests', (req, res) => res.json(dbManager.resetRuleRequestCount(req.params.id)));
|
|
287
|
+
// 解除规则的黑名单状态
|
|
288
|
+
app.put('/api/rules/:id/clear-blacklist', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
289
|
+
const { id } = req.params;
|
|
290
|
+
const rule = dbManager.getRule(id);
|
|
291
|
+
if (!rule) {
|
|
292
|
+
res.status(404).json({ error: 'Rule not found' });
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
// 找到该规则所属的路由
|
|
296
|
+
const routes = dbManager.getRoutes();
|
|
297
|
+
const route = routes.find(r => {
|
|
298
|
+
const rules = dbManager.getRules(r.id);
|
|
299
|
+
return rules.some(r => r.id === id);
|
|
300
|
+
});
|
|
301
|
+
if (!route) {
|
|
302
|
+
res.status(404).json({ error: 'Route not found' });
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
try {
|
|
306
|
+
yield dbManager.removeFromBlacklist(rule.targetServiceId, route.id, rule.contentType);
|
|
307
|
+
res.json({ success: true });
|
|
308
|
+
}
|
|
309
|
+
catch (error) {
|
|
310
|
+
console.error('Error clearing blacklist:', error);
|
|
311
|
+
res.status(500).json({ error: 'Failed to clear blacklist' });
|
|
312
|
+
}
|
|
313
|
+
})));
|
|
314
|
+
// 获取规则的黑名单状态
|
|
315
|
+
app.get('/api/rules/:routeId/blacklist-status', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
316
|
+
const { routeId } = req.params;
|
|
317
|
+
const rules = dbManager.getRules(routeId);
|
|
318
|
+
try {
|
|
319
|
+
const results = yield Promise.all(rules.map((rule) => __awaiter(void 0, void 0, void 0, function* () {
|
|
320
|
+
const blacklistStatus = yield dbManager.getRuleBlacklistStatus(rule.targetServiceId, routeId, rule.contentType);
|
|
321
|
+
return {
|
|
322
|
+
ruleId: rule.id,
|
|
323
|
+
isBlacklisted: blacklistStatus !== null,
|
|
324
|
+
blacklistEntry: blacklistStatus,
|
|
325
|
+
};
|
|
326
|
+
})));
|
|
327
|
+
res.json(results);
|
|
328
|
+
}
|
|
329
|
+
catch (error) {
|
|
330
|
+
console.error('Error getting blacklist status:', error);
|
|
331
|
+
res.status(500).json({ error: 'Failed to get blacklist status' });
|
|
332
|
+
}
|
|
333
|
+
})));
|
|
286
334
|
app.get('/api/logs', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
287
335
|
const rawLimit = typeof req.query.limit === 'string' ? parseInt(req.query.limit, 10) : NaN;
|
|
288
336
|
const rawOffset = typeof req.query.offset === 'string' ? parseInt(req.query.offset, 10) : NaN;
|
|
@@ -319,6 +367,18 @@ const registerRoutes = (dbManager, proxyServer) => {
|
|
|
319
367
|
yield dbManager.clearErrorLogs();
|
|
320
368
|
res.json(true);
|
|
321
369
|
})));
|
|
370
|
+
app.get('/api/logs/count', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
371
|
+
const count = yield dbManager.getLogsCount();
|
|
372
|
+
res.json({ count });
|
|
373
|
+
})));
|
|
374
|
+
app.get('/api/access-logs/count', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
375
|
+
const count = yield dbManager.getAccessLogsCount();
|
|
376
|
+
res.json({ count });
|
|
377
|
+
})));
|
|
378
|
+
app.get('/api/error-logs/count', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
379
|
+
const count = yield dbManager.getErrorLogsCount();
|
|
380
|
+
res.json({ count });
|
|
381
|
+
})));
|
|
322
382
|
app.get('/api/config', (_req, res) => res.json(dbManager.getConfig()));
|
|
323
383
|
app.put('/api/config', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
324
384
|
const config = req.body;
|
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
36
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
37
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -19,6 +52,18 @@ const streaming_1 = require("./transformers/streaming");
|
|
|
19
52
|
const chunk_collector_1 = require("./transformers/chunk-collector");
|
|
20
53
|
const claude_openai_1 = require("./transformers/claude-openai");
|
|
21
54
|
const SUPPORTED_TARGETS = ['claude-code', 'codex'];
|
|
55
|
+
// 需要排除的路径模式(非业务请求)
|
|
56
|
+
const IGNORED_PATHS = [
|
|
57
|
+
'/favicon.ico',
|
|
58
|
+
'/robots.txt',
|
|
59
|
+
'/sitemap.xml',
|
|
60
|
+
];
|
|
61
|
+
/**
|
|
62
|
+
* 检查路径是否应该被忽略
|
|
63
|
+
*/
|
|
64
|
+
function shouldIgnorePath(path) {
|
|
65
|
+
return IGNORED_PATHS.some(ignored => path === ignored || path.startsWith(ignored + '?'));
|
|
66
|
+
}
|
|
22
67
|
class ProxyServer {
|
|
23
68
|
constructor(dbManager, app) {
|
|
24
69
|
Object.defineProperty(this, "app", {
|
|
@@ -67,6 +112,10 @@ class ProxyServer {
|
|
|
67
112
|
// Access logging middleware
|
|
68
113
|
this.app.use((req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
69
114
|
var _a;
|
|
115
|
+
// 忽略非业务请求
|
|
116
|
+
if (shouldIgnorePath(req.path)) {
|
|
117
|
+
return next();
|
|
118
|
+
}
|
|
70
119
|
// Capture client info
|
|
71
120
|
const clientIp = ((_a = req.headers['x-forwarded-for']) === null || _a === void 0 ? void 0 : _a.split(',')[0]) || req.socket.remoteAddress || '';
|
|
72
121
|
const userAgent = req.headers['user-agent'] || '';
|
|
@@ -80,35 +129,61 @@ class ProxyServer {
|
|
|
80
129
|
clientIp,
|
|
81
130
|
userAgent,
|
|
82
131
|
});
|
|
83
|
-
|
|
84
|
-
res.send = originalSend;
|
|
132
|
+
const updateLog = (res, data) => {
|
|
85
133
|
const responseTime = Date.now() - startTime;
|
|
86
134
|
accessLog.then((accessLogId) => {
|
|
87
|
-
|
|
135
|
+
const updateData = {
|
|
88
136
|
responseTime,
|
|
89
|
-
|
|
90
|
-
|
|
137
|
+
};
|
|
138
|
+
let errorMessage = '';
|
|
139
|
+
if (res instanceof Error) {
|
|
140
|
+
updateData.error = res.message;
|
|
141
|
+
errorMessage = res.message;
|
|
142
|
+
}
|
|
143
|
+
else if (res.statusCode >= 400) {
|
|
144
|
+
updateData.statusCode = res.statusCode;
|
|
145
|
+
updateData.error = res.statusMessage;
|
|
146
|
+
// @ts-ignore
|
|
147
|
+
updateData.responseHeaders = this.normalizeResponseHeaders(res.headers || {});
|
|
148
|
+
errorMessage = res.statusMessage;
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
updateData.statusCode = res.statusCode;
|
|
152
|
+
// @ts-ignore
|
|
153
|
+
updateData.responseHeaders = this.normalizeResponseHeaders(res.headers || {});
|
|
154
|
+
updateData.responseBody = data ? JSON.stringify(data) : undefined;
|
|
155
|
+
}
|
|
156
|
+
this.dbManager.updateAccessLog(accessLogId, updateData);
|
|
157
|
+
// 记录错误日志
|
|
158
|
+
if (res instanceof Error || res.statusCode >= 400) {
|
|
159
|
+
this.dbManager.addErrorLog({
|
|
160
|
+
timestamp: Date.now(),
|
|
161
|
+
method: req.method,
|
|
162
|
+
path: req.path,
|
|
163
|
+
statusCode: 503,
|
|
164
|
+
errorMessage,
|
|
165
|
+
requestHeaders: this.normalizeHeaders(req.headers),
|
|
166
|
+
requestBody: req.body ? JSON.stringify(req.body) : undefined,
|
|
167
|
+
responseBody: data ? JSON.stringify(data) : undefined,
|
|
168
|
+
// @ts-ignore
|
|
169
|
+
responseHeaders: res.headers ? this.normalizeResponseHeaders(res.headers) : undefined,
|
|
170
|
+
responseTime,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
91
173
|
});
|
|
174
|
+
};
|
|
175
|
+
res.send = (data) => {
|
|
176
|
+
res.send = originalSend;
|
|
177
|
+
updateLog(res, data);
|
|
92
178
|
return originalSend(data);
|
|
93
179
|
};
|
|
94
180
|
res.json = (data) => {
|
|
95
181
|
res.json = originalJson;
|
|
96
|
-
|
|
97
|
-
accessLog.then((accessLogId) => {
|
|
98
|
-
this.dbManager.updateAccessLog(accessLogId, {
|
|
99
|
-
responseTime,
|
|
100
|
-
statusCode: res.statusCode,
|
|
101
|
-
});
|
|
102
|
-
});
|
|
182
|
+
updateLog(res, data);
|
|
103
183
|
return originalJson(data);
|
|
104
184
|
};
|
|
105
185
|
res.on('error', (err) => {
|
|
106
|
-
|
|
107
|
-
this.dbManager.updateAccessLog(accessLogId, {
|
|
108
|
-
statusCode: res.statusCode,
|
|
109
|
-
error: err.message,
|
|
110
|
-
});
|
|
111
|
-
});
|
|
186
|
+
updateLog(err);
|
|
112
187
|
});
|
|
113
188
|
next();
|
|
114
189
|
}));
|
|
@@ -116,7 +191,8 @@ class ProxyServer {
|
|
|
116
191
|
this.app.use((req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
117
192
|
const startTime = Date.now();
|
|
118
193
|
const originalSend = res.send.bind(res);
|
|
119
|
-
|
|
194
|
+
// 忽略非业务请求,并且只记录支持的编程工具请求
|
|
195
|
+
if (!shouldIgnorePath(req.path) && SUPPORTED_TARGETS.some(target => req.path.startsWith(`/${target}/`))) {
|
|
120
196
|
res.send = (data) => {
|
|
121
197
|
var _a;
|
|
122
198
|
res.send = originalSend;
|
|
@@ -144,7 +220,7 @@ class ProxyServer {
|
|
|
144
220
|
this.app.use('/codex', this.createFixedRouteHandler('codex'));
|
|
145
221
|
// Dynamic proxy middleware
|
|
146
222
|
this.app.use((req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
147
|
-
var _a, _b, _c, _d;
|
|
223
|
+
var _a, _b, _c, _d, _e;
|
|
148
224
|
// 根路径 / 不应该被代理中间件处理,应该传递给静态文件服务
|
|
149
225
|
if (req.path === '/') {
|
|
150
226
|
return next();
|
|
@@ -194,11 +270,24 @@ class ProxyServer {
|
|
|
194
270
|
catch (error) {
|
|
195
271
|
console.error(`Service ${service.name} failed:`, error.message);
|
|
196
272
|
lastError = error;
|
|
197
|
-
//
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
273
|
+
// 检测是否是 timeout 错误
|
|
274
|
+
const isTimeout = error.code === 'ECONNABORTED' ||
|
|
275
|
+
((_b = error.message) === null || _b === void 0 ? void 0 : _b.toLowerCase().includes('timeout')) ||
|
|
276
|
+
(error.errno && error.errno === 'ETIMEDOUT');
|
|
277
|
+
// 判断错误类型并加入黑名单
|
|
278
|
+
if (isTimeout) {
|
|
279
|
+
// Timeout错误,加入黑名单
|
|
280
|
+
yield this.dbManager.addToBlacklist(service.id, route.id, rule.contentType, 'Request timeout - the upstream API took too long to respond', undefined, // timeout没有HTTP状态码
|
|
281
|
+
'timeout');
|
|
282
|
+
console.log(`Service ${service.name} added to blacklist due to timeout (${route.id}:${rule.contentType}:${service.id})`);
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
// HTTP错误,检查状态码
|
|
286
|
+
const statusCode = ((_c = error.response) === null || _c === void 0 ? void 0 : _c.status) || 500;
|
|
287
|
+
if (statusCode >= 400) {
|
|
288
|
+
yield this.dbManager.addToBlacklist(service.id, route.id, rule.contentType, error.message, statusCode, 'http');
|
|
289
|
+
console.log(`Service ${service.name} added to blacklist due to HTTP error ${statusCode} (${route.id}:${rule.contentType}:${service.id})`);
|
|
290
|
+
}
|
|
202
291
|
}
|
|
203
292
|
// 继续尝试下一个服务
|
|
204
293
|
continue;
|
|
@@ -207,7 +296,7 @@ class ProxyServer {
|
|
|
207
296
|
// 所有服务都失败了
|
|
208
297
|
console.error('All services failed');
|
|
209
298
|
// 记录日志
|
|
210
|
-
if (((
|
|
299
|
+
if (((_d = this.config) === null || _d === void 0 ? void 0 : _d.enableLogging) && SUPPORTED_TARGETS.some(target => req.path.startsWith(`/${target}/`))) {
|
|
211
300
|
yield this.dbManager.addLog({
|
|
212
301
|
timestamp: Date.now(),
|
|
213
302
|
method: req.method,
|
|
@@ -249,7 +338,7 @@ class ProxyServer {
|
|
|
249
338
|
}
|
|
250
339
|
catch (error) {
|
|
251
340
|
console.error('Proxy error:', error);
|
|
252
|
-
if (((
|
|
341
|
+
if (((_e = this.config) === null || _e === void 0 ? void 0 : _e.enableLogging) && SUPPORTED_TARGETS.some(target => req.path.startsWith(`/${target}/`))) {
|
|
253
342
|
yield this.dbManager.addLog({
|
|
254
343
|
timestamp: Date.now(),
|
|
255
344
|
method: req.method,
|
|
@@ -290,7 +379,7 @@ class ProxyServer {
|
|
|
290
379
|
}
|
|
291
380
|
createFixedRouteHandler(targetType) {
|
|
292
381
|
return (req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
293
|
-
var _a, _b, _c, _d;
|
|
382
|
+
var _a, _b, _c, _d, _e;
|
|
294
383
|
try {
|
|
295
384
|
// 检查API Key验证
|
|
296
385
|
if (this.config.apiKey) {
|
|
@@ -344,11 +433,24 @@ class ProxyServer {
|
|
|
344
433
|
catch (error) {
|
|
345
434
|
console.error(`Service ${service.name} failed:`, error.message);
|
|
346
435
|
lastError = error;
|
|
347
|
-
//
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
436
|
+
// 检测是否是 timeout 错误
|
|
437
|
+
const isTimeout = error.code === 'ECONNABORTED' ||
|
|
438
|
+
((_b = error.message) === null || _b === void 0 ? void 0 : _b.toLowerCase().includes('timeout')) ||
|
|
439
|
+
(error.errno && error.errno === 'ETIMEDOUT');
|
|
440
|
+
// 判断错误类型并加入黑名单
|
|
441
|
+
if (isTimeout) {
|
|
442
|
+
// Timeout错误,加入黑名单
|
|
443
|
+
yield this.dbManager.addToBlacklist(service.id, route.id, rule.contentType, 'Request timeout - the upstream API took too long to respond', undefined, // timeout没有HTTP状态码
|
|
444
|
+
'timeout');
|
|
445
|
+
console.log(`Service ${service.name} added to blacklist due to timeout (${route.id}:${rule.contentType}:${service.id})`);
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
448
|
+
// HTTP错误,检查状态码
|
|
449
|
+
const statusCode = ((_c = error.response) === null || _c === void 0 ? void 0 : _c.status) || 500;
|
|
450
|
+
if (statusCode >= 400) {
|
|
451
|
+
yield this.dbManager.addToBlacklist(service.id, route.id, rule.contentType, error.message, statusCode, 'http');
|
|
452
|
+
console.log(`Service ${service.name} added to blacklist due to HTTP error ${statusCode} (${route.id}:${rule.contentType}:${service.id})`);
|
|
453
|
+
}
|
|
352
454
|
}
|
|
353
455
|
// 继续尝试下一个服务
|
|
354
456
|
continue;
|
|
@@ -357,7 +459,7 @@ class ProxyServer {
|
|
|
357
459
|
// 所有服务都失败了
|
|
358
460
|
console.error('All services failed');
|
|
359
461
|
// 记录日志
|
|
360
|
-
if (((
|
|
462
|
+
if (((_d = this.config) === null || _d === void 0 ? void 0 : _d.enableLogging) && SUPPORTED_TARGETS.some(target => req.path.startsWith(`/${target}/`))) {
|
|
361
463
|
yield this.dbManager.addLog({
|
|
362
464
|
timestamp: Date.now(),
|
|
363
465
|
method: req.method,
|
|
@@ -399,7 +501,7 @@ class ProxyServer {
|
|
|
399
501
|
}
|
|
400
502
|
catch (error) {
|
|
401
503
|
console.error(`Fixed route error for ${targetType}:`, error);
|
|
402
|
-
if (((
|
|
504
|
+
if (((_e = this.config) === null || _e === void 0 ? void 0 : _e.enableLogging) && SUPPORTED_TARGETS.some(target => req.path.startsWith(`/${target}/`))) {
|
|
403
505
|
yield this.dbManager.addLog({
|
|
404
506
|
timestamp: Date.now(),
|
|
405
507
|
method: req.method,
|
|
@@ -450,6 +552,29 @@ class ProxyServer {
|
|
|
450
552
|
const activeRoutes = this.getActiveRoutes();
|
|
451
553
|
return activeRoutes.find(route => route.targetType === targetType && route.isActive);
|
|
452
554
|
}
|
|
555
|
+
/**
|
|
556
|
+
* 根据GLM计费逻辑判断请求是否应该计费
|
|
557
|
+
* 核心规则:
|
|
558
|
+
* 1. 最后一条消息必须是 role: "user"
|
|
559
|
+
* 2. 上一条消息不能是包含 tool_calls 的 assistant 消息(即不是工具回传)
|
|
560
|
+
*/
|
|
561
|
+
shouldChargeRequest(requestBody) {
|
|
562
|
+
const messages = requestBody === null || requestBody === void 0 ? void 0 : requestBody.messages;
|
|
563
|
+
if (!messages || !Array.isArray(messages) || messages.length === 0) {
|
|
564
|
+
return false;
|
|
565
|
+
}
|
|
566
|
+
const lastMessage = messages[messages.length - 1];
|
|
567
|
+
if (lastMessage.role !== 'user') {
|
|
568
|
+
return false;
|
|
569
|
+
}
|
|
570
|
+
if (messages.length > 1) {
|
|
571
|
+
const previousMessage = messages[messages.length - 2];
|
|
572
|
+
if (previousMessage.role === 'assistant' && previousMessage.tool_calls) {
|
|
573
|
+
return false;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
return true;
|
|
577
|
+
}
|
|
453
578
|
/**
|
|
454
579
|
* 从数据库实时获取服务配置
|
|
455
580
|
* @param serviceId 服务ID
|
|
@@ -484,10 +609,15 @@ class ProxyServer {
|
|
|
484
609
|
}
|
|
485
610
|
// 检查并重置到期的规则
|
|
486
611
|
this.dbManager.checkAndResetRuleIfNeeded(rule.id);
|
|
612
|
+
this.dbManager.checkAndResetRequestCountIfNeeded(rule.id);
|
|
487
613
|
// 检查token限制
|
|
488
614
|
if (rule.tokenLimit && rule.totalTokensUsed !== undefined && rule.totalTokensUsed >= rule.tokenLimit) {
|
|
489
615
|
continue; // 跳过超限规则
|
|
490
616
|
}
|
|
617
|
+
// 检查请求次数限制
|
|
618
|
+
if (rule.requestCountLimit && rule.totalRequestsUsed !== undefined && rule.totalRequestsUsed >= rule.requestCountLimit) {
|
|
619
|
+
continue; // 跳过超限规则
|
|
620
|
+
}
|
|
491
621
|
return rule;
|
|
492
622
|
}
|
|
493
623
|
}
|
|
@@ -502,10 +632,15 @@ class ProxyServer {
|
|
|
502
632
|
}
|
|
503
633
|
// 检查并重置到期的规则
|
|
504
634
|
this.dbManager.checkAndResetRuleIfNeeded(rule.id);
|
|
635
|
+
this.dbManager.checkAndResetRequestCountIfNeeded(rule.id);
|
|
505
636
|
// 检查token限制
|
|
506
637
|
if (rule.tokenLimit && rule.totalTokensUsed !== undefined && rule.totalTokensUsed >= rule.tokenLimit) {
|
|
507
638
|
continue; // 跳过超限规则
|
|
508
639
|
}
|
|
640
|
+
// 检查请求次数限制
|
|
641
|
+
if (rule.requestCountLimit && rule.totalRequestsUsed !== undefined && rule.totalRequestsUsed >= rule.requestCountLimit) {
|
|
642
|
+
continue; // 跳过超限规则
|
|
643
|
+
}
|
|
509
644
|
return rule;
|
|
510
645
|
}
|
|
511
646
|
// 3. 最后返回 default 规则
|
|
@@ -518,10 +653,15 @@ class ProxyServer {
|
|
|
518
653
|
}
|
|
519
654
|
// 检查并重置到期的规则
|
|
520
655
|
this.dbManager.checkAndResetRuleIfNeeded(rule.id);
|
|
656
|
+
this.dbManager.checkAndResetRequestCountIfNeeded(rule.id);
|
|
521
657
|
// 检查token限制
|
|
522
658
|
if (rule.tokenLimit && rule.totalTokensUsed !== undefined && rule.totalTokensUsed >= rule.tokenLimit) {
|
|
523
659
|
continue; // 跳过超限规则
|
|
524
660
|
}
|
|
661
|
+
// 检查请求次数限制
|
|
662
|
+
if (rule.requestCountLimit && rule.totalRequestsUsed !== undefined && rule.totalRequestsUsed >= rule.requestCountLimit) {
|
|
663
|
+
continue; // 跳过超限规则
|
|
664
|
+
}
|
|
525
665
|
return rule;
|
|
526
666
|
}
|
|
527
667
|
return undefined;
|
|
@@ -551,12 +691,22 @@ class ProxyServer {
|
|
|
551
691
|
// 4. 检查并重置到期的规则
|
|
552
692
|
candidates.forEach(rule => {
|
|
553
693
|
this.dbManager.checkAndResetRuleIfNeeded(rule.id);
|
|
694
|
+
this.dbManager.checkAndResetRequestCountIfNeeded(rule.id);
|
|
554
695
|
});
|
|
555
|
-
// 5.
|
|
696
|
+
// 5. 过滤掉超过限制的规则(仅在有多个候选规则时)
|
|
556
697
|
if (candidates.length > 1) {
|
|
557
698
|
const filteredCandidates = candidates.filter(rule => {
|
|
699
|
+
// 检查token限制
|
|
558
700
|
if (rule.tokenLimit && rule.totalTokensUsed !== undefined) {
|
|
559
|
-
|
|
701
|
+
if (rule.totalTokensUsed >= rule.tokenLimit) {
|
|
702
|
+
return false;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
// 检查请求次数限制
|
|
706
|
+
if (rule.requestCountLimit && rule.totalRequestsUsed !== undefined) {
|
|
707
|
+
if (rule.totalRequestsUsed >= rule.requestCountLimit) {
|
|
708
|
+
return false;
|
|
709
|
+
}
|
|
560
710
|
}
|
|
561
711
|
return true; // 没有设置限制的规则总是可用
|
|
562
712
|
});
|
|
@@ -1042,6 +1192,7 @@ class ProxyServer {
|
|
|
1042
1192
|
let responseBodyForLog;
|
|
1043
1193
|
let streamChunksForLog;
|
|
1044
1194
|
let upstreamRequestForLog;
|
|
1195
|
+
let actuallyUsedProxy = false; // 标记是否实际使用了代理
|
|
1045
1196
|
const finalizeLog = (statusCode, error) => __awaiter(this, void 0, void 0, function* () {
|
|
1046
1197
|
var _a, _b;
|
|
1047
1198
|
if (logged || !((_a = this.config) === null || _a === void 0 ? void 0 : _a.enableLogging))
|
|
@@ -1088,6 +1239,10 @@ class ProxyServer {
|
|
|
1088
1239
|
this.dbManager.incrementRuleTokenUsage(rule.id, totalTokens);
|
|
1089
1240
|
}
|
|
1090
1241
|
}
|
|
1242
|
+
// 更新规则的请求次数(只在成功请求时更新)
|
|
1243
|
+
if (statusCode < 400 && this.shouldChargeRequest(req.body)) {
|
|
1244
|
+
this.dbManager.incrementRuleRequestCount(rule.id, 1);
|
|
1245
|
+
}
|
|
1091
1246
|
});
|
|
1092
1247
|
try {
|
|
1093
1248
|
if (targetType === 'claude-code') {
|
|
@@ -1143,6 +1298,35 @@ class ProxyServer {
|
|
|
1143
1298
|
if (['POST', 'PUT', 'PATCH'].includes(req.method.toUpperCase())) {
|
|
1144
1299
|
config.data = requestBody;
|
|
1145
1300
|
}
|
|
1301
|
+
// 应用代理配置
|
|
1302
|
+
if (service.enableProxy) {
|
|
1303
|
+
const appConfig = this.dbManager.getConfig();
|
|
1304
|
+
if (appConfig.proxyEnabled && appConfig.proxyUrl) {
|
|
1305
|
+
try {
|
|
1306
|
+
const { HttpsProxyAgent } = yield Promise.resolve().then(() => __importStar(require('https-proxy-agent')));
|
|
1307
|
+
const proxyAuth = appConfig.proxyUsername && appConfig.proxyPassword
|
|
1308
|
+
? `${appConfig.proxyUsername}:${appConfig.proxyPassword}@`
|
|
1309
|
+
: '';
|
|
1310
|
+
let proxyUrl = appConfig.proxyUrl;
|
|
1311
|
+
if (!proxyUrl.startsWith('http://') && !proxyUrl.startsWith('https://')) {
|
|
1312
|
+
proxyUrl = `http://${proxyAuth}${proxyUrl}`;
|
|
1313
|
+
}
|
|
1314
|
+
else if (proxyAuth) {
|
|
1315
|
+
// 如果 URL 已经包含协议,需要插入认证信息
|
|
1316
|
+
const urlObj = new URL(proxyUrl);
|
|
1317
|
+
urlObj.username = appConfig.proxyUsername;
|
|
1318
|
+
urlObj.password = appConfig.proxyPassword;
|
|
1319
|
+
proxyUrl = urlObj.toString();
|
|
1320
|
+
}
|
|
1321
|
+
config.httpsAgent = new HttpsProxyAgent(proxyUrl);
|
|
1322
|
+
config.httpAgent = new HttpsProxyAgent(proxyUrl);
|
|
1323
|
+
actuallyUsedProxy = true; // 标记实际使用了代理
|
|
1324
|
+
}
|
|
1325
|
+
catch (error) {
|
|
1326
|
+
console.error('[Proxy] Failed to create proxy agent:', error);
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1146
1330
|
// 记录实际发出的请求信息作为日志的一部分
|
|
1147
1331
|
const actualModel = (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model) || '';
|
|
1148
1332
|
const maxTokensFieldName = this.getMaxTokensFieldName(actualModel);
|
|
@@ -1150,9 +1334,11 @@ class ProxyServer {
|
|
|
1150
1334
|
upstreamRequestForLog = {
|
|
1151
1335
|
url: `${service.apiUrl}${mappedPath}`,
|
|
1152
1336
|
model: actualModel,
|
|
1153
|
-
|
|
1154
|
-
maxTokensField: maxTokensFieldName,
|
|
1337
|
+
[maxTokensFieldName]: actualMaxTokens,
|
|
1155
1338
|
};
|
|
1339
|
+
if (actuallyUsedProxy) {
|
|
1340
|
+
upstreamRequestForLog.useProxy = true;
|
|
1341
|
+
}
|
|
1156
1342
|
const response = yield (0, axios_1.default)(config);
|
|
1157
1343
|
const responseHeaders = response.headers || {};
|
|
1158
1344
|
const contentType = typeof responseHeaders['content-type'] === 'string' ? responseHeaders['content-type'] : '';
|