aicodeswitch 2.1.5 → 3.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/README.md +2 -4
- package/UPGRADE.md +5 -0
- package/bin/restore.js +56 -0
- package/dist/server/database-factory.js +158 -0
- package/dist/server/fs-database.js +1510 -0
- package/dist/server/main.js +40 -42
- package/dist/server/migrate-to-fs.js +253 -0
- package/dist/server/proxy-server.js +80 -17
- package/dist/server/transformers/chunk-collector.js +2 -1
- package/dist/ui/assets/index-Bo5rJH01.js +472 -0
- package/dist/ui/assets/index-uPfIRVTr.css +1 -0
- package/dist/ui/index.html +2 -2
- package/package.json +7 -4
- package/dist/ui/assets/index-4IziQEi6.js +0 -470
- package/dist/ui/assets/index-BVblbmz5.css +0 -1
- package/dist/ui/migration.md +0 -7
package/dist/server/main.js
CHANGED
|
@@ -19,7 +19,7 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
19
19
|
const path_1 = __importDefault(require("path"));
|
|
20
20
|
const crypto_1 = require("crypto");
|
|
21
21
|
const https_proxy_agent_1 = require("https-proxy-agent");
|
|
22
|
-
const
|
|
22
|
+
const database_factory_1 = require("./database-factory");
|
|
23
23
|
const proxy_server_1 = require("./proxy-server");
|
|
24
24
|
const os_1 = __importDefault(require("os"));
|
|
25
25
|
const auth_1 = require("./auth");
|
|
@@ -33,7 +33,7 @@ const config_1 = require("./config");
|
|
|
33
33
|
const appDir = path_1.default.join(os_1.default.homedir(), '.aicodeswitch');
|
|
34
34
|
const dataDir = path_1.default.join(appDir, 'data');
|
|
35
35
|
const dotenvPath = path_1.default.resolve(appDir, 'aicodeswitch.conf');
|
|
36
|
-
const
|
|
36
|
+
const upgradeHashFilePath = path_1.default.join(appDir, 'upgrade-hash');
|
|
37
37
|
if (fs_1.default.existsSync(dotenvPath)) {
|
|
38
38
|
dotenv_1.default.config({ path: dotenvPath });
|
|
39
39
|
}
|
|
@@ -742,14 +742,14 @@ const registerRoutes = (dbManager, proxyServer) => {
|
|
|
742
742
|
app.put('/api/routes/:id', (req, res) => res.json(dbManager.updateRoute(req.params.id, req.body)));
|
|
743
743
|
app.delete('/api/routes/:id', (req, res) => res.json(dbManager.deleteRoute(req.params.id)));
|
|
744
744
|
app.post('/api/routes/:id/activate', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
745
|
-
const result = dbManager.activateRoute(req.params.id);
|
|
745
|
+
const result = yield dbManager.activateRoute(req.params.id);
|
|
746
746
|
if (result) {
|
|
747
747
|
yield proxyServer.reloadRoutes();
|
|
748
748
|
}
|
|
749
749
|
res.json(result);
|
|
750
750
|
})));
|
|
751
751
|
app.post('/api/routes/:id/deactivate', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
752
|
-
const result = dbManager.deactivateRoute(req.params.id);
|
|
752
|
+
const result = yield dbManager.deactivateRoute(req.params.id);
|
|
753
753
|
if (result) {
|
|
754
754
|
yield proxyServer.reloadRoutes();
|
|
755
755
|
}
|
|
@@ -778,7 +778,7 @@ const registerRoutes = (dbManager, proxyServer) => {
|
|
|
778
778
|
}
|
|
779
779
|
// 步骤3:停用所有激活的路由
|
|
780
780
|
console.log('[Deactivate All Routes] Deactivating all active routes...');
|
|
781
|
-
const deactivatedCount = dbManager.deactivateAllRoutes();
|
|
781
|
+
const deactivatedCount = yield dbManager.deactivateAllRoutes();
|
|
782
782
|
if (deactivatedCount > 0) {
|
|
783
783
|
console.log(`[Deactivate All Routes] Deactivated ${deactivatedCount} route(s), reloading routes...`);
|
|
784
784
|
yield proxyServer.reloadRoutes();
|
|
@@ -874,6 +874,10 @@ const registerRoutes = (dbManager, proxyServer) => {
|
|
|
874
874
|
yield dbManager.clearErrorLogs();
|
|
875
875
|
res.json(true);
|
|
876
876
|
})));
|
|
877
|
+
app.delete('/api/statistics', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
878
|
+
yield dbManager.resetStatistics();
|
|
879
|
+
res.json(true);
|
|
880
|
+
})));
|
|
877
881
|
app.get('/api/logs/count', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
878
882
|
const count = yield dbManager.getLogsCount();
|
|
879
883
|
res.json({ count });
|
|
@@ -885,7 +889,7 @@ const registerRoutes = (dbManager, proxyServer) => {
|
|
|
885
889
|
app.get('/api/config', (_req, res) => res.json(dbManager.getConfig()));
|
|
886
890
|
app.put('/api/config', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
887
891
|
const config = req.body;
|
|
888
|
-
const result = dbManager.updateConfig(config);
|
|
892
|
+
const result = yield dbManager.updateConfig(config);
|
|
889
893
|
if (result) {
|
|
890
894
|
yield proxyServer.updateConfig(config);
|
|
891
895
|
updateProxyConfig(config);
|
|
@@ -1287,11 +1291,11 @@ ${instruction}
|
|
|
1287
1291
|
const rawOffset = typeof req.query.offset === 'string' ? parseInt(req.query.offset, 10) : NaN;
|
|
1288
1292
|
const limit = Number.isFinite(rawLimit) ? rawLimit : 100;
|
|
1289
1293
|
const offset = Number.isFinite(rawOffset) ? rawOffset : 0;
|
|
1290
|
-
const sessions = dbManager.getSessions(limit, offset);
|
|
1294
|
+
const sessions = yield dbManager.getSessions(undefined, limit, offset);
|
|
1291
1295
|
res.json(sessions);
|
|
1292
1296
|
})));
|
|
1293
1297
|
app.get('/api/sessions/count', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1294
|
-
const count = dbManager.getSessionsCount();
|
|
1298
|
+
const count = yield dbManager.getSessionsCount();
|
|
1295
1299
|
res.json({ count });
|
|
1296
1300
|
})));
|
|
1297
1301
|
app.get('/api/sessions/:id', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -1334,67 +1338,60 @@ ${instruction}
|
|
|
1334
1338
|
const text = yield resp.text();
|
|
1335
1339
|
res.type('text/plain').send(text);
|
|
1336
1340
|
})));
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
path_1.default.resolve(__dirname, '../../public/migration.md'),
|
|
1343
|
-
// 生产环境:dist/server/main.js -> dist/ui/migration.md
|
|
1344
|
-
path_1.default.resolve(__dirname, '../ui/migration.md'),
|
|
1345
|
-
];
|
|
1346
|
-
for (const possiblePath of possiblePaths) {
|
|
1347
|
-
if (fs_1.default.existsSync(possiblePath)) {
|
|
1348
|
-
return possiblePath;
|
|
1349
|
-
}
|
|
1341
|
+
app.get('/api/docs/upgrade', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1342
|
+
const resp = yield fetch('https://unpkg.com/aicodeswitch/docs/upgrade.md');
|
|
1343
|
+
if (!resp.ok) {
|
|
1344
|
+
res.status(500).send('');
|
|
1345
|
+
return;
|
|
1350
1346
|
}
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1347
|
+
const text = yield resp.text();
|
|
1348
|
+
res.type('text/plain').send(text);
|
|
1349
|
+
})));
|
|
1350
|
+
app.get('/api/upgrade', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1354
1351
|
try {
|
|
1355
|
-
// 读取
|
|
1356
|
-
const
|
|
1357
|
-
if (!
|
|
1352
|
+
// 读取 upgrade.md 文件
|
|
1353
|
+
const upgradePath = path_1.default.resolve(__dirname, '../../UPGRADE.md');
|
|
1354
|
+
if (!upgradePath) {
|
|
1358
1355
|
res.json({ shouldShow: false, content: '' });
|
|
1359
1356
|
return;
|
|
1360
1357
|
}
|
|
1361
|
-
const content = fs_1.default.readFileSync(
|
|
1358
|
+
const content = fs_1.default.readFileSync(upgradePath, 'utf-8').trim();
|
|
1362
1359
|
// 计算当前内容的 hash
|
|
1363
1360
|
const currentHash = (0, crypto_1.createHash)('sha256').update(content).digest('hex');
|
|
1364
1361
|
// 如果 hash 文件不存在,说明是第一次安装
|
|
1365
|
-
if (!fs_1.default.existsSync(
|
|
1362
|
+
if (!fs_1.default.existsSync(upgradeHashFilePath)) {
|
|
1366
1363
|
// 第一次安装,直接保存当前 hash,不显示弹窗
|
|
1367
|
-
fs_1.default.writeFileSync(
|
|
1364
|
+
fs_1.default.writeFileSync(upgradeHashFilePath, currentHash, 'utf-8');
|
|
1368
1365
|
res.json({ shouldShow: false, content: '' });
|
|
1369
1366
|
return;
|
|
1370
1367
|
}
|
|
1371
1368
|
// 读取已保存的 hash
|
|
1372
|
-
const savedHash = fs_1.default.readFileSync(
|
|
1369
|
+
const savedHash = fs_1.default.readFileSync(upgradeHashFilePath, 'utf-8').trim();
|
|
1373
1370
|
// 如果 hash 不同,需要显示弹窗
|
|
1374
1371
|
const shouldShow = savedHash !== currentHash;
|
|
1375
1372
|
res.json({ shouldShow, content: shouldShow ? content : '' });
|
|
1376
1373
|
}
|
|
1377
1374
|
catch (error) {
|
|
1378
|
-
console.error('Failed to read
|
|
1375
|
+
console.error('Failed to read upgrade file:', error);
|
|
1379
1376
|
res.json({ shouldShow: false, content: '' });
|
|
1380
1377
|
}
|
|
1381
1378
|
})));
|
|
1382
|
-
app.post('/api/
|
|
1379
|
+
app.post('/api/upgrade/ack', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1383
1380
|
try {
|
|
1384
|
-
// 读取
|
|
1385
|
-
const
|
|
1386
|
-
if (!
|
|
1381
|
+
// 读取 upgrade.md 文件并计算 hash
|
|
1382
|
+
const upgradePath = path_1.default.resolve(__dirname, '../../UPGRADE.md');
|
|
1383
|
+
if (!upgradePath) {
|
|
1387
1384
|
res.json({ success: false });
|
|
1388
1385
|
return;
|
|
1389
1386
|
}
|
|
1390
|
-
const content = fs_1.default.readFileSync(
|
|
1387
|
+
const content = fs_1.default.readFileSync(upgradePath, 'utf-8').trim();
|
|
1391
1388
|
const hash = (0, crypto_1.createHash)('sha256').update(content).digest('hex');
|
|
1392
1389
|
// 保存 hash 到文件
|
|
1393
|
-
fs_1.default.writeFileSync(
|
|
1390
|
+
fs_1.default.writeFileSync(upgradeHashFilePath, hash, 'utf-8');
|
|
1394
1391
|
res.json({ success: true });
|
|
1395
1392
|
}
|
|
1396
1393
|
catch (error) {
|
|
1397
|
-
console.error('Failed to acknowledge
|
|
1394
|
+
console.error('Failed to acknowledge upgrade:', error);
|
|
1398
1395
|
res.json({ success: false });
|
|
1399
1396
|
}
|
|
1400
1397
|
})));
|
|
@@ -1414,9 +1411,10 @@ ${instruction}
|
|
|
1414
1411
|
};
|
|
1415
1412
|
const start = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
1416
1413
|
fs_1.default.mkdirSync(dataDir, { recursive: true });
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
yield
|
|
1414
|
+
// 自动检测数据库类型并执行迁移(如果需要)
|
|
1415
|
+
console.log('[Server] Initializing database...');
|
|
1416
|
+
const dbManager = yield database_factory_1.DatabaseFactory.createAuto(dataDir);
|
|
1417
|
+
console.log('[Server] Database initialized successfully');
|
|
1420
1418
|
const proxyServer = new proxy_server_1.ProxyServer(dbManager, app);
|
|
1421
1419
|
// Initialize proxy server and register proxy routes last
|
|
1422
1420
|
proxyServer.initialize();
|
|
@@ -0,0 +1,253 @@
|
|
|
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
|
+
})();
|
|
35
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
36
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
37
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
38
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
39
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
40
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
41
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
45
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
46
|
+
};
|
|
47
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
|
+
exports.migrateToFileSystem = migrateToFileSystem;
|
|
49
|
+
exports.needsMigration = needsMigration;
|
|
50
|
+
exports.verifyMigration = verifyMigration;
|
|
51
|
+
const path_1 = __importDefault(require("path"));
|
|
52
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
53
|
+
/**
|
|
54
|
+
* 数据库迁移工具
|
|
55
|
+
* 从 better-sqlite3 和 leveldb 迁移到文件系统数据库
|
|
56
|
+
*/
|
|
57
|
+
function migrateToFileSystem(dataPath) {
|
|
58
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
59
|
+
console.log('[Migration] Starting migration to file system database...');
|
|
60
|
+
try {
|
|
61
|
+
// 动态导入旧数据库(如果存在)
|
|
62
|
+
const oldDbPath = path_1.default.join(dataPath, 'app.db');
|
|
63
|
+
const oldDbExists = yield promises_1.default.access(oldDbPath).then(() => true).catch(() => false);
|
|
64
|
+
if (!oldDbExists) {
|
|
65
|
+
console.log('[Migration] No old database found, skipping migration');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
// 尝试导入旧数据库模块(仅在有 SQLite 依赖时可用)
|
|
69
|
+
let DatabaseManager;
|
|
70
|
+
try {
|
|
71
|
+
const dbModule = yield Promise.resolve().then(() => __importStar(require('./database.js')));
|
|
72
|
+
DatabaseManager = dbModule.DatabaseManager;
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
console.log('[Migration] Old database module not available');
|
|
76
|
+
console.log('[Migration] This is expected if better-sqlite3 is not installed');
|
|
77
|
+
console.log('[Migration] To migrate old data, please run: yarn add better-sqlite3 level');
|
|
78
|
+
console.log('[Migration] Then run the migration again');
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
// 创建旧数据库实例
|
|
82
|
+
console.log('[Migration] Initializing old database...');
|
|
83
|
+
const oldDb = new DatabaseManager(dataPath);
|
|
84
|
+
yield oldDb.initialize();
|
|
85
|
+
// 导出核心数据
|
|
86
|
+
console.log('[Migration] Exporting core data from old database...');
|
|
87
|
+
const vendors = oldDb.getVendors();
|
|
88
|
+
const services = oldDb.getAPIServices();
|
|
89
|
+
const routes = oldDb.getRoutes();
|
|
90
|
+
const rules = oldDb.getRules();
|
|
91
|
+
const config = oldDb.getConfig();
|
|
92
|
+
console.log(`[Migration] Found ${vendors.length} vendors`);
|
|
93
|
+
console.log(`[Migration] Found ${services.length} services`);
|
|
94
|
+
console.log(`[Migration] Found ${routes.length} routes`);
|
|
95
|
+
console.log(`[Migration] Found ${rules.length} rules`);
|
|
96
|
+
// 保存核心数据到新的文件系统格式
|
|
97
|
+
console.log('[Migration] Saving core data to file system...');
|
|
98
|
+
yield promises_1.default.writeFile(path_1.default.join(dataPath, 'vendors.json'), JSON.stringify(vendors, null, 2));
|
|
99
|
+
yield promises_1.default.writeFile(path_1.default.join(dataPath, 'services.json'), JSON.stringify(services, null, 2));
|
|
100
|
+
yield promises_1.default.writeFile(path_1.default.join(dataPath, 'routes.json'), JSON.stringify(routes, null, 2));
|
|
101
|
+
yield promises_1.default.writeFile(path_1.default.join(dataPath, 'rules.json'), JSON.stringify(rules, null, 2));
|
|
102
|
+
yield promises_1.default.writeFile(path_1.default.join(dataPath, 'config.json'), JSON.stringify(config, null, 2));
|
|
103
|
+
// 迁移 sessions(如果存在)
|
|
104
|
+
try {
|
|
105
|
+
console.log('[Migration] Migrating sessions...');
|
|
106
|
+
const sessions = oldDb.getSessions(10000, 0);
|
|
107
|
+
yield promises_1.default.writeFile(path_1.default.join(dataPath, 'sessions.json'), JSON.stringify(sessions, null, 2));
|
|
108
|
+
console.log(`[Migration] Migrated ${sessions.length} sessions`);
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
console.log('[Migration] Could not migrate sessions:', error);
|
|
112
|
+
// 创建空的 sessions 文件
|
|
113
|
+
yield promises_1.default.writeFile(path_1.default.join(dataPath, 'sessions.json'), JSON.stringify([], null, 2));
|
|
114
|
+
}
|
|
115
|
+
// 迁移请求日志(限制数量以避免文件过大)
|
|
116
|
+
try {
|
|
117
|
+
console.log('[Migration] Migrating request logs (last 5000)...');
|
|
118
|
+
const logs = yield oldDb.getLogs(5000, 0);
|
|
119
|
+
// 完整迁移所有日志,不进行任何截断
|
|
120
|
+
if (logs.length > 0) {
|
|
121
|
+
// 修复字段名:将 response 改为 responseBody
|
|
122
|
+
const cleanedLogs = logs.map((log) => (Object.assign(Object.assign({}, log), {
|
|
123
|
+
// 兼容旧的 response 字段名,重命名为 responseBody
|
|
124
|
+
responseBody: log.responseBody || log.response,
|
|
125
|
+
// 移除旧的 response 字段(如果存在)
|
|
126
|
+
response: undefined })));
|
|
127
|
+
yield promises_1.default.writeFile(path_1.default.join(dataPath, 'logs.json'), JSON.stringify(cleanedLogs, null, 2));
|
|
128
|
+
console.log(`[Migration] Migrated ${logs.length} request logs`);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
// 创建空的日志文件
|
|
132
|
+
yield promises_1.default.writeFile(path_1.default.join(dataPath, 'logs.json'), JSON.stringify([], null, 2));
|
|
133
|
+
console.log('[Migration] No request logs to migrate');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
console.log('[Migration] Could not migrate logs:', error instanceof Error ? error.message : error);
|
|
138
|
+
// 创建空的日志文件
|
|
139
|
+
yield promises_1.default.writeFile(path_1.default.join(dataPath, 'logs.json'), JSON.stringify([], null, 2));
|
|
140
|
+
}
|
|
141
|
+
// 迁移错误日志
|
|
142
|
+
try {
|
|
143
|
+
console.log('[Migration] Migrating error logs (last 1000)...');
|
|
144
|
+
const errorLogs = yield oldDb.getErrorLogs(1000, 0);
|
|
145
|
+
yield promises_1.default.writeFile(path_1.default.join(dataPath, 'error-logs.json'), JSON.stringify(errorLogs, null, 2));
|
|
146
|
+
console.log(`[Migration] Migrated ${errorLogs.length} error logs`);
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
console.log('[Migration] Could not migrate error logs:', error);
|
|
150
|
+
// 创建空的错误日志文件
|
|
151
|
+
yield promises_1.default.writeFile(path_1.default.join(dataPath, 'error-logs.json'), JSON.stringify([], null, 2));
|
|
152
|
+
}
|
|
153
|
+
// 创建空的黑名单文件
|
|
154
|
+
yield promises_1.default.writeFile(path_1.default.join(dataPath, 'blacklist.json'), JSON.stringify([], null, 2));
|
|
155
|
+
// 关闭旧数据库连接
|
|
156
|
+
try {
|
|
157
|
+
console.log('[Migration] Closing old database connections...');
|
|
158
|
+
oldDb.close();
|
|
159
|
+
// 等待一下确保文件句柄被释放
|
|
160
|
+
yield new Promise(resolve => setTimeout(resolve, 1000));
|
|
161
|
+
console.log('[Migration] Old database connections closed');
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
console.log('[Migration] Warning: Could not close old database:', error instanceof Error ? error.message : error);
|
|
165
|
+
}
|
|
166
|
+
// 保留原始数据库文件,不进行备份或重命名
|
|
167
|
+
// 这样如果迁移失败,用户可以使用老版本继续运行
|
|
168
|
+
console.log('[Migration] ✅ Migration data export completed successfully!');
|
|
169
|
+
console.log('[Migration] Original database files preserved for rollback');
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
console.error('[Migration] ❌ Migration failed:', error);
|
|
173
|
+
console.error('[Migration] Stack trace:', error.stack);
|
|
174
|
+
throw error;
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* 检查是否需要迁移
|
|
180
|
+
* @param dataPath 数据目录路径
|
|
181
|
+
* @returns 是否需要迁移
|
|
182
|
+
*/
|
|
183
|
+
function needsMigration(dataPath) {
|
|
184
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
185
|
+
try {
|
|
186
|
+
// 检查是否存在旧的 SQLite 数据库
|
|
187
|
+
const oldDbPath = path_1.default.join(dataPath, 'app.db');
|
|
188
|
+
const oldDbExists = yield promises_1.default.access(oldDbPath).then(() => true).catch(() => false);
|
|
189
|
+
if (!oldDbExists) {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
// 检查是否已经存在新的文件系统数据库
|
|
193
|
+
const newDbPath = path_1.default.join(dataPath, 'config.json');
|
|
194
|
+
const newDbExists = yield promises_1.default.access(newDbPath).then(() => true).catch(() => false);
|
|
195
|
+
// 如果旧数据库存在且新数据库不存在,则需要迁移
|
|
196
|
+
return oldDbExists && !newDbExists;
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
console.error('[Migration] Error checking migration status:', error);
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* 验证迁移结果
|
|
206
|
+
* @param dataPath 数据目录路径
|
|
207
|
+
* @returns 验证结果
|
|
208
|
+
*/
|
|
209
|
+
function verifyMigration(dataPath) {
|
|
210
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
211
|
+
const errors = [];
|
|
212
|
+
const warnings = [];
|
|
213
|
+
try {
|
|
214
|
+
// 检查必需的文件
|
|
215
|
+
const requiredFiles = [
|
|
216
|
+
'vendors.json',
|
|
217
|
+
'services.json',
|
|
218
|
+
'routes.json',
|
|
219
|
+
'rules.json',
|
|
220
|
+
'config.json',
|
|
221
|
+
'sessions.json',
|
|
222
|
+
'logs.json',
|
|
223
|
+
'error-logs.json',
|
|
224
|
+
'blacklist.json',
|
|
225
|
+
];
|
|
226
|
+
for (const file of requiredFiles) {
|
|
227
|
+
const filePath = path_1.default.join(dataPath, file);
|
|
228
|
+
try {
|
|
229
|
+
yield promises_1.default.access(filePath);
|
|
230
|
+
// 尝试解析 JSON
|
|
231
|
+
const content = yield promises_1.default.readFile(filePath, 'utf-8');
|
|
232
|
+
JSON.parse(content);
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
errors.push(`File ${file} is missing or invalid`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return {
|
|
239
|
+
success: errors.length === 0,
|
|
240
|
+
errors,
|
|
241
|
+
warnings,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
errors.push(`Verification failed: ${error.message}`);
|
|
246
|
+
return {
|
|
247
|
+
success: false,
|
|
248
|
+
errors,
|
|
249
|
+
warnings,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
}
|
|
@@ -198,7 +198,7 @@ class ProxyServer {
|
|
|
198
198
|
method: req.method,
|
|
199
199
|
path: req.path,
|
|
200
200
|
headers: this.normalizeHeaders(req.headers),
|
|
201
|
-
body: req.body
|
|
201
|
+
body: req.body,
|
|
202
202
|
error: (lastError === null || lastError === void 0 ? void 0 : lastError.message) || 'All services failed',
|
|
203
203
|
});
|
|
204
204
|
}
|
|
@@ -246,7 +246,7 @@ class ProxyServer {
|
|
|
246
246
|
method: req.method,
|
|
247
247
|
path: req.path,
|
|
248
248
|
headers: this.normalizeHeaders(req.headers),
|
|
249
|
-
body: req.body
|
|
249
|
+
body: req.body,
|
|
250
250
|
error: error.message,
|
|
251
251
|
});
|
|
252
252
|
}
|
|
@@ -379,7 +379,7 @@ class ProxyServer {
|
|
|
379
379
|
method: req.method,
|
|
380
380
|
path: req.path,
|
|
381
381
|
headers: this.normalizeHeaders(req.headers),
|
|
382
|
-
body: req.body
|
|
382
|
+
body: req.body,
|
|
383
383
|
error: (lastError === null || lastError === void 0 ? void 0 : lastError.message) || 'All services failed',
|
|
384
384
|
});
|
|
385
385
|
}
|
|
@@ -425,7 +425,7 @@ class ProxyServer {
|
|
|
425
425
|
method: req.method,
|
|
426
426
|
path: req.path,
|
|
427
427
|
headers: this.normalizeHeaders(req.headers),
|
|
428
|
-
body: req.body
|
|
428
|
+
body: req.body,
|
|
429
429
|
error: error.message,
|
|
430
430
|
});
|
|
431
431
|
}
|
|
@@ -1285,6 +1285,7 @@ class ProxyServer {
|
|
|
1285
1285
|
* 提取会话标题(默认方法)
|
|
1286
1286
|
* 对于新会话,尝试从第一条消息的内容中提取标题
|
|
1287
1287
|
* 优化:使用第一条用户消息的完整内容,并智能截取
|
|
1288
|
+
* 对于结构化内容(数组),从最后一个元素取值
|
|
1288
1289
|
*/
|
|
1289
1290
|
defaultExtractSessionTitle(request, sessionId) {
|
|
1290
1291
|
var _a;
|
|
@@ -1304,11 +1305,19 @@ class ProxyServer {
|
|
|
1304
1305
|
if (typeof content === 'string') {
|
|
1305
1306
|
rawText = content;
|
|
1306
1307
|
}
|
|
1307
|
-
else if (Array.isArray(content)) {
|
|
1308
|
+
else if (Array.isArray(content) && content.length > 0) {
|
|
1308
1309
|
// 处理结构化内容(如图片+文本)
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1310
|
+
// 从最后一个元素取值,通常最后的文本才是真正的用户输入
|
|
1311
|
+
const lastBlock = content[content.length - 1];
|
|
1312
|
+
if ((lastBlock === null || lastBlock === void 0 ? void 0 : lastBlock.type) === 'text' && (lastBlock === null || lastBlock === void 0 ? void 0 : lastBlock.text)) {
|
|
1313
|
+
rawText = lastBlock.text;
|
|
1314
|
+
}
|
|
1315
|
+
else {
|
|
1316
|
+
// 如果最后一个不是 text 类型,尝试找到第一个 text 类型作为备用
|
|
1317
|
+
const textBlock = content.find((block) => (block === null || block === void 0 ? void 0 : block.type) === 'text');
|
|
1318
|
+
if (textBlock === null || textBlock === void 0 ? void 0 : textBlock.text) {
|
|
1319
|
+
rawText = textBlock.text;
|
|
1320
|
+
}
|
|
1312
1321
|
}
|
|
1313
1322
|
}
|
|
1314
1323
|
if (rawText) {
|
|
@@ -1422,7 +1431,7 @@ class ProxyServer {
|
|
|
1422
1431
|
method: req.method,
|
|
1423
1432
|
path: req.path,
|
|
1424
1433
|
headers: this.normalizeHeaders(req.headers),
|
|
1425
|
-
body: req.body
|
|
1434
|
+
body: req.body,
|
|
1426
1435
|
statusCode,
|
|
1427
1436
|
responseTime: Date.now() - startTime,
|
|
1428
1437
|
targetProvider: service.name,
|
|
@@ -1607,7 +1616,8 @@ class ProxyServer {
|
|
|
1607
1616
|
const serializer = new streaming_1.SSESerializerTransform();
|
|
1608
1617
|
// 收集响应头
|
|
1609
1618
|
responseHeadersForLog = this.normalizeResponseHeaders(responseHeaders);
|
|
1610
|
-
|
|
1619
|
+
// 监听事件收集器的完成事件,确保所有chunks都被收集
|
|
1620
|
+
const finalizeChunks = () => {
|
|
1611
1621
|
const usage = converter.getUsage();
|
|
1612
1622
|
if (usage) {
|
|
1613
1623
|
usageForLog = (0, claude_openai_1.extractTokenUsageFromClaudeUsage)(usage);
|
|
@@ -1621,8 +1631,24 @@ class ProxyServer {
|
|
|
1621
1631
|
}
|
|
1622
1632
|
// 收集stream chunks(每个chunk是一个完整的SSE事件)
|
|
1623
1633
|
streamChunksForLog = eventCollector.getChunks();
|
|
1634
|
+
// 将所有 chunks 合并成完整的响应体用于日志记录
|
|
1635
|
+
responseBodyForLog = streamChunksForLog.join('\n');
|
|
1624
1636
|
console.log('[Proxy] Stream request finished, collected chunks:', (streamChunksForLog === null || streamChunksForLog === void 0 ? void 0 : streamChunksForLog.length) || 0);
|
|
1637
|
+
console.log('[Proxy] Response body length:', (responseBodyForLog === null || responseBodyForLog === void 0 ? void 0 : responseBodyForLog.length) || 0);
|
|
1625
1638
|
void finalizeLog(res.statusCode);
|
|
1639
|
+
};
|
|
1640
|
+
// 在pipeline完成且eventCollector flush后执行
|
|
1641
|
+
eventCollector.on('finish', () => {
|
|
1642
|
+
console.log('[Proxy] EventCollector finished, collecting chunks...');
|
|
1643
|
+
finalizeChunks();
|
|
1644
|
+
});
|
|
1645
|
+
// 备用:如果eventCollector的finish没有触发,监听res的finish
|
|
1646
|
+
res.on('finish', () => {
|
|
1647
|
+
console.log('[Proxy] Response finished');
|
|
1648
|
+
if (!streamChunksForLog) {
|
|
1649
|
+
console.log('[Proxy] Chunks not collected yet, forcing collection...');
|
|
1650
|
+
finalizeChunks();
|
|
1651
|
+
}
|
|
1626
1652
|
});
|
|
1627
1653
|
// 监听 res 的错误事件
|
|
1628
1654
|
res.on('error', (err) => {
|
|
@@ -1693,7 +1719,8 @@ class ProxyServer {
|
|
|
1693
1719
|
const converter = new streaming_1.ClaudeToOpenAIChatEventTransform({ model: requestBody === null || requestBody === void 0 ? void 0 : requestBody.model });
|
|
1694
1720
|
const serializer = new streaming_1.SSESerializerTransform();
|
|
1695
1721
|
responseHeadersForLog = this.normalizeResponseHeaders(responseHeaders);
|
|
1696
|
-
|
|
1722
|
+
// 监听事件收集器的完成事件,确保所有chunks都被收集
|
|
1723
|
+
const finalizeChunks = () => {
|
|
1697
1724
|
const usage = converter.getUsage();
|
|
1698
1725
|
if (usage) {
|
|
1699
1726
|
usageForLog = (0, claude_openai_1.extractTokenUsageFromOpenAIUsage)(usage);
|
|
@@ -1706,8 +1733,24 @@ class ProxyServer {
|
|
|
1706
1733
|
}
|
|
1707
1734
|
}
|
|
1708
1735
|
streamChunksForLog = eventCollector.getChunks();
|
|
1736
|
+
// 将所有 chunks 合并成完整的响应体用于日志记录
|
|
1737
|
+
responseBodyForLog = streamChunksForLog.join('\n');
|
|
1709
1738
|
console.log('[Proxy] Codex stream request finished, collected chunks:', (streamChunksForLog === null || streamChunksForLog === void 0 ? void 0 : streamChunksForLog.length) || 0);
|
|
1739
|
+
console.log('[Proxy] Response body length:', (responseBodyForLog === null || responseBodyForLog === void 0 ? void 0 : responseBodyForLog.length) || 0);
|
|
1710
1740
|
void finalizeLog(res.statusCode);
|
|
1741
|
+
};
|
|
1742
|
+
// 在pipeline完成且eventCollector flush后执行
|
|
1743
|
+
eventCollector.on('finish', () => {
|
|
1744
|
+
console.log('[Proxy] EventCollector finished (codex), collecting chunks...');
|
|
1745
|
+
finalizeChunks();
|
|
1746
|
+
});
|
|
1747
|
+
// 备用:如果eventCollector的finish没有触发,监听res的finish
|
|
1748
|
+
res.on('finish', () => {
|
|
1749
|
+
console.log('[Proxy] Response finished (codex)');
|
|
1750
|
+
if (!streamChunksForLog) {
|
|
1751
|
+
console.log('[Proxy] Chunks not collected yet, forcing collection...');
|
|
1752
|
+
finalizeChunks();
|
|
1753
|
+
}
|
|
1711
1754
|
});
|
|
1712
1755
|
// 监听 res 的错误事件
|
|
1713
1756
|
res.on('error', (err) => {
|
|
@@ -1766,23 +1809,43 @@ class ProxyServer {
|
|
|
1766
1809
|
return;
|
|
1767
1810
|
}
|
|
1768
1811
|
// 默认stream处理(无转换)
|
|
1812
|
+
const parser = new streaming_1.SSEParserTransform();
|
|
1769
1813
|
const eventCollector = new chunk_collector_1.SSEEventCollectorTransform();
|
|
1814
|
+
const serializer = new streaming_1.SSESerializerTransform();
|
|
1770
1815
|
responseHeadersForLog = this.normalizeResponseHeaders(responseHeaders);
|
|
1771
1816
|
this.copyResponseHeaders(responseHeaders, res);
|
|
1772
|
-
//
|
|
1773
|
-
|
|
1774
|
-
console.error('[Proxy] Response stream error:', err);
|
|
1775
|
-
});
|
|
1776
|
-
res.on('finish', () => {
|
|
1817
|
+
// 监听事件收集器的完成事件,确保所有chunks都被收集
|
|
1818
|
+
const finalizeChunks = () => {
|
|
1777
1819
|
streamChunksForLog = eventCollector.getChunks();
|
|
1820
|
+
// 将所有 chunks 合并成完整的响应体用于日志记录
|
|
1821
|
+
responseBodyForLog = streamChunksForLog.join('\n');
|
|
1778
1822
|
// 尝试从event collector中提取usage信息
|
|
1779
1823
|
const extractedUsage = eventCollector.extractUsage();
|
|
1780
1824
|
if (extractedUsage) {
|
|
1781
1825
|
usageForLog = this.extractTokenUsage(extractedUsage);
|
|
1782
1826
|
}
|
|
1827
|
+
console.log('[Proxy] Default stream request finished, collected chunks:', (streamChunksForLog === null || streamChunksForLog === void 0 ? void 0 : streamChunksForLog.length) || 0);
|
|
1828
|
+
console.log('[Proxy] Response body length:', (responseBodyForLog === null || responseBodyForLog === void 0 ? void 0 : responseBodyForLog.length) || 0);
|
|
1783
1829
|
void finalizeLog(res.statusCode);
|
|
1830
|
+
};
|
|
1831
|
+
// 在pipeline完成且eventCollector flush后执行
|
|
1832
|
+
eventCollector.on('finish', () => {
|
|
1833
|
+
console.log('[Proxy] EventCollector finished (default stream), collecting chunks...');
|
|
1834
|
+
finalizeChunks();
|
|
1835
|
+
});
|
|
1836
|
+
// 备用:如果eventCollector的finish没有触发,监听res的finish
|
|
1837
|
+
res.on('finish', () => {
|
|
1838
|
+
console.log('[Proxy] Response finished (default stream)');
|
|
1839
|
+
if (!streamChunksForLog) {
|
|
1840
|
+
console.log('[Proxy] Chunks not collected yet, forcing collection...');
|
|
1841
|
+
finalizeChunks();
|
|
1842
|
+
}
|
|
1843
|
+
});
|
|
1844
|
+
// 监听 res 的错误事件
|
|
1845
|
+
res.on('error', (err) => {
|
|
1846
|
+
console.error('[Proxy] Response stream error:', err);
|
|
1784
1847
|
});
|
|
1785
|
-
(0, stream_1.pipeline)(response.data, eventCollector, res, (error) => __awaiter(this, void 0, void 0, function* () {
|
|
1848
|
+
(0, stream_1.pipeline)(response.data, parser, eventCollector, serializer, res, (error) => __awaiter(this, void 0, void 0, function* () {
|
|
1786
1849
|
if (error) {
|
|
1787
1850
|
console.error('[Proxy] Pipeline error (default stream):', error);
|
|
1788
1851
|
// 记录到错误日志
|
|
@@ -203,7 +203,8 @@ class SSEEventCollectorTransform extends stream_1.Transform {
|
|
|
203
203
|
this.currentEvent = { dataLines: [], rawLines: [] };
|
|
204
204
|
return;
|
|
205
205
|
}
|
|
206
|
-
|
|
206
|
+
// SSE格式要求事件以空行结束,所以添加一个空行
|
|
207
|
+
const raw = this.currentEvent.rawLines.join('\n') + '\n';
|
|
207
208
|
const event = {
|
|
208
209
|
event: this.currentEvent.event,
|
|
209
210
|
id: this.currentEvent.id,
|