aicodeswitch 1.10.1 → 2.0.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/CHANGELOG.md +4 -0
- package/bin/cli.js +7 -9
- package/bin/restart.js +7 -229
- package/bin/restore.js +1 -1
- package/bin/start.js +75 -87
- package/bin/stop.js +77 -14
- package/bin/ui.js +19 -134
- package/bin/update.js +1 -1
- package/bin/utils/get-server.js +58 -0
- package/bin/utils/port-utils.js +118 -0
- package/bin/version.js +1 -1
- package/dist/server/database.js +480 -129
- package/dist/server/main.js +164 -22
- package/dist/server/proxy-server.js +417 -120
- package/dist/server/transformers/claude-openai.js +86 -3
- package/dist/server/transformers/streaming.js +4 -1
- package/dist/server/utils.js +16 -0
- package/dist/ui/assets/index-BLqGemLn.js +423 -0
- package/dist/ui/assets/index-IVPeH7yC.css +1 -0
- package/dist/ui/index.html +2 -2
- package/dist/ui/migration.md +7 -0
- package/package.json +3 -2
- package/public/migration.md +7 -0
- package/dist/ui/assets/index-BdKga7KO.js +0 -391
- package/dist/ui/assets/index-Dat4drax.css +0 -1
package/dist/server/main.js
CHANGED
|
@@ -17,11 +17,13 @@ const cors_1 = __importDefault(require("cors"));
|
|
|
17
17
|
const dotenv_1 = __importDefault(require("dotenv"));
|
|
18
18
|
const fs_1 = __importDefault(require("fs"));
|
|
19
19
|
const path_1 = __importDefault(require("path"));
|
|
20
|
+
const crypto_1 = require("crypto");
|
|
20
21
|
const database_1 = require("./database");
|
|
21
22
|
const proxy_server_1 = require("./proxy-server");
|
|
22
23
|
const os_1 = __importDefault(require("os"));
|
|
23
24
|
const auth_1 = require("./auth");
|
|
24
25
|
const version_check_1 = require("./version-check");
|
|
26
|
+
const utils_1 = require("./utils");
|
|
25
27
|
const dotenvPath = path_1.default.resolve(os_1.default.homedir(), '.aicodeswitch/aicodeswitch.conf');
|
|
26
28
|
if (fs_1.default.existsSync(dotenvPath)) {
|
|
27
29
|
dotenv_1.default.config({ path: dotenvPath });
|
|
@@ -283,6 +285,54 @@ const registerRoutes = (dbManager, proxyServer) => {
|
|
|
283
285
|
app.put('/api/rules/:id', (req, res) => res.json(dbManager.updateRule(req.params.id, req.body)));
|
|
284
286
|
app.delete('/api/rules/:id', (req, res) => res.json(dbManager.deleteRule(req.params.id)));
|
|
285
287
|
app.put('/api/rules/:id/reset-tokens', (req, res) => res.json(dbManager.resetRuleTokenUsage(req.params.id)));
|
|
288
|
+
app.put('/api/rules/:id/reset-requests', (req, res) => res.json(dbManager.resetRuleRequestCount(req.params.id)));
|
|
289
|
+
// 解除规则的黑名单状态
|
|
290
|
+
app.put('/api/rules/:id/clear-blacklist', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
291
|
+
const { id } = req.params;
|
|
292
|
+
const rule = dbManager.getRule(id);
|
|
293
|
+
if (!rule) {
|
|
294
|
+
res.status(404).json({ error: 'Rule not found' });
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
// 找到该规则所属的路由
|
|
298
|
+
const routes = dbManager.getRoutes();
|
|
299
|
+
const route = routes.find(r => {
|
|
300
|
+
const rules = dbManager.getRules(r.id);
|
|
301
|
+
return rules.some(r => r.id === id);
|
|
302
|
+
});
|
|
303
|
+
if (!route) {
|
|
304
|
+
res.status(404).json({ error: 'Route not found' });
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
try {
|
|
308
|
+
yield dbManager.removeFromBlacklist(rule.targetServiceId, route.id, rule.contentType);
|
|
309
|
+
res.json({ success: true });
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
console.error('Error clearing blacklist:', error);
|
|
313
|
+
res.status(500).json({ error: 'Failed to clear blacklist' });
|
|
314
|
+
}
|
|
315
|
+
})));
|
|
316
|
+
// 获取规则的黑名单状态
|
|
317
|
+
app.get('/api/rules/:routeId/blacklist-status', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
318
|
+
const { routeId } = req.params;
|
|
319
|
+
const rules = dbManager.getRules(routeId);
|
|
320
|
+
try {
|
|
321
|
+
const results = yield Promise.all(rules.map((rule) => __awaiter(void 0, void 0, void 0, function* () {
|
|
322
|
+
const blacklistStatus = yield dbManager.getRuleBlacklistStatus(rule.targetServiceId, routeId, rule.contentType);
|
|
323
|
+
return {
|
|
324
|
+
ruleId: rule.id,
|
|
325
|
+
isBlacklisted: blacklistStatus !== null,
|
|
326
|
+
blacklistEntry: blacklistStatus,
|
|
327
|
+
};
|
|
328
|
+
})));
|
|
329
|
+
res.json(results);
|
|
330
|
+
}
|
|
331
|
+
catch (error) {
|
|
332
|
+
console.error('Error getting blacklist status:', error);
|
|
333
|
+
res.status(500).json({ error: 'Failed to get blacklist status' });
|
|
334
|
+
}
|
|
335
|
+
})));
|
|
286
336
|
app.get('/api/logs', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
287
337
|
const rawLimit = typeof req.query.limit === 'string' ? parseInt(req.query.limit, 10) : NaN;
|
|
288
338
|
const rawOffset = typeof req.query.offset === 'string' ? parseInt(req.query.offset, 10) : NaN;
|
|
@@ -295,18 +345,6 @@ const registerRoutes = (dbManager, proxyServer) => {
|
|
|
295
345
|
yield dbManager.clearLogs();
|
|
296
346
|
res.json(true);
|
|
297
347
|
})));
|
|
298
|
-
app.get('/api/access-logs', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
299
|
-
const rawLimit = typeof req.query.limit === 'string' ? parseInt(req.query.limit, 10) : NaN;
|
|
300
|
-
const rawOffset = typeof req.query.offset === 'string' ? parseInt(req.query.offset, 10) : NaN;
|
|
301
|
-
const limit = Number.isFinite(rawLimit) ? rawLimit : 100;
|
|
302
|
-
const offset = Number.isFinite(rawOffset) ? rawOffset : 0;
|
|
303
|
-
const logs = yield dbManager.getAccessLogs(limit, offset);
|
|
304
|
-
res.json(logs);
|
|
305
|
-
})));
|
|
306
|
-
app.delete('/api/access-logs', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
307
|
-
yield dbManager.clearAccessLogs();
|
|
308
|
-
res.json(true);
|
|
309
|
-
})));
|
|
310
348
|
app.get('/api/error-logs', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
311
349
|
const rawLimit = typeof req.query.limit === 'string' ? parseInt(req.query.limit, 10) : NaN;
|
|
312
350
|
const rawOffset = typeof req.query.offset === 'string' ? parseInt(req.query.offset, 10) : NaN;
|
|
@@ -323,10 +361,6 @@ const registerRoutes = (dbManager, proxyServer) => {
|
|
|
323
361
|
const count = yield dbManager.getLogsCount();
|
|
324
362
|
res.json({ count });
|
|
325
363
|
})));
|
|
326
|
-
app.get('/api/access-logs/count', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
327
|
-
const count = yield dbManager.getAccessLogsCount();
|
|
328
|
-
res.json({ count });
|
|
329
|
-
})));
|
|
330
364
|
app.get('/api/error-logs/count', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
331
365
|
const count = yield dbManager.getErrorLogsCount();
|
|
332
366
|
res.json({ count });
|
|
@@ -384,6 +418,41 @@ const registerRoutes = (dbManager, proxyServer) => {
|
|
|
384
418
|
const stats = yield dbManager.getStatistics(days);
|
|
385
419
|
res.json(stats);
|
|
386
420
|
})));
|
|
421
|
+
// Sessions 相关端点
|
|
422
|
+
app.get('/api/sessions', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
423
|
+
const rawLimit = typeof req.query.limit === 'string' ? parseInt(req.query.limit, 10) : NaN;
|
|
424
|
+
const rawOffset = typeof req.query.offset === 'string' ? parseInt(req.query.offset, 10) : NaN;
|
|
425
|
+
const limit = Number.isFinite(rawLimit) ? rawLimit : 100;
|
|
426
|
+
const offset = Number.isFinite(rawOffset) ? rawOffset : 0;
|
|
427
|
+
const sessions = dbManager.getSessions(limit, offset);
|
|
428
|
+
res.json(sessions);
|
|
429
|
+
})));
|
|
430
|
+
app.get('/api/sessions/count', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
431
|
+
const count = dbManager.getSessionsCount();
|
|
432
|
+
res.json({ count });
|
|
433
|
+
})));
|
|
434
|
+
app.get('/api/sessions/:id', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
435
|
+
const session = dbManager.getSession(req.params.id);
|
|
436
|
+
if (!session) {
|
|
437
|
+
res.status(404).json({ error: 'Session not found' });
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
res.json(session);
|
|
441
|
+
})));
|
|
442
|
+
app.get('/api/sessions/:id/logs', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
443
|
+
const rawLimit = typeof req.query.limit === 'string' ? parseInt(req.query.limit, 10) : NaN;
|
|
444
|
+
const limit = Number.isFinite(rawLimit) ? rawLimit : 100;
|
|
445
|
+
const logs = yield dbManager.getLogsBySessionId(req.params.id, limit);
|
|
446
|
+
res.json(logs);
|
|
447
|
+
})));
|
|
448
|
+
app.delete('/api/sessions/:id', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
449
|
+
const result = dbManager.deleteSession(req.params.id);
|
|
450
|
+
res.json(result);
|
|
451
|
+
})));
|
|
452
|
+
app.delete('/api/sessions', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
453
|
+
dbManager.clearSessions();
|
|
454
|
+
res.json(true);
|
|
455
|
+
})));
|
|
387
456
|
app.get('/api/docs/recommend-vendors', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
388
457
|
const resp = yield fetch('https://unpkg.com/aicodeswitch/docs/vendors-recommand.md');
|
|
389
458
|
if (!resp.ok) {
|
|
@@ -402,24 +471,97 @@ const registerRoutes = (dbManager, proxyServer) => {
|
|
|
402
471
|
const text = yield resp.text();
|
|
403
472
|
res.type('text/plain').send(text);
|
|
404
473
|
})));
|
|
405
|
-
|
|
474
|
+
// Migration 相关端点
|
|
475
|
+
const getMigrationHashPath = () => path_1.default.join(dataDir, '.migration-hash');
|
|
476
|
+
// 查找 migration.md 文件的路径
|
|
477
|
+
const findMigrationPath = () => {
|
|
478
|
+
// 可能的路径列表
|
|
479
|
+
const possiblePaths = [
|
|
480
|
+
// 开发环境:src/server/main.ts -> public/migration.md
|
|
481
|
+
path_1.default.resolve(__dirname, '../../public/migration.md'),
|
|
482
|
+
// 生产环境:dist/server/main.js -> dist/ui/migration.md
|
|
483
|
+
path_1.default.resolve(__dirname, '../ui/migration.md'),
|
|
484
|
+
];
|
|
485
|
+
for (const possiblePath of possiblePaths) {
|
|
486
|
+
if (fs_1.default.existsSync(possiblePath)) {
|
|
487
|
+
return possiblePath;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return null;
|
|
491
|
+
};
|
|
492
|
+
app.get('/api/migration', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
493
|
+
try {
|
|
494
|
+
// 读取 migration.md 文件
|
|
495
|
+
const migrationPath = findMigrationPath();
|
|
496
|
+
if (!migrationPath) {
|
|
497
|
+
res.json({ shouldShow: false, content: '' });
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
const content = fs_1.default.readFileSync(migrationPath, 'utf-8');
|
|
501
|
+
// 计算当前内容的 hash
|
|
502
|
+
const currentHash = (0, crypto_1.createHash)('sha256').update(content).digest('hex');
|
|
503
|
+
// 读取之前保存的 hash
|
|
504
|
+
const hashPath = getMigrationHashPath();
|
|
505
|
+
let savedHash = '';
|
|
506
|
+
if (fs_1.default.existsSync(hashPath)) {
|
|
507
|
+
savedHash = fs_1.default.readFileSync(hashPath, 'utf-8').trim();
|
|
508
|
+
}
|
|
509
|
+
// 如果 hash 不同,需要显示弹窗
|
|
510
|
+
const shouldShow = savedHash !== currentHash;
|
|
511
|
+
res.json({ shouldShow, content: shouldShow ? content : '' });
|
|
512
|
+
}
|
|
513
|
+
catch (error) {
|
|
514
|
+
console.error('Failed to read migration file:', error);
|
|
515
|
+
res.json({ shouldShow: false, content: '' });
|
|
516
|
+
}
|
|
517
|
+
})));
|
|
518
|
+
app.post('/api/migration/ack', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
519
|
+
try {
|
|
520
|
+
// 读取 migration.md 文件并计算 hash
|
|
521
|
+
const migrationPath = findMigrationPath();
|
|
522
|
+
if (!migrationPath) {
|
|
523
|
+
res.json({ success: false });
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
const content = fs_1.default.readFileSync(migrationPath, 'utf-8');
|
|
527
|
+
const hash = (0, crypto_1.createHash)('sha256').update(content).digest('hex');
|
|
528
|
+
// 保存 hash 到文件
|
|
529
|
+
const hashPath = getMigrationHashPath();
|
|
530
|
+
fs_1.default.writeFileSync(hashPath, hash, 'utf-8');
|
|
531
|
+
res.json({ success: true });
|
|
532
|
+
}
|
|
533
|
+
catch (error) {
|
|
534
|
+
console.error('Failed to acknowledge migration:', error);
|
|
535
|
+
res.json({ success: false });
|
|
536
|
+
}
|
|
537
|
+
})));
|
|
406
538
|
};
|
|
407
539
|
const start = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
408
540
|
fs_1.default.mkdirSync(dataDir, { recursive: true });
|
|
409
541
|
const dbManager = new database_1.DatabaseManager(dataDir);
|
|
410
|
-
yield dbManager.initialize();
|
|
411
542
|
const proxyServer = new proxy_server_1.ProxyServer(dbManager, app);
|
|
543
|
+
yield dbManager.initialize();
|
|
544
|
+
// Initialize proxy server and register proxy routes last
|
|
545
|
+
proxyServer.initialize();
|
|
412
546
|
// Register admin routes first
|
|
413
547
|
registerRoutes(dbManager, proxyServer);
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
const
|
|
548
|
+
yield proxyServer.registerProxyRoutes();
|
|
549
|
+
app.use(express_1.default.static(path_1.default.resolve(__dirname, '../ui')));
|
|
550
|
+
const isPortUsable = yield (0, utils_1.checkPortUsable)(port);
|
|
551
|
+
if (!isPortUsable) {
|
|
552
|
+
console.error(`端口 ${port} 已被占用,无法启动服务。请执行 aicos stop 后重启。`);
|
|
553
|
+
process.exit(1);
|
|
554
|
+
}
|
|
555
|
+
const server = app.listen(port, host, () => {
|
|
417
556
|
console.log(`Admin server running on http://${host}:${port}`);
|
|
418
557
|
});
|
|
419
558
|
const shutdown = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
420
559
|
console.log('Shutting down server...');
|
|
421
560
|
dbManager.close();
|
|
422
|
-
|
|
561
|
+
server.close(() => {
|
|
562
|
+
console.log('Server stopped.');
|
|
563
|
+
process.exit(0);
|
|
564
|
+
});
|
|
423
565
|
});
|
|
424
566
|
process.on('SIGINT', shutdown);
|
|
425
567
|
process.on('SIGTERM', shutdown);
|