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.
@@ -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
- res.send = (data) => {
84
- res.send = originalSend;
132
+ const updateLog = (res, data) => {
85
133
  const responseTime = Date.now() - startTime;
86
134
  accessLog.then((accessLogId) => {
87
- this.dbManager.updateAccessLog(accessLogId, {
135
+ const updateData = {
88
136
  responseTime,
89
- statusCode: res.statusCode,
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
- const responseTime = Date.now() - startTime;
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
- accessLog.then((accessLogId) => {
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
- if (SUPPORTED_TARGETS.some(target => req.path.startsWith(`/${target}/`))) {
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
- // 判断是否应该加入黑名单 (4xx + 5xx)
198
- const statusCode = ((_b = error.response) === null || _b === void 0 ? void 0 : _b.status) || 500;
199
- if (statusCode >= 400) {
200
- yield this.dbManager.addToBlacklist(service.id, route.id, rule.contentType, error.message, statusCode);
201
- console.log(`Service ${service.name} added to blacklist (${route.id}:${rule.contentType}:${service.id})`);
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 (((_c = this.config) === null || _c === void 0 ? void 0 : _c.enableLogging) && SUPPORTED_TARGETS.some(target => req.path.startsWith(`/${target}/`))) {
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 (((_d = this.config) === null || _d === void 0 ? void 0 : _d.enableLogging) && SUPPORTED_TARGETS.some(target => req.path.startsWith(`/${target}/`))) {
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
- // 判断是否应该加入黑名单 (4xx + 5xx)
348
- const statusCode = ((_b = error.response) === null || _b === void 0 ? void 0 : _b.status) || 500;
349
- if (statusCode >= 400) {
350
- yield this.dbManager.addToBlacklist(service.id, route.id, rule.contentType, error.message, statusCode);
351
- console.log(`Service ${service.name} added to blacklist (${route.id}:${rule.contentType}:${service.id})`);
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 (((_c = this.config) === null || _c === void 0 ? void 0 : _c.enableLogging) && SUPPORTED_TARGETS.some(target => req.path.startsWith(`/${target}/`))) {
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 (((_d = this.config) === null || _d === void 0 ? void 0 : _d.enableLogging) && SUPPORTED_TARGETS.some(target => req.path.startsWith(`/${target}/`))) {
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. 过滤掉超过token限制的规则(仅在有多个候选规则时)
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
- return rule.totalTokensUsed < rule.tokenLimit;
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
- maxTokens: actualMaxTokens,
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'] : '';