aicodeswitch 1.0.0 → 1.1.1

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.
@@ -19,6 +19,7 @@ const streaming_1 = require("./transformers/streaming");
19
19
  const chunk_collector_1 = require("./transformers/chunk-collector");
20
20
  const claude_openai_1 = require("./transformers/claude-openai");
21
21
  const openai_responses_1 = require("./transformers/openai-responses");
22
+ const SUPPORTED_TARGETS = ['claude-code', 'codex'];
22
23
  class ProxyServer {
23
24
  constructor(dbManager, app) {
24
25
  Object.defineProperty(this, "app", {
@@ -68,39 +69,71 @@ class ProxyServer {
68
69
  // Capture client info
69
70
  const clientIp = ((_a = req.headers['x-forwarded-for']) === null || _a === void 0 ? void 0 : _a.split(',')[0]) || req.socket.remoteAddress || '';
70
71
  const userAgent = req.headers['user-agent'] || '';
71
- this.dbManager.addAccessLog({
72
+ const startTime = Date.now();
73
+ const originalSend = res.send.bind(res);
74
+ const originalJson = res.json.bind(res);
75
+ const accessLog = this.dbManager.addAccessLog({
72
76
  timestamp: Date.now(),
73
77
  method: req.method,
74
78
  path: req.path,
75
- headers: this.normalizeHeaders(req.headers),
76
- body: req.body ? JSON.stringify(req.body) : undefined,
77
79
  clientIp,
78
80
  userAgent,
79
- statusCode: res.statusCode,
80
81
  });
81
- next();
82
- }));
83
- // Logging middleware (legacy RequestLog)
84
- this.app.use((req, res, next) => __awaiter(this, void 0, void 0, function* () {
85
- const startTime = Date.now();
86
- const originalSend = res.send.bind(res);
87
82
  res.send = (data) => {
88
- var _a;
89
83
  res.send = originalSend;
90
- if (!res.locals.skipLog && ((_a = this.config) === null || _a === void 0 ? void 0 : _a.enableLogging)) {
91
- const responseTime = Date.now() - startTime;
92
- this.dbManager.addLog({
93
- timestamp: Date.now(),
94
- method: req.method,
95
- path: req.path,
96
- headers: this.normalizeHeaders(req.headers),
97
- body: req.body ? JSON.stringify(req.body) : undefined,
84
+ const responseTime = Date.now() - startTime;
85
+ accessLog.then((accessLogId) => {
86
+ this.dbManager.updateAccessLog(accessLogId, {
87
+ responseTime,
98
88
  statusCode: res.statusCode,
89
+ });
90
+ });
91
+ return originalSend(data);
92
+ };
93
+ res.json = (data) => {
94
+ res.json = originalJson;
95
+ const responseTime = Date.now() - startTime;
96
+ accessLog.then((accessLogId) => {
97
+ this.dbManager.updateAccessLog(accessLogId, {
99
98
  responseTime,
99
+ statusCode: res.statusCode,
100
100
  });
101
- }
102
- return res.send(data);
101
+ });
102
+ return originalJson(data);
103
103
  };
104
+ res.on('error', (err) => {
105
+ accessLog.then((accessLogId) => {
106
+ this.dbManager.updateAccessLog(accessLogId, {
107
+ statusCode: res.statusCode,
108
+ error: err.message,
109
+ });
110
+ });
111
+ });
112
+ next();
113
+ }));
114
+ // Logging middleware (legacy RequestLog)
115
+ this.app.use((req, res, next) => __awaiter(this, void 0, void 0, function* () {
116
+ const startTime = Date.now();
117
+ const originalSend = res.send.bind(res);
118
+ if (SUPPORTED_TARGETS.some(target => req.path.startsWith(`/${target}/`))) {
119
+ res.send = (data) => {
120
+ var _a;
121
+ res.send = originalSend;
122
+ if (!res.locals.skipLog && ((_a = this.config) === null || _a === void 0 ? void 0 : _a.enableLogging) && SUPPORTED_TARGETS.some(target => req.path.startsWith(`/${target}/`))) {
123
+ const responseTime = Date.now() - startTime;
124
+ this.dbManager.addLog({
125
+ timestamp: Date.now(),
126
+ method: req.method,
127
+ path: req.path,
128
+ headers: this.normalizeHeaders(req.headers),
129
+ body: req.body ? JSON.stringify(req.body) : undefined,
130
+ statusCode: res.statusCode,
131
+ responseTime,
132
+ });
133
+ }
134
+ return res.send(data);
135
+ };
136
+ }
104
137
  next();
105
138
  }));
106
139
  // Fixed route handlers
@@ -109,8 +142,12 @@ class ProxyServer {
109
142
  this.app.use('/codex/', this.createFixedRouteHandler('codex'));
110
143
  this.app.use('/codex', this.createFixedRouteHandler('codex'));
111
144
  // Dynamic proxy middleware
112
- this.app.use((req, res, _next) => __awaiter(this, void 0, void 0, function* () {
145
+ this.app.use((req, res, next) => __awaiter(this, void 0, void 0, function* () {
113
146
  var _a;
147
+ // 根路径 / 不应该被代理中间件处理,应该传递给静态文件服务
148
+ if (req.path === '/') {
149
+ return next();
150
+ }
114
151
  try {
115
152
  const route = this.findMatchingRoute(req);
116
153
  if (!route) {
@@ -128,7 +165,7 @@ class ProxyServer {
128
165
  }
129
166
  catch (error) {
130
167
  console.error('Proxy error:', error);
131
- if ((_a = this.config) === null || _a === void 0 ? void 0 : _a.enableLogging) {
168
+ if (((_a = this.config) === null || _a === void 0 ? void 0 : _a.enableLogging) && SUPPORTED_TARGETS.some(target => req.path.startsWith(`/${target}/`))) {
132
169
  yield this.dbManager.addLog({
133
170
  timestamp: Date.now(),
134
171
  method: req.method,
@@ -181,7 +218,7 @@ class ProxyServer {
181
218
  }
182
219
  catch (error) {
183
220
  console.error(`Fixed route error for ${targetType}:`, error);
184
- if ((_a = this.config) === null || _a === void 0 ? void 0 : _a.enableLogging) {
221
+ if (((_a = this.config) === null || _a === void 0 ? void 0 : _a.enableLogging) && SUPPORTED_TARGETS.some(target => req.path.startsWith(`/${target}/`))) {
185
222
  yield this.dbManager.addLog({
186
223
  timestamp: Date.now(),
187
224
  method: req.method,
@@ -218,9 +255,29 @@ class ProxyServer {
218
255
  const rules = this.rules.get(routeId);
219
256
  if (!rules)
220
257
  return undefined;
221
- // Determine content type from request
258
+ const body = req.body;
259
+ const requestModel = body === null || body === void 0 ? void 0 : body.model;
260
+ // 1. 首先查找 model-mapping 类型的规则,按 sortOrder 降序匹配
261
+ if (requestModel) {
262
+ const modelMappingRules = rules.filter(rule => rule.contentType === 'model-mapping' &&
263
+ rule.replacedModel &&
264
+ requestModel.includes(rule.replacedModel));
265
+ if (modelMappingRules.length > 0) {
266
+ return modelMappingRules[0]; // 已按 sortOrder 降序排序
267
+ }
268
+ }
269
+ // 2. 查找其他内容类型的规则
222
270
  const contentType = this.determineContentType(req);
223
- return rules.find(rule => rule.contentType === contentType) || rules.find(rule => rule.contentType === 'default');
271
+ const contentTypeRules = rules.filter(rule => rule.contentType === contentType);
272
+ if (contentTypeRules.length > 0) {
273
+ return contentTypeRules[0]; // 已按 sortOrder 降序排序
274
+ }
275
+ // 3. 最后返回 default 规则
276
+ const defaultRules = rules.filter(rule => rule.contentType === 'default');
277
+ if (defaultRules.length > 0) {
278
+ return defaultRules[0]; // 已按 sortOrder 降序排序
279
+ }
280
+ return undefined;
224
281
  }
225
282
  determineContentType(req) {
226
283
  const body = req.body;
@@ -596,10 +653,15 @@ class ProxyServer {
596
653
  let responseBodyForLog;
597
654
  let streamChunksForLog;
598
655
  const finalizeLog = (statusCode, error) => __awaiter(this, void 0, void 0, function* () {
599
- var _a;
656
+ var _a, _b;
600
657
  if (logged || !((_a = this.config) === null || _a === void 0 ? void 0 : _a.enableLogging))
601
658
  return;
602
659
  logged = true;
660
+ // 获取供应商信息
661
+ const vendors = this.dbManager.getVendors();
662
+ const vendor = vendors.find(v => v.id === service.vendorId);
663
+ // 从请求体中提取模型信息
664
+ const requestModel = (_b = req.body) === null || _b === void 0 ? void 0 : _b.model;
603
665
  yield this.dbManager.addLog({
604
666
  timestamp: Date.now(),
605
667
  method: req.method,
@@ -616,6 +678,9 @@ class ProxyServer {
616
678
  targetServiceId: service.id,
617
679
  targetServiceName: service.name,
618
680
  targetModel: rule.targetModel,
681
+ vendorId: service.vendorId,
682
+ vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
683
+ requestModel,
619
684
  responseHeaders: responseHeadersForLog,
620
685
  responseBody: responseBodyForLog,
621
686
  streamChunks: streamChunksForLog,
@@ -780,6 +845,26 @@ class ProxyServer {
780
845
  this.copyResponseHeaders(responseHeaders, res);
781
846
  res.on('finish', () => {
782
847
  streamChunksForLog = chunkCollector.getChunks();
848
+ // 尝试从stream chunks中解析usage信息
849
+ if (streamChunksForLog && streamChunksForLog.length > 0) {
850
+ // 合并所有chunks并尝试解析usage
851
+ const allChunks = streamChunksForLog.join('');
852
+ // 查找包含usage信息的部分
853
+ const usageMatch = allChunks.match(/usage[\s\S]*?\{[\s\S]*?\}/);
854
+ if (usageMatch) {
855
+ try {
856
+ // 尝试解析usage信息
857
+ const usageStr = usageMatch[0];
858
+ const jsonStart = usageStr.indexOf('{');
859
+ const jsonEnd = usageStr.lastIndexOf('}') + 1;
860
+ const usageJson = JSON.parse(usageStr.slice(jsonStart, jsonEnd));
861
+ usageForLog = this.extractTokenUsage(usageJson);
862
+ }
863
+ catch (e) {
864
+ console.error('Failed to parse usage from stream chunks:', e);
865
+ }
866
+ }
867
+ }
783
868
  void finalizeLog(res.statusCode);
784
869
  });
785
870
  (0, stream_1.pipeline)(response.data, chunkCollector, res, (error) => {
@@ -853,7 +938,25 @@ class ProxyServer {
853
938
  catch (error) {
854
939
  console.error('Proxy error:', error);
855
940
  yield finalizeLog(500, error.message);
856
- res.status(500).json({ error: error.message });
941
+ // 根据请求类型返回适当格式的错误响应
942
+ const streamRequested = this.isStreamRequested(req, req.body || {});
943
+ if (streamRequested && route.targetType === 'claude-code') {
944
+ // 对于 Claude Code 的流式请求,返回 SSE 格式的错误响应
945
+ res.setHeader('Content-Type', 'text/event-stream');
946
+ res.setHeader('Cache-Control', 'no-cache');
947
+ res.setHeader('Connection', 'keep-alive');
948
+ res.status(500);
949
+ // 发送错误事件
950
+ const errorEvent = `event: error\ndata: ${JSON.stringify({ error: error.message })}\n\n`;
951
+ const doneEvent = `data: [DONE]\n\n`;
952
+ res.write(errorEvent);
953
+ res.write(doneEvent);
954
+ res.end();
955
+ }
956
+ else {
957
+ // 对于非流式请求,返回 JSON 格式的错误响应
958
+ res.status(500).json({ error: error.message });
959
+ }
857
960
  }
858
961
  });
859
962
  }
@@ -863,7 +966,9 @@ class ProxyServer {
863
966
  this.rules.clear();
864
967
  for (const route of this.routes) {
865
968
  const routeRules = this.dbManager.getRules(route.id);
866
- this.rules.set(route.id, routeRules);
969
+ // 确保按 sortOrder 降序排序(database 层已处理,但再次确保)
970
+ const sortedRules = [...routeRules].sort((a, b) => (b.sortOrder || 0) - (a.sortOrder || 0));
971
+ this.rules.set(route.id, sortedRules);
867
972
  }
868
973
  // Load all services
869
974
  const allServices = this.dbManager.getAPIServices();