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.
- package/CLAUDE.md +182 -0
- package/LICENSE +671 -0
- package/README.md +13 -0
- package/bin/cli.js +35 -3
- package/bin/update.js +251 -0
- package/bin/version.js +97 -0
- package/dist/server/database.js +22 -11
- package/dist/server/proxy-server.js +134 -29
- package/dist/ui/assets/index-BJB9D8Y6.js +285 -0
- package/dist/ui/assets/index-CRLNbjRB.css +1 -0
- package/dist/ui/index.html +2 -2
- package/package.json +29 -3
- package/dist/ui/assets/index-BN77E7-U.js +0 -259
- package/dist/ui/assets/index-CaNSVfpD.css +0 -1
|
@@ -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
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
this.dbManager.
|
|
93
|
-
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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();
|