aicodeswitch 3.9.3 → 4.0.0
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 +37 -36
- package/UPGRADE.md +5 -3
- package/bin/restore.js +126 -22
- package/bin/start.js +29 -41
- package/bin/stop.js +3 -3
- package/bin/utils/config-helpers.js +198 -0
- package/dist/server/config-managed-fields.js +69 -0
- package/dist/server/config-merge.js +260 -0
- package/dist/server/database-factory.js +11 -181
- package/dist/server/fs-database.js +211 -31
- package/dist/server/main.js +380 -241
- package/dist/server/original-config-reader.js +154 -88
- package/dist/server/proxy-server.js +993 -385
- package/dist/server/rules-status-service.js +285 -54
- package/dist/server/transformers/chunk-collector.js +26 -4
- package/dist/server/transformers/streaming.js +2334 -280
- package/dist/server/transformers/transformers.js +1765 -0
- package/dist/ui/assets/index-GQBwe1Rm.js +514 -0
- package/dist/ui/index.html +1 -1
- package/package.json +4 -8
- package/schema/claude.schema.md +15 -14
- package/schema/deepseek-chat.schema.md +799 -0
- package/schema/{openai.schema.md → openai-chat-completions.schema.md} +9 -1083
- package/schema/openai-responses.schema.md +226196 -0
- package/schema/stream.md +2592 -0
- package/dist/server/database.js +0 -1609
- package/dist/server/migrate-to-fs.js +0 -353
- package/dist/server/transformers/claude-openai.js +0 -868
- package/dist/server/transformers/gemini.js +0 -625
- package/dist/ui/assets/index-DNtgPQMm.js +0 -511
package/dist/server/database.js
DELETED
|
@@ -1,1609 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
var __rest = (this && this.__rest) || function (s, e) {
|
|
12
|
-
var t = {};
|
|
13
|
-
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
14
|
-
t[p] = s[p];
|
|
15
|
-
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
16
|
-
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
17
|
-
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
18
|
-
t[p[i]] = s[p[i]];
|
|
19
|
-
}
|
|
20
|
-
return t;
|
|
21
|
-
};
|
|
22
|
-
var __asyncValues = (this && this.__asyncValues) || function (o) {
|
|
23
|
-
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
24
|
-
var m = o[Symbol.asyncIterator], i;
|
|
25
|
-
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
|
26
|
-
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
|
27
|
-
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
|
28
|
-
};
|
|
29
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
30
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
31
|
-
};
|
|
32
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
33
|
-
exports.DatabaseManager = void 0;
|
|
34
|
-
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
35
|
-
const level_1 = require("level");
|
|
36
|
-
const path_1 = __importDefault(require("path"));
|
|
37
|
-
const crypto_1 = __importDefault(require("crypto"));
|
|
38
|
-
const crypto_js_1 = __importDefault(require("crypto-js"));
|
|
39
|
-
class DatabaseManager {
|
|
40
|
-
constructor(dataPath) {
|
|
41
|
-
Object.defineProperty(this, "db", {
|
|
42
|
-
enumerable: true,
|
|
43
|
-
configurable: true,
|
|
44
|
-
writable: true,
|
|
45
|
-
value: void 0
|
|
46
|
-
});
|
|
47
|
-
Object.defineProperty(this, "logDb", {
|
|
48
|
-
enumerable: true,
|
|
49
|
-
configurable: true,
|
|
50
|
-
writable: true,
|
|
51
|
-
value: void 0
|
|
52
|
-
});
|
|
53
|
-
Object.defineProperty(this, "errorLogDb", {
|
|
54
|
-
enumerable: true,
|
|
55
|
-
configurable: true,
|
|
56
|
-
writable: true,
|
|
57
|
-
value: void 0
|
|
58
|
-
});
|
|
59
|
-
Object.defineProperty(this, "blacklistDb", {
|
|
60
|
-
enumerable: true,
|
|
61
|
-
configurable: true,
|
|
62
|
-
writable: true,
|
|
63
|
-
value: void 0
|
|
64
|
-
});
|
|
65
|
-
// 缓存机制:总数查询缓存
|
|
66
|
-
Object.defineProperty(this, "logsCountCache", {
|
|
67
|
-
enumerable: true,
|
|
68
|
-
configurable: true,
|
|
69
|
-
writable: true,
|
|
70
|
-
value: null
|
|
71
|
-
});
|
|
72
|
-
Object.defineProperty(this, "errorLogsCountCache", {
|
|
73
|
-
enumerable: true,
|
|
74
|
-
configurable: true,
|
|
75
|
-
writable: true,
|
|
76
|
-
value: null
|
|
77
|
-
});
|
|
78
|
-
Object.defineProperty(this, "CACHE_TTL", {
|
|
79
|
-
enumerable: true,
|
|
80
|
-
configurable: true,
|
|
81
|
-
writable: true,
|
|
82
|
-
value: 1000
|
|
83
|
-
}); // 1秒缓存TTL
|
|
84
|
-
this.db = new better_sqlite3_1.default(path_1.default.join(dataPath, 'app.db'));
|
|
85
|
-
// 启用外键约束(SQLite 默认禁用,必须手动启用才能使 ON DELETE CASCADE 生效)
|
|
86
|
-
this.db.pragma('foreign_keys = ON');
|
|
87
|
-
// 配置数据库以确保实时读取最新数据
|
|
88
|
-
// WAL 模式 + normal 同步模式: 提供最佳的并发性和实时性
|
|
89
|
-
this.db.pragma('journal_mode = WAL');
|
|
90
|
-
this.db.pragma('synchronous = NORMAL');
|
|
91
|
-
// 确保读取操作不会看到旧的数据快照
|
|
92
|
-
// 在 WAL 模式下,默认情况下读取操作不会阻塞写入操作
|
|
93
|
-
// 设置 read_uncommitted = 0 确保读取最新提交的数据
|
|
94
|
-
this.db.pragma('read_uncommitted = 0');
|
|
95
|
-
this.logDb = new level_1.Level(path_1.default.join(dataPath, 'logs'), { valueEncoding: 'json' });
|
|
96
|
-
this.errorLogDb = new level_1.Level(path_1.default.join(dataPath, 'error-logs'), { valueEncoding: 'json' });
|
|
97
|
-
this.blacklistDb = new level_1.Level(path_1.default.join(dataPath, 'service-blacklist'), { valueEncoding: 'json' });
|
|
98
|
-
}
|
|
99
|
-
initialize() {
|
|
100
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
101
|
-
this.createTables();
|
|
102
|
-
yield this.runMigrations();
|
|
103
|
-
yield this.ensureDefaultConfig();
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
runMigrations() {
|
|
107
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
108
|
-
const columns = this.db.pragma('table_info(api_services)');
|
|
109
|
-
// 检查是否有旧的 max_output_tokens 字段(单值版本)
|
|
110
|
-
const hasOldMaxOutputTokens = columns.some((col) => col.name === 'max_output_tokens');
|
|
111
|
-
// 检查是否已经有新的 model_limits 字段(JSON 版本)
|
|
112
|
-
const hasModelLimits = columns.some((col) => col.name === 'model_limits');
|
|
113
|
-
if (!hasModelLimits) {
|
|
114
|
-
if (hasOldMaxOutputTokens) {
|
|
115
|
-
// 如果有旧字段,先删除旧字段(SQLite 不支持 ALTER TABLE DROP COLUMN,需要重建表)
|
|
116
|
-
console.log('[DB] Running migration: Replacing max_output_tokens with model_limits');
|
|
117
|
-
yield this.migrateMaxOutputTokensToModelLimits();
|
|
118
|
-
}
|
|
119
|
-
else {
|
|
120
|
-
// 直接添加新字段
|
|
121
|
-
console.log('[DB] Running migration: Adding model_limits column to api_services table');
|
|
122
|
-
this.db.exec('ALTER TABLE api_services ADD COLUMN model_limits TEXT;');
|
|
123
|
-
console.log('[DB] Migration completed: model_limits column added');
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
// 检查vendors表是否有sort_order字段
|
|
127
|
-
const vendorsColumns = this.db.pragma('table_info(vendors)');
|
|
128
|
-
const hasSortOrder = vendorsColumns.some((col) => col.name === 'sort_order');
|
|
129
|
-
if (!hasSortOrder) {
|
|
130
|
-
console.log('[DB] Running migration: Adding sort_order column to vendors table');
|
|
131
|
-
this.db.exec('ALTER TABLE vendors ADD COLUMN sort_order INTEGER DEFAULT 0;');
|
|
132
|
-
console.log('[DB] Migration completed: sort_order column added to vendors');
|
|
133
|
-
}
|
|
134
|
-
// 检查rules表是否有token相关字段
|
|
135
|
-
const rulesColumns = this.db.pragma('table_info(rules)');
|
|
136
|
-
const hasTokenLimit = rulesColumns.some((col) => col.name === 'token_limit');
|
|
137
|
-
const hasTotalTokensUsed = rulesColumns.some((col) => col.name === 'total_tokens_used');
|
|
138
|
-
const hasResetInterval = rulesColumns.some((col) => col.name === 'reset_interval');
|
|
139
|
-
const hasLastResetAt = rulesColumns.some((col) => col.name === 'last_reset_at');
|
|
140
|
-
if (!hasTokenLimit) {
|
|
141
|
-
console.log('[DB] Running migration: Adding token_limit column to rules table');
|
|
142
|
-
this.db.exec('ALTER TABLE rules ADD COLUMN token_limit INTEGER;');
|
|
143
|
-
console.log('[DB] Migration completed: token_limit column added');
|
|
144
|
-
}
|
|
145
|
-
if (!hasTotalTokensUsed) {
|
|
146
|
-
console.log('[DB] Running migration: Adding total_tokens_used column to rules table');
|
|
147
|
-
this.db.exec('ALTER TABLE rules ADD COLUMN total_tokens_used INTEGER DEFAULT 0;');
|
|
148
|
-
console.log('[DB] Migration completed: total_tokens_used column added');
|
|
149
|
-
}
|
|
150
|
-
if (!hasResetInterval) {
|
|
151
|
-
console.log('[DB] Running migration: Adding reset_interval column to rules table');
|
|
152
|
-
this.db.exec('ALTER TABLE rules ADD COLUMN reset_interval INTEGER;');
|
|
153
|
-
console.log('[DB] Migration completed: reset_interval column added');
|
|
154
|
-
}
|
|
155
|
-
if (!hasLastResetAt) {
|
|
156
|
-
console.log('[DB] Running migration: Adding last_reset_at column to rules table');
|
|
157
|
-
this.db.exec('ALTER TABLE rules ADD COLUMN last_reset_at INTEGER;');
|
|
158
|
-
console.log('[DB] Migration completed: last_reset_at column added');
|
|
159
|
-
}
|
|
160
|
-
// 检查rules表是否有timeout字段
|
|
161
|
-
const hasRuleTimeout = rulesColumns.some((col) => col.name === 'timeout');
|
|
162
|
-
if (!hasRuleTimeout) {
|
|
163
|
-
console.log('[DB] Running migration: Adding timeout column to rules table');
|
|
164
|
-
this.db.exec('ALTER TABLE rules ADD COLUMN timeout INTEGER;');
|
|
165
|
-
console.log('[DB] Migration completed: timeout column added to rules');
|
|
166
|
-
}
|
|
167
|
-
// 检查api_services表是否有timeout字段,如果有则移除
|
|
168
|
-
const hasServiceTimeout = columns.some((col) => col.name === 'timeout');
|
|
169
|
-
if (hasServiceTimeout) {
|
|
170
|
-
console.log('[DB] Running migration: Removing timeout column from api_services table');
|
|
171
|
-
yield this.migrateRemoveServiceTimeout();
|
|
172
|
-
console.log('[DB] Migration completed: timeout column removed from api_services');
|
|
173
|
-
}
|
|
174
|
-
// 检查api_services表是否有enable_proxy字段
|
|
175
|
-
const hasEnableProxy = columns.some((col) => col.name === 'enable_proxy');
|
|
176
|
-
if (!hasEnableProxy) {
|
|
177
|
-
console.log('[DB] Running migration: Adding enable_proxy column to api_services table');
|
|
178
|
-
this.db.exec('ALTER TABLE api_services ADD COLUMN enable_proxy INTEGER DEFAULT 0;');
|
|
179
|
-
console.log('[DB] Migration completed: enable_proxy column added');
|
|
180
|
-
}
|
|
181
|
-
// 检查rules表是否有请求次数相关字段
|
|
182
|
-
const hasRequestCountLimit = rulesColumns.some((col) => col.name === 'request_count_limit');
|
|
183
|
-
const hasTotalRequestsUsed = rulesColumns.some((col) => col.name === 'total_requests_used');
|
|
184
|
-
const hasRequestResetInterval = rulesColumns.some((col) => col.name === 'request_reset_interval');
|
|
185
|
-
const hasRequestLastResetAt = rulesColumns.some((col) => col.name === 'request_last_reset_at');
|
|
186
|
-
if (!hasRequestCountLimit) {
|
|
187
|
-
console.log('[DB] Running migration: Adding request_count_limit column to rules table');
|
|
188
|
-
this.db.exec('ALTER TABLE rules ADD COLUMN request_count_limit INTEGER;');
|
|
189
|
-
console.log('[DB] Migration completed: request_count_limit column added');
|
|
190
|
-
}
|
|
191
|
-
if (!hasTotalRequestsUsed) {
|
|
192
|
-
console.log('[DB] Running migration: Adding total_requests_used column to rules table');
|
|
193
|
-
this.db.exec('ALTER TABLE rules ADD COLUMN total_requests_used INTEGER DEFAULT 0;');
|
|
194
|
-
console.log('[DB] Migration completed: total_requests_used column added');
|
|
195
|
-
}
|
|
196
|
-
if (!hasRequestResetInterval) {
|
|
197
|
-
console.log('[DB] Running migration: Adding request_reset_interval column to rules table');
|
|
198
|
-
this.db.exec('ALTER TABLE rules ADD COLUMN request_reset_interval INTEGER;');
|
|
199
|
-
console.log('[DB] Migration completed: request_reset_interval column added');
|
|
200
|
-
}
|
|
201
|
-
if (!hasRequestLastResetAt) {
|
|
202
|
-
console.log('[DB] Running migration: Adding request_last_reset_at column to rules table');
|
|
203
|
-
this.db.exec('ALTER TABLE rules ADD COLUMN request_last_reset_at INTEGER;');
|
|
204
|
-
console.log('[DB] Migration completed: request_last_reset_at column added');
|
|
205
|
-
}
|
|
206
|
-
// 检查rules表是否有request_reset_base_time字段
|
|
207
|
-
const hasRequestResetBaseTime = rulesColumns.some((col) => col.name === 'request_reset_base_time');
|
|
208
|
-
if (!hasRequestResetBaseTime) {
|
|
209
|
-
console.log('[DB] Running migration: Adding request_reset_base_time column to rules table');
|
|
210
|
-
this.db.exec('ALTER TABLE rules ADD COLUMN request_reset_base_time INTEGER;');
|
|
211
|
-
console.log('[DB] Migration completed: request_reset_base_time column added');
|
|
212
|
-
}
|
|
213
|
-
// 检查rules表是否有token_reset_base_time字段
|
|
214
|
-
const hasTokenResetBaseTime = rulesColumns.some((col) => col.name === 'token_reset_base_time');
|
|
215
|
-
if (!hasTokenResetBaseTime) {
|
|
216
|
-
console.log('[DB] Running migration: Adding token_reset_base_time column to rules table');
|
|
217
|
-
this.db.exec('ALTER TABLE rules ADD COLUMN token_reset_base_time INTEGER;');
|
|
218
|
-
console.log('[DB] Migration completed: token_reset_base_time column added');
|
|
219
|
-
}
|
|
220
|
-
// 检查api_services表是否有超量配置相关字段
|
|
221
|
-
// Token超量配置
|
|
222
|
-
const hasEnableTokenLimit = columns.some((col) => col.name === 'enable_token_limit');
|
|
223
|
-
if (!hasEnableTokenLimit) {
|
|
224
|
-
console.log('[DB] Running migration: Adding enable_token_limit column to api_services table');
|
|
225
|
-
this.db.exec('ALTER TABLE api_services ADD COLUMN enable_token_limit INTEGER DEFAULT 0;');
|
|
226
|
-
console.log('[DB] Migration completed: enable_token_limit column added');
|
|
227
|
-
}
|
|
228
|
-
const hasServiceTokenLimit = columns.some((col) => col.name === 'token_limit');
|
|
229
|
-
if (!hasServiceTokenLimit) {
|
|
230
|
-
console.log('[DB] Running migration: Adding token_limit column to api_services table');
|
|
231
|
-
this.db.exec('ALTER TABLE api_services ADD COLUMN token_limit INTEGER;');
|
|
232
|
-
console.log('[DB] Migration completed: token_limit column added');
|
|
233
|
-
}
|
|
234
|
-
const hasTokenResetInterval = columns.some((col) => col.name === 'token_reset_interval');
|
|
235
|
-
if (!hasTokenResetInterval) {
|
|
236
|
-
console.log('[DB] Running migration: Adding token_reset_interval column to api_services table');
|
|
237
|
-
this.db.exec('ALTER TABLE api_services ADD COLUMN token_reset_interval INTEGER;');
|
|
238
|
-
console.log('[DB] Migration completed: token_reset_interval column added');
|
|
239
|
-
}
|
|
240
|
-
const hasServiceTokenResetBaseTime = columns.some((col) => col.name === 'token_reset_base_time');
|
|
241
|
-
if (!hasServiceTokenResetBaseTime) {
|
|
242
|
-
console.log('[DB] Running migration: Adding token_reset_base_time column to api_services table');
|
|
243
|
-
this.db.exec('ALTER TABLE api_services ADD COLUMN token_reset_base_time INTEGER;');
|
|
244
|
-
console.log('[DB] Migration completed: token_reset_base_time column added');
|
|
245
|
-
}
|
|
246
|
-
// 请求次数超量配置
|
|
247
|
-
const hasEnableRequestLimit = columns.some((col) => col.name === 'enable_request_limit');
|
|
248
|
-
if (!hasEnableRequestLimit) {
|
|
249
|
-
console.log('[DB] Running migration: Adding enable_request_limit column to api_services table');
|
|
250
|
-
this.db.exec('ALTER TABLE api_services ADD COLUMN enable_request_limit INTEGER DEFAULT 0;');
|
|
251
|
-
console.log('[DB] Migration completed: enable_request_limit column added');
|
|
252
|
-
}
|
|
253
|
-
const hasServiceRequestCountLimit = columns.some((col) => col.name === 'request_count_limit');
|
|
254
|
-
if (!hasServiceRequestCountLimit) {
|
|
255
|
-
console.log('[DB] Running migration: Adding request_count_limit column to api_services table');
|
|
256
|
-
this.db.exec('ALTER TABLE api_services ADD COLUMN request_count_limit INTEGER;');
|
|
257
|
-
console.log('[DB] Migration completed: request_count_limit column added');
|
|
258
|
-
}
|
|
259
|
-
const hasServiceRequestResetInterval = columns.some((col) => col.name === 'request_reset_interval');
|
|
260
|
-
if (!hasServiceRequestResetInterval) {
|
|
261
|
-
console.log('[DB] Running migration: Adding request_reset_interval column to api_services table');
|
|
262
|
-
this.db.exec('ALTER TABLE api_services ADD COLUMN request_reset_interval INTEGER;');
|
|
263
|
-
console.log('[DB] Migration completed: request_reset_interval column added');
|
|
264
|
-
}
|
|
265
|
-
const hasServiceRequestResetBaseTime = columns.some((col) => col.name === 'request_reset_base_time');
|
|
266
|
-
if (!hasServiceRequestResetBaseTime) {
|
|
267
|
-
console.log('[DB] Running migration: Adding request_reset_base_time column to api_services table');
|
|
268
|
-
this.db.exec('ALTER TABLE api_services ADD COLUMN request_reset_base_time INTEGER;');
|
|
269
|
-
console.log('[DB] Migration completed: request_reset_base_time column added');
|
|
270
|
-
}
|
|
271
|
-
// 检查api_services表是否有auth_type字段
|
|
272
|
-
const hasAuthType = columns.some((col) => col.name === 'auth_type');
|
|
273
|
-
if (!hasAuthType) {
|
|
274
|
-
console.log('[DB] Running migration: Adding auth_type column to api_services table');
|
|
275
|
-
this.db.exec('ALTER TABLE api_services ADD COLUMN auth_type TEXT DEFAULT NULL;');
|
|
276
|
-
console.log('[DB] Migration completed: auth_type column added');
|
|
277
|
-
}
|
|
278
|
-
// 检查rules表是否有is_disabled字段(临时屏蔽功能)
|
|
279
|
-
const hasIsDisabled = rulesColumns.some((col) => col.name === 'is_disabled');
|
|
280
|
-
if (!hasIsDisabled) {
|
|
281
|
-
console.log('[DB] Running migration: Adding is_disabled column to rules table');
|
|
282
|
-
this.db.exec('ALTER TABLE rules ADD COLUMN is_disabled INTEGER DEFAULT 0;');
|
|
283
|
-
console.log('[DB] Migration completed: is_disabled column added');
|
|
284
|
-
}
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
migrateMaxOutputTokensToModelLimits() {
|
|
288
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
289
|
-
// SQLite 不支持直接删除列,需要重建表
|
|
290
|
-
// 先临时禁用外键约束
|
|
291
|
-
this.db.pragma('foreign_keys = OFF');
|
|
292
|
-
this.db.exec(`
|
|
293
|
-
CREATE TABLE api_services_new (
|
|
294
|
-
id TEXT PRIMARY KEY,
|
|
295
|
-
vendor_id TEXT NOT NULL,
|
|
296
|
-
name TEXT NOT NULL,
|
|
297
|
-
api_url TEXT NOT NULL,
|
|
298
|
-
api_key TEXT NOT NULL,
|
|
299
|
-
timeout INTEGER,
|
|
300
|
-
source_type TEXT,
|
|
301
|
-
supported_models TEXT,
|
|
302
|
-
model_limits TEXT,
|
|
303
|
-
created_at INTEGER NOT NULL,
|
|
304
|
-
updated_at INTEGER NOT NULL,
|
|
305
|
-
FOREIGN KEY (vendor_id) REFERENCES vendors(id) ON DELETE CASCADE
|
|
306
|
-
);
|
|
307
|
-
|
|
308
|
-
INSERT INTO api_services_new
|
|
309
|
-
SELECT
|
|
310
|
-
id, vendor_id, name, api_url, api_key, timeout, source_type, supported_models,
|
|
311
|
-
NULL, -- model_limits 设为 NULL,旧数据需要手动配置
|
|
312
|
-
created_at, updated_at
|
|
313
|
-
FROM api_services;
|
|
314
|
-
|
|
315
|
-
DROP TABLE api_services;
|
|
316
|
-
ALTER TABLE api_services_new RENAME TO api_services;
|
|
317
|
-
`);
|
|
318
|
-
// 重新启用外键约束
|
|
319
|
-
this.db.pragma('foreign_keys = ON');
|
|
320
|
-
console.log('[DB] Migration completed: Replaced max_output_tokens with model_limits');
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
migrateRemoveServiceTimeout() {
|
|
324
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
325
|
-
// SQLite 不支持直接删除列,需要重建表
|
|
326
|
-
this.db.pragma('foreign_keys = OFF');
|
|
327
|
-
this.db.exec(`
|
|
328
|
-
CREATE TABLE api_services_new (
|
|
329
|
-
id TEXT PRIMARY KEY,
|
|
330
|
-
vendor_id TEXT NOT NULL,
|
|
331
|
-
name TEXT NOT NULL,
|
|
332
|
-
api_url TEXT NOT NULL,
|
|
333
|
-
api_key TEXT NOT NULL,
|
|
334
|
-
source_type TEXT,
|
|
335
|
-
supported_models TEXT,
|
|
336
|
-
model_limits TEXT,
|
|
337
|
-
created_at INTEGER NOT NULL,
|
|
338
|
-
updated_at INTEGER NOT NULL,
|
|
339
|
-
FOREIGN KEY (vendor_id) REFERENCES vendors(id) ON DELETE CASCADE
|
|
340
|
-
);
|
|
341
|
-
|
|
342
|
-
INSERT INTO api_services_new (id, vendor_id, name, api_url, api_key, source_type, supported_models, model_limits, created_at, updated_at)
|
|
343
|
-
SELECT id, vendor_id, name, api_url, api_key, source_type, supported_models, model_limits, created_at, updated_at
|
|
344
|
-
FROM api_services;
|
|
345
|
-
|
|
346
|
-
DROP TABLE api_services;
|
|
347
|
-
ALTER TABLE api_services_new RENAME TO api_services;
|
|
348
|
-
`);
|
|
349
|
-
this.db.pragma('foreign_keys = ON');
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
createTables() {
|
|
353
|
-
this.db.exec(`
|
|
354
|
-
CREATE TABLE IF NOT EXISTS vendors (
|
|
355
|
-
id TEXT PRIMARY KEY,
|
|
356
|
-
name TEXT NOT NULL,
|
|
357
|
-
description TEXT,
|
|
358
|
-
sort_order INTEGER DEFAULT 0,
|
|
359
|
-
created_at INTEGER NOT NULL,
|
|
360
|
-
updated_at INTEGER NOT NULL
|
|
361
|
-
);
|
|
362
|
-
|
|
363
|
-
CREATE TABLE IF NOT EXISTS api_services (
|
|
364
|
-
id TEXT PRIMARY KEY,
|
|
365
|
-
vendor_id TEXT NOT NULL,
|
|
366
|
-
name TEXT NOT NULL,
|
|
367
|
-
api_url TEXT NOT NULL,
|
|
368
|
-
api_key TEXT NOT NULL,
|
|
369
|
-
source_type TEXT,
|
|
370
|
-
supported_models TEXT,
|
|
371
|
-
created_at INTEGER NOT NULL,
|
|
372
|
-
updated_at INTEGER NOT NULL,
|
|
373
|
-
FOREIGN KEY (vendor_id) REFERENCES vendors(id) ON DELETE CASCADE
|
|
374
|
-
);
|
|
375
|
-
|
|
376
|
-
CREATE TABLE IF NOT EXISTS routes (
|
|
377
|
-
id TEXT PRIMARY KEY,
|
|
378
|
-
name TEXT NOT NULL,
|
|
379
|
-
description TEXT,
|
|
380
|
-
target_type TEXT NOT NULL CHECK(target_type IN ('claude-code', 'codex')),
|
|
381
|
-
is_active INTEGER DEFAULT 0,
|
|
382
|
-
created_at INTEGER NOT NULL,
|
|
383
|
-
updated_at INTEGER NOT NULL
|
|
384
|
-
);
|
|
385
|
-
|
|
386
|
-
CREATE TABLE IF NOT EXISTS rules (
|
|
387
|
-
id TEXT PRIMARY KEY,
|
|
388
|
-
route_id TEXT NOT NULL,
|
|
389
|
-
content_type TEXT NOT NULL CHECK(content_type IN ('default', 'background', 'thinking', 'long-context', 'image-understanding', 'model-mapping', 'high-iq')),
|
|
390
|
-
target_service_id TEXT NOT NULL,
|
|
391
|
-
target_model TEXT,
|
|
392
|
-
replaced_model TEXT,
|
|
393
|
-
sort_order INTEGER DEFAULT 0,
|
|
394
|
-
timeout INTEGER,
|
|
395
|
-
token_limit INTEGER,
|
|
396
|
-
total_tokens_used INTEGER DEFAULT 0,
|
|
397
|
-
reset_interval INTEGER,
|
|
398
|
-
last_reset_at INTEGER,
|
|
399
|
-
token_reset_base_time INTEGER,
|
|
400
|
-
request_count_limit INTEGER,
|
|
401
|
-
total_requests_used INTEGER DEFAULT 0,
|
|
402
|
-
request_reset_interval INTEGER,
|
|
403
|
-
request_last_reset_at INTEGER,
|
|
404
|
-
request_reset_base_time INTEGER,
|
|
405
|
-
is_disabled INTEGER DEFAULT 0,
|
|
406
|
-
created_at INTEGER NOT NULL,
|
|
407
|
-
updated_at INTEGER NOT NULL,
|
|
408
|
-
FOREIGN KEY (route_id) REFERENCES routes(id) ON DELETE CASCADE,
|
|
409
|
-
FOREIGN KEY (target_service_id) REFERENCES api_services(id) ON DELETE CASCADE
|
|
410
|
-
);
|
|
411
|
-
|
|
412
|
-
CREATE TABLE IF NOT EXISTS config (
|
|
413
|
-
key TEXT PRIMARY KEY,
|
|
414
|
-
value TEXT NOT NULL
|
|
415
|
-
);
|
|
416
|
-
|
|
417
|
-
CREATE TABLE IF NOT EXISTS sessions (
|
|
418
|
-
id TEXT PRIMARY KEY,
|
|
419
|
-
target_type TEXT NOT NULL CHECK(target_type IN ('claude-code', 'codex')),
|
|
420
|
-
title TEXT,
|
|
421
|
-
first_request_at INTEGER NOT NULL,
|
|
422
|
-
last_request_at INTEGER NOT NULL,
|
|
423
|
-
request_count INTEGER DEFAULT 1,
|
|
424
|
-
total_tokens INTEGER DEFAULT 0,
|
|
425
|
-
vendor_id TEXT,
|
|
426
|
-
vendor_name TEXT,
|
|
427
|
-
service_id TEXT,
|
|
428
|
-
service_name TEXT,
|
|
429
|
-
model TEXT
|
|
430
|
-
);
|
|
431
|
-
`);
|
|
432
|
-
}
|
|
433
|
-
ensureDefaultConfig() {
|
|
434
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
435
|
-
const config = this.db.prepare('SELECT * FROM config WHERE key = ?').get('app_config');
|
|
436
|
-
if (!config) {
|
|
437
|
-
const defaultConfig = {
|
|
438
|
-
enableLogging: true,
|
|
439
|
-
logRetentionDays: 30,
|
|
440
|
-
maxLogSize: 100000,
|
|
441
|
-
apiKey: '',
|
|
442
|
-
enableFailover: true, // 默认启用智能故障切换
|
|
443
|
-
proxyEnabled: false, // 默认不启用代理
|
|
444
|
-
proxyUrl: '',
|
|
445
|
-
proxyUsername: '',
|
|
446
|
-
proxyPassword: '',
|
|
447
|
-
};
|
|
448
|
-
this.db.prepare('INSERT INTO config (key, value) VALUES (?, ?)').run('app_config', JSON.stringify(defaultConfig));
|
|
449
|
-
}
|
|
450
|
-
});
|
|
451
|
-
}
|
|
452
|
-
// Vendor operations
|
|
453
|
-
getVendors() {
|
|
454
|
-
const rows = this.db.prepare('SELECT * FROM vendors ORDER BY sort_order DESC, created_at DESC').all();
|
|
455
|
-
return rows.map((row) => {
|
|
456
|
-
const vendorId = row.id;
|
|
457
|
-
// 获取该供应商的所有服务
|
|
458
|
-
const services = this.getAPIServices(vendorId);
|
|
459
|
-
// 移除服务中的 vendorId 字段
|
|
460
|
-
const servicesWithoutVendorId = services.map((_a) => {
|
|
461
|
-
var { vendorId } = _a, service = __rest(_a, ["vendorId"]);
|
|
462
|
-
return service;
|
|
463
|
-
});
|
|
464
|
-
return {
|
|
465
|
-
id: row.id,
|
|
466
|
-
name: row.name,
|
|
467
|
-
description: row.description,
|
|
468
|
-
sortOrder: row.sort_order,
|
|
469
|
-
services: servicesWithoutVendorId, // 添加嵌套的 services 数组
|
|
470
|
-
createdAt: row.created_at,
|
|
471
|
-
updatedAt: row.updated_at,
|
|
472
|
-
};
|
|
473
|
-
});
|
|
474
|
-
}
|
|
475
|
-
createVendor(vendor) {
|
|
476
|
-
const id = crypto_1.default.randomUUID();
|
|
477
|
-
const now = Date.now();
|
|
478
|
-
this.db
|
|
479
|
-
.prepare('INSERT INTO vendors (id, name, description, sort_order, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)')
|
|
480
|
-
.run(id, vendor.name, vendor.description || null, vendor.sortOrder || 0, now, now);
|
|
481
|
-
return Object.assign(Object.assign({}, vendor), { id, createdAt: now, updatedAt: now });
|
|
482
|
-
}
|
|
483
|
-
updateVendor(id, vendor) {
|
|
484
|
-
const now = Date.now();
|
|
485
|
-
const result = this.db
|
|
486
|
-
.prepare('UPDATE vendors SET name = ?, description = ?, sort_order = ?, updated_at = ? WHERE id = ?')
|
|
487
|
-
.run(vendor.name, vendor.description || null, vendor.sortOrder !== undefined ? vendor.sortOrder : 0, now, id);
|
|
488
|
-
return result.changes > 0;
|
|
489
|
-
}
|
|
490
|
-
deleteVendor(id) {
|
|
491
|
-
const result = this.db.prepare('DELETE FROM vendors WHERE id = ?').run(id);
|
|
492
|
-
return result.changes > 0;
|
|
493
|
-
}
|
|
494
|
-
// API Service operations
|
|
495
|
-
getAPIServices(vendorId) {
|
|
496
|
-
// 每次都重新准备语句以确保获取最新数据
|
|
497
|
-
const query = vendorId
|
|
498
|
-
? 'SELECT * FROM api_services WHERE vendor_id = ? ORDER BY created_at DESC'
|
|
499
|
-
: 'SELECT * FROM api_services ORDER BY created_at DESC';
|
|
500
|
-
// 不缓存 prepared statement,每次重新创建以确保读取最新数据
|
|
501
|
-
const stmt = this.db.prepare(query);
|
|
502
|
-
const rows = vendorId ? stmt.all(vendorId) : stmt.all();
|
|
503
|
-
const services = rows.map((row) => ({
|
|
504
|
-
id: row.id,
|
|
505
|
-
vendorId: row.vendor_id,
|
|
506
|
-
name: row.name,
|
|
507
|
-
apiUrl: row.api_url,
|
|
508
|
-
apiKey: row.api_key,
|
|
509
|
-
sourceType: row.source_type,
|
|
510
|
-
authType: row.auth_type,
|
|
511
|
-
supportedModels: row.supported_models ? row.supported_models.split(',').map((model) => model.trim()).filter((model) => model.length > 0) : undefined,
|
|
512
|
-
modelLimits: row.model_limits ? JSON.parse(row.model_limits) : undefined,
|
|
513
|
-
enableProxy: row.enable_proxy === 1,
|
|
514
|
-
// Token超量配置
|
|
515
|
-
enableTokenLimit: row.enable_token_limit === 1,
|
|
516
|
-
tokenLimit: row.token_limit,
|
|
517
|
-
tokenResetInterval: row.token_reset_interval,
|
|
518
|
-
tokenResetBaseTime: row.token_reset_base_time,
|
|
519
|
-
// 请求次数超量配置
|
|
520
|
-
enableRequestLimit: row.enable_request_limit === 1,
|
|
521
|
-
requestCountLimit: row.request_count_limit,
|
|
522
|
-
requestResetInterval: row.request_reset_interval,
|
|
523
|
-
requestResetBaseTime: row.request_reset_base_time,
|
|
524
|
-
createdAt: row.created_at,
|
|
525
|
-
updatedAt: row.updated_at,
|
|
526
|
-
}));
|
|
527
|
-
// 调试日志: 记录读取的服务信息
|
|
528
|
-
if (process.env.NODE_ENV === 'development' && services.length > 0) {
|
|
529
|
-
console.log(`[DB] Read ${services.length} services from database, first service: ${services[0].name} -> ${services[0].apiUrl}`);
|
|
530
|
-
}
|
|
531
|
-
return services;
|
|
532
|
-
}
|
|
533
|
-
createAPIService(service) {
|
|
534
|
-
const id = crypto_1.default.randomUUID();
|
|
535
|
-
const now = Date.now();
|
|
536
|
-
this.db
|
|
537
|
-
.prepare('INSERT INTO api_services (id, vendor_id, name, api_url, api_key, source_type, auth_type, supported_models, model_limits, enable_proxy, enable_token_limit, token_limit, token_reset_interval, token_reset_base_time, enable_request_limit, request_count_limit, request_reset_interval, request_reset_base_time, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)')
|
|
538
|
-
.run(id, service.vendorId, service.name, service.apiUrl, service.apiKey, service.sourceType || null, service.authType || null, service.supportedModels ? service.supportedModels.join(',') : null, service.modelLimits ? JSON.stringify(service.modelLimits) : null, service.enableProxy ? 1 : 0, service.enableTokenLimit ? 1 : 0, service.tokenLimit || null, service.tokenResetInterval || null, service.tokenResetBaseTime || null, service.enableRequestLimit ? 1 : 0, service.requestCountLimit || null, service.requestResetInterval || null, service.requestResetBaseTime || null, now, now);
|
|
539
|
-
return Object.assign(Object.assign({}, service), { id, createdAt: now, updatedAt: now });
|
|
540
|
-
}
|
|
541
|
-
updateAPIService(id, service) {
|
|
542
|
-
const now = Date.now();
|
|
543
|
-
const result = this.db
|
|
544
|
-
.prepare('UPDATE api_services SET vendor_id = ?, name = ?, api_url = ?, api_key = ?, source_type = ?, auth_type = ?, supported_models = ?, model_limits = ?, enable_proxy = ?, enable_token_limit = ?, token_limit = ?, token_reset_interval = ?, token_reset_base_time = ?, enable_request_limit = ?, request_count_limit = ?, request_reset_interval = ?, request_reset_base_time = ?, updated_at = ? WHERE id = ?')
|
|
545
|
-
.run(service.vendorId, service.name, service.apiUrl, service.apiKey, service.sourceType || null, service.authType || null, service.supportedModels ? service.supportedModels.join(',') : null, service.modelLimits ? JSON.stringify(service.modelLimits) : null, service.enableProxy !== undefined ? (service.enableProxy ? 1 : 0) : null, service.enableTokenLimit !== undefined ? (service.enableTokenLimit ? 1 : 0) : null, service.tokenLimit !== undefined ? service.tokenLimit : null, service.tokenResetInterval !== undefined ? service.tokenResetInterval : null, service.tokenResetBaseTime !== undefined ? service.tokenResetBaseTime : null, service.enableRequestLimit !== undefined ? (service.enableRequestLimit ? 1 : 0) : null, service.requestCountLimit !== undefined ? service.requestCountLimit : null, service.requestResetInterval !== undefined ? service.requestResetInterval : null, service.requestResetBaseTime !== undefined ? service.requestResetBaseTime : null, now, id);
|
|
546
|
-
// 调试日志: 记录更新操作
|
|
547
|
-
if (result.changes > 0 && process.env.NODE_ENV === 'development') {
|
|
548
|
-
console.log(`[DB] Updated service ${id}: ${service.name} -> ${service.apiUrl}`);
|
|
549
|
-
}
|
|
550
|
-
// 如果更新成功,检查是否需要同步更新关联规则的超量限制
|
|
551
|
-
if (result.changes > 0) {
|
|
552
|
-
this.syncRulesWithServiceLimits(id, service);
|
|
553
|
-
}
|
|
554
|
-
return result.changes > 0;
|
|
555
|
-
}
|
|
556
|
-
/**
|
|
557
|
-
* 同步更新使用该服务的规则的超量限制
|
|
558
|
-
* 当API服务的超量限制修改时,自动更新所有使用该服务的规则
|
|
559
|
-
*/
|
|
560
|
-
syncRulesWithServiceLimits(serviceId, service) {
|
|
561
|
-
// 获取所有使用该服务的规则
|
|
562
|
-
const rules = this.db.prepare('SELECT id FROM rules WHERE target_service_id = ?').all(serviceId);
|
|
563
|
-
if (rules.length === 0) {
|
|
564
|
-
return; // 没有规则使用此服务,无需同步
|
|
565
|
-
}
|
|
566
|
-
const now = Date.now();
|
|
567
|
-
const ruleIds = rules.map(r => r.id);
|
|
568
|
-
// Token超量限制同步
|
|
569
|
-
if (service.enableTokenLimit !== undefined || service.tokenLimit !== undefined ||
|
|
570
|
-
service.tokenResetInterval !== undefined || service.tokenResetBaseTime !== undefined) {
|
|
571
|
-
// 获取当前服务的最新配置
|
|
572
|
-
const currentService = this.db.prepare('SELECT enable_token_limit, token_limit, token_reset_interval, token_reset_base_time FROM api_services WHERE id = ?').get(serviceId);
|
|
573
|
-
if (currentService && currentService.enable_token_limit === 1) {
|
|
574
|
-
// 启用了Token超量限制,同步到所有规则
|
|
575
|
-
this.db.prepare('UPDATE rules SET token_limit = ?, reset_interval = ?, token_reset_base_time = ?, updated_at = ? WHERE target_service_id = ?').run(currentService.token_limit, currentService.token_reset_interval, currentService.token_reset_base_time, now, serviceId);
|
|
576
|
-
console.log(`[DB] Synced token limits for ${ruleIds.length} rule(s) using service ${serviceId}`);
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
// 请求次数超量限制同步
|
|
580
|
-
if (service.enableRequestLimit !== undefined || service.requestCountLimit !== undefined ||
|
|
581
|
-
service.requestResetInterval !== undefined || service.requestResetBaseTime !== undefined) {
|
|
582
|
-
// 获取当前服务的最新配置
|
|
583
|
-
const currentService = this.db.prepare('SELECT enable_request_limit, request_count_limit, request_reset_interval, request_reset_base_time FROM api_services WHERE id = ?').get(serviceId);
|
|
584
|
-
if (currentService && currentService.enable_request_limit === 1) {
|
|
585
|
-
// 启用了请求次数超量限制,同步到所有规则
|
|
586
|
-
this.db.prepare('UPDATE rules SET request_count_limit = ?, request_reset_interval = ?, request_reset_base_time = ?, updated_at = ? WHERE target_service_id = ?').run(currentService.request_count_limit, currentService.request_reset_interval, currentService.request_reset_base_time, now, serviceId);
|
|
587
|
-
console.log(`[DB] Synced request count limits for ${ruleIds.length} rule(s) using service ${serviceId}`);
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
deleteAPIService(id) {
|
|
592
|
-
const result = this.db.prepare('DELETE FROM api_services WHERE id = ?').run(id);
|
|
593
|
-
return result.changes > 0;
|
|
594
|
-
}
|
|
595
|
-
// Route operations
|
|
596
|
-
getRoutes() {
|
|
597
|
-
const rows = this.db.prepare('SELECT * FROM routes ORDER BY created_at DESC').all();
|
|
598
|
-
return rows.map((row) => ({
|
|
599
|
-
id: row.id,
|
|
600
|
-
name: row.name,
|
|
601
|
-
description: row.description,
|
|
602
|
-
targetType: row.target_type,
|
|
603
|
-
isActive: row.is_active === 1,
|
|
604
|
-
createdAt: row.created_at,
|
|
605
|
-
updatedAt: row.updated_at,
|
|
606
|
-
}));
|
|
607
|
-
}
|
|
608
|
-
createRoute(route) {
|
|
609
|
-
const id = crypto_1.default.randomUUID();
|
|
610
|
-
const now = Date.now();
|
|
611
|
-
this.db
|
|
612
|
-
.prepare('INSERT INTO routes (id, name, description, target_type, is_active, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)')
|
|
613
|
-
.run(id, route.name, route.description || null, route.targetType, route.isActive ? 1 : 0, now, now);
|
|
614
|
-
return Object.assign(Object.assign({}, route), { id, createdAt: now, updatedAt: now });
|
|
615
|
-
}
|
|
616
|
-
updateRoute(id, route) {
|
|
617
|
-
const now = Date.now();
|
|
618
|
-
const result = this.db
|
|
619
|
-
.prepare('UPDATE routes SET name = ?, description = ?, target_type = ?, updated_at = ? WHERE id = ?')
|
|
620
|
-
.run(route.name, route.description || null, route.targetType, now, id);
|
|
621
|
-
return result.changes > 0;
|
|
622
|
-
}
|
|
623
|
-
deleteRoute(id) {
|
|
624
|
-
const result = this.db.prepare('DELETE FROM routes WHERE id = ?').run(id);
|
|
625
|
-
return result.changes > 0;
|
|
626
|
-
}
|
|
627
|
-
activateRoute(id) {
|
|
628
|
-
const route = this.getRoutes().find(r => r.id === id);
|
|
629
|
-
if (route) {
|
|
630
|
-
this.db.prepare('UPDATE routes SET is_active = 0 WHERE target_type = ?').run(route.targetType);
|
|
631
|
-
const result = this.db.prepare('UPDATE routes SET is_active = 1 WHERE id = ?').run(id);
|
|
632
|
-
return result.changes > 0;
|
|
633
|
-
}
|
|
634
|
-
return false;
|
|
635
|
-
}
|
|
636
|
-
deactivateRoute(id) {
|
|
637
|
-
const result = this.db.prepare('UPDATE routes SET is_active = 0 WHERE id = ?').run(id);
|
|
638
|
-
return result.changes > 0;
|
|
639
|
-
}
|
|
640
|
-
deactivateAllRoutes() {
|
|
641
|
-
const result = this.db.prepare('UPDATE routes SET is_active = 0 WHERE is_active = 1').run();
|
|
642
|
-
return result.changes;
|
|
643
|
-
}
|
|
644
|
-
// Rule operations
|
|
645
|
-
getRules(routeId) {
|
|
646
|
-
const query = routeId
|
|
647
|
-
? 'SELECT * FROM rules WHERE route_id = ? ORDER BY sort_order DESC, created_at DESC'
|
|
648
|
-
: 'SELECT * FROM rules ORDER BY sort_order DESC, created_at DESC';
|
|
649
|
-
const stmt = routeId ? this.db.prepare(query).bind(routeId) : this.db.prepare(query);
|
|
650
|
-
const rows = stmt.all();
|
|
651
|
-
return rows.map((row) => ({
|
|
652
|
-
id: row.id,
|
|
653
|
-
routeId: row.route_id,
|
|
654
|
-
contentType: row.content_type,
|
|
655
|
-
targetServiceId: row.target_service_id,
|
|
656
|
-
targetModel: row.target_model,
|
|
657
|
-
replacedModel: row.replaced_model,
|
|
658
|
-
sortOrder: row.sort_order,
|
|
659
|
-
timeout: row.timeout,
|
|
660
|
-
tokenLimit: row.token_limit,
|
|
661
|
-
totalTokensUsed: row.total_tokens_used,
|
|
662
|
-
resetInterval: row.reset_interval,
|
|
663
|
-
lastResetAt: row.last_reset_at,
|
|
664
|
-
tokenResetBaseTime: row.token_reset_base_time,
|
|
665
|
-
requestCountLimit: row.request_count_limit,
|
|
666
|
-
totalRequestsUsed: row.total_requests_used,
|
|
667
|
-
requestResetInterval: row.request_reset_interval,
|
|
668
|
-
requestLastResetAt: row.request_last_reset_at,
|
|
669
|
-
requestResetBaseTime: row.request_reset_base_time,
|
|
670
|
-
isDisabled: row.is_disabled === 1,
|
|
671
|
-
createdAt: row.created_at,
|
|
672
|
-
updatedAt: row.updated_at,
|
|
673
|
-
}));
|
|
674
|
-
}
|
|
675
|
-
getRule(id) {
|
|
676
|
-
const row = this.db.prepare('SELECT * FROM rules WHERE id = ?').get(id);
|
|
677
|
-
if (!row)
|
|
678
|
-
return undefined;
|
|
679
|
-
return {
|
|
680
|
-
id: row.id,
|
|
681
|
-
routeId: row.route_id,
|
|
682
|
-
contentType: row.content_type,
|
|
683
|
-
targetServiceId: row.target_service_id,
|
|
684
|
-
targetModel: row.target_model,
|
|
685
|
-
replacedModel: row.replaced_model,
|
|
686
|
-
sortOrder: row.sort_order,
|
|
687
|
-
timeout: row.timeout,
|
|
688
|
-
tokenLimit: row.token_limit,
|
|
689
|
-
totalTokensUsed: row.total_tokens_used,
|
|
690
|
-
resetInterval: row.reset_interval,
|
|
691
|
-
lastResetAt: row.last_reset_at,
|
|
692
|
-
tokenResetBaseTime: row.token_reset_base_time,
|
|
693
|
-
requestCountLimit: row.request_count_limit,
|
|
694
|
-
totalRequestsUsed: row.total_requests_used,
|
|
695
|
-
requestResetInterval: row.request_reset_interval,
|
|
696
|
-
requestLastResetAt: row.request_last_reset_at,
|
|
697
|
-
requestResetBaseTime: row.request_reset_base_time,
|
|
698
|
-
isDisabled: row.is_disabled === 1,
|
|
699
|
-
createdAt: row.created_at,
|
|
700
|
-
updatedAt: row.updated_at,
|
|
701
|
-
};
|
|
702
|
-
}
|
|
703
|
-
createRule(route) {
|
|
704
|
-
const id = crypto_1.default.randomUUID();
|
|
705
|
-
const now = Date.now();
|
|
706
|
-
this.db
|
|
707
|
-
.prepare('INSERT INTO rules (id, route_id, content_type, target_service_id, target_model, replaced_model, sort_order, timeout, token_limit, total_tokens_used, reset_interval, last_reset_at, token_reset_base_time, request_count_limit, total_requests_used, request_reset_interval, request_last_reset_at, request_reset_base_time, is_disabled, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)')
|
|
708
|
-
.run(id, route.routeId, route.contentType, route.targetServiceId, route.targetModel || null, route.replacedModel || null, route.sortOrder || 0, route.timeout || null, route.tokenLimit || null, route.totalTokensUsed || 0, route.resetInterval || null, route.lastResetAt || null, route.tokenResetBaseTime || null, route.requestCountLimit || null, route.totalRequestsUsed || 0, route.requestResetInterval || null, route.requestLastResetAt || null, route.requestResetBaseTime || null, route.isDisabled ? 1 : 0, now, now);
|
|
709
|
-
return Object.assign(Object.assign({}, route), { id, createdAt: now, updatedAt: now });
|
|
710
|
-
}
|
|
711
|
-
updateRule(id, route) {
|
|
712
|
-
const now = Date.now();
|
|
713
|
-
const result = this.db
|
|
714
|
-
.prepare('UPDATE rules SET content_type = ?, target_service_id = ?, target_model = ?, replaced_model = ?, sort_order = ?, timeout = ?, token_limit = ?, reset_interval = ?, token_reset_base_time = ?, request_count_limit = ?, request_reset_interval = ?, request_reset_base_time = ?, is_disabled = ?, updated_at = ? WHERE id = ?')
|
|
715
|
-
.run(route.contentType, route.targetServiceId, route.targetModel || null, route.replacedModel || null, route.sortOrder || 0, route.timeout !== undefined ? route.timeout : null, route.tokenLimit !== undefined ? route.tokenLimit : null, route.resetInterval !== undefined ? route.resetInterval : null, route.tokenResetBaseTime !== undefined ? route.tokenResetBaseTime : null, route.requestCountLimit !== undefined ? route.requestCountLimit : null, route.requestResetInterval !== undefined ? route.requestResetInterval : null, route.requestResetBaseTime !== undefined ? route.requestResetBaseTime : null, route.isDisabled !== undefined ? (route.isDisabled ? 1 : 0) : null, now, id);
|
|
716
|
-
return result.changes > 0;
|
|
717
|
-
}
|
|
718
|
-
deleteRule(id) {
|
|
719
|
-
const result = this.db.prepare('DELETE FROM rules WHERE id = ?').run(id);
|
|
720
|
-
return result.changes > 0;
|
|
721
|
-
}
|
|
722
|
-
/**
|
|
723
|
-
* 切换规则的临时屏蔽状态
|
|
724
|
-
* @param ruleId 规则ID
|
|
725
|
-
* @returns 是否成功
|
|
726
|
-
*/
|
|
727
|
-
toggleRuleDisabled(ruleId) {
|
|
728
|
-
const rule = this.getRule(ruleId);
|
|
729
|
-
if (!rule) {
|
|
730
|
-
return { success: false, isDisabled: false };
|
|
731
|
-
}
|
|
732
|
-
const now = Date.now();
|
|
733
|
-
const newDisabledState = !rule.isDisabled;
|
|
734
|
-
const result = this.db
|
|
735
|
-
.prepare('UPDATE rules SET is_disabled = ?, updated_at = ? WHERE id = ?')
|
|
736
|
-
.run(newDisabledState ? 1 : 0, now, ruleId);
|
|
737
|
-
return {
|
|
738
|
-
success: result.changes > 0,
|
|
739
|
-
isDisabled: newDisabledState
|
|
740
|
-
};
|
|
741
|
-
}
|
|
742
|
-
/**
|
|
743
|
-
* 增加规则的token使用量
|
|
744
|
-
* @param ruleId 规则ID
|
|
745
|
-
* @param tokensUsed 使用的token数量
|
|
746
|
-
* @returns 是否成功
|
|
747
|
-
*/
|
|
748
|
-
incrementRuleTokenUsage(ruleId, tokensUsed) {
|
|
749
|
-
const result = this.db
|
|
750
|
-
.prepare('UPDATE rules SET total_tokens_used = total_tokens_used + ? WHERE id = ?')
|
|
751
|
-
.run(tokensUsed, ruleId);
|
|
752
|
-
return result.changes > 0;
|
|
753
|
-
}
|
|
754
|
-
/**
|
|
755
|
-
* 重置规则的token使用量
|
|
756
|
-
* @param ruleId 规则ID
|
|
757
|
-
* @returns 是否成功
|
|
758
|
-
*/
|
|
759
|
-
resetRuleTokenUsage(ruleId) {
|
|
760
|
-
const now = Date.now();
|
|
761
|
-
const result = this.db
|
|
762
|
-
.prepare('UPDATE rules SET total_tokens_used = 0, last_reset_at = ? WHERE id = ?')
|
|
763
|
-
.run(now, ruleId);
|
|
764
|
-
return result.changes > 0;
|
|
765
|
-
}
|
|
766
|
-
/**
|
|
767
|
-
* 检查并重置到期的规则
|
|
768
|
-
* 如果规则设置了reset_interval且已经到了重置时间,则自动重置token使用量
|
|
769
|
-
* @param ruleId 规则ID
|
|
770
|
-
* @returns 是否进行了重置
|
|
771
|
-
*/
|
|
772
|
-
checkAndResetRuleIfNeeded(ruleId) {
|
|
773
|
-
const rule = this.db
|
|
774
|
-
.prepare('SELECT reset_interval, last_reset_at, token_reset_base_time FROM rules WHERE id = ?')
|
|
775
|
-
.get(ruleId);
|
|
776
|
-
if (!rule || !rule.reset_interval) {
|
|
777
|
-
return false; // 没有设置重置间隔
|
|
778
|
-
}
|
|
779
|
-
const now = Date.now();
|
|
780
|
-
const resetIntervalMs = rule.reset_interval * 60 * 60 * 1000; // 小时转毫秒
|
|
781
|
-
const baseTime = rule.token_reset_base_time;
|
|
782
|
-
const lastResetAt = rule.last_reset_at || 0;
|
|
783
|
-
// 场景1: 设置了时间基点
|
|
784
|
-
if (baseTime) {
|
|
785
|
-
if (now >= baseTime) {
|
|
786
|
-
this.resetRuleTokenUsageWithBaseTime(ruleId, baseTime);
|
|
787
|
-
return true;
|
|
788
|
-
}
|
|
789
|
-
return false;
|
|
790
|
-
}
|
|
791
|
-
// 场景2: 未设置时间基点,使用原始逻辑(向后兼容)
|
|
792
|
-
if (now - lastResetAt >= resetIntervalMs) {
|
|
793
|
-
this.resetRuleTokenUsage(ruleId);
|
|
794
|
-
return true;
|
|
795
|
-
}
|
|
796
|
-
return false;
|
|
797
|
-
}
|
|
798
|
-
/**
|
|
799
|
-
* 重置规则的Token使用量(带时间基点更新)
|
|
800
|
-
*/
|
|
801
|
-
resetRuleTokenUsageWithBaseTime(ruleId, currentBaseTime) {
|
|
802
|
-
const now = Date.now();
|
|
803
|
-
const rule = this.db
|
|
804
|
-
.prepare('SELECT reset_interval FROM rules WHERE id = ?')
|
|
805
|
-
.get(ruleId);
|
|
806
|
-
if (!rule || !rule.reset_interval) {
|
|
807
|
-
return false;
|
|
808
|
-
}
|
|
809
|
-
const resetIntervalMs = rule.reset_interval * 60 * 60 * 1000;
|
|
810
|
-
// 计算下一个时间基点
|
|
811
|
-
let nextBaseTime = currentBaseTime;
|
|
812
|
-
while (nextBaseTime <= now) {
|
|
813
|
-
nextBaseTime += resetIntervalMs;
|
|
814
|
-
}
|
|
815
|
-
const result = this.db
|
|
816
|
-
.prepare('UPDATE rules SET total_tokens_used = 0, last_reset_at = ?, token_reset_base_time = ? WHERE id = ?')
|
|
817
|
-
.run(now, nextBaseTime, ruleId);
|
|
818
|
-
return result.changes > 0;
|
|
819
|
-
}
|
|
820
|
-
/**
|
|
821
|
-
* 增加规则的请求次数
|
|
822
|
-
* @param ruleId 规则ID
|
|
823
|
-
* @param count 增加的次数
|
|
824
|
-
* @returns 是否成功
|
|
825
|
-
*/
|
|
826
|
-
incrementRuleRequestCount(ruleId, count) {
|
|
827
|
-
const result = this.db
|
|
828
|
-
.prepare('UPDATE rules SET total_requests_used = total_requests_used + ? WHERE id = ?')
|
|
829
|
-
.run(count, ruleId);
|
|
830
|
-
return result.changes > 0;
|
|
831
|
-
}
|
|
832
|
-
/**
|
|
833
|
-
* 重置规则的请求次数
|
|
834
|
-
* @param ruleId 规则ID
|
|
835
|
-
* @returns 是否成功
|
|
836
|
-
*/
|
|
837
|
-
resetRuleRequestCount(ruleId) {
|
|
838
|
-
const now = Date.now();
|
|
839
|
-
const result = this.db
|
|
840
|
-
.prepare('UPDATE rules SET total_requests_used = 0, request_last_reset_at = ? WHERE id = ?')
|
|
841
|
-
.run(now, ruleId);
|
|
842
|
-
return result.changes > 0;
|
|
843
|
-
}
|
|
844
|
-
/**
|
|
845
|
-
* 检查并重置到期的规则(请求次数)
|
|
846
|
-
* 如果规则设置了request_reset_interval且已经到了重置时间,则自动重置请求次数
|
|
847
|
-
* @param ruleId 规则ID
|
|
848
|
-
* @returns 是否进行了重置
|
|
849
|
-
*/
|
|
850
|
-
checkAndResetRequestCountIfNeeded(ruleId) {
|
|
851
|
-
const rule = this.db
|
|
852
|
-
.prepare('SELECT request_reset_interval, request_last_reset_at, request_reset_base_time FROM rules WHERE id = ?')
|
|
853
|
-
.get(ruleId);
|
|
854
|
-
if (!rule || !rule.request_reset_interval) {
|
|
855
|
-
return false; // 没有设置重置间隔
|
|
856
|
-
}
|
|
857
|
-
const now = Date.now();
|
|
858
|
-
const resetIntervalMs = rule.request_reset_interval * 60 * 60 * 1000; // 小时转毫秒
|
|
859
|
-
const baseTime = rule.request_reset_base_time;
|
|
860
|
-
const lastResetAt = rule.request_last_reset_at || 0;
|
|
861
|
-
// 场景1: 设置了时间基点
|
|
862
|
-
if (baseTime) {
|
|
863
|
-
if (now >= baseTime) {
|
|
864
|
-
this.resetRuleRequestCountWithBaseTime(ruleId, baseTime);
|
|
865
|
-
return true;
|
|
866
|
-
}
|
|
867
|
-
return false;
|
|
868
|
-
}
|
|
869
|
-
// 场景2: 未设置时间基点,使用原始逻辑(向后兼容)
|
|
870
|
-
if (now - lastResetAt >= resetIntervalMs) {
|
|
871
|
-
this.resetRuleRequestCount(ruleId);
|
|
872
|
-
return true;
|
|
873
|
-
}
|
|
874
|
-
return false;
|
|
875
|
-
}
|
|
876
|
-
/**
|
|
877
|
-
* 重置规则的请求次数(带时间基点更新)
|
|
878
|
-
*/
|
|
879
|
-
resetRuleRequestCountWithBaseTime(ruleId, currentBaseTime) {
|
|
880
|
-
const now = Date.now();
|
|
881
|
-
const rule = this.db
|
|
882
|
-
.prepare('SELECT request_reset_interval FROM rules WHERE id = ?')
|
|
883
|
-
.get(ruleId);
|
|
884
|
-
if (!rule || !rule.request_reset_interval) {
|
|
885
|
-
return false;
|
|
886
|
-
}
|
|
887
|
-
const resetIntervalMs = rule.request_reset_interval * 60 * 60 * 1000;
|
|
888
|
-
// 计算下一个时间基点
|
|
889
|
-
let nextBaseTime = currentBaseTime;
|
|
890
|
-
while (nextBaseTime <= now) {
|
|
891
|
-
nextBaseTime += resetIntervalMs;
|
|
892
|
-
}
|
|
893
|
-
const result = this.db
|
|
894
|
-
.prepare('UPDATE rules SET total_requests_used = 0, request_last_reset_at = ?, request_reset_base_time = ? WHERE id = ?')
|
|
895
|
-
.run(now, nextBaseTime, ruleId);
|
|
896
|
-
return result.changes > 0;
|
|
897
|
-
}
|
|
898
|
-
// Log operations
|
|
899
|
-
addLog(log) {
|
|
900
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
901
|
-
const id = crypto_1.default.randomUUID();
|
|
902
|
-
yield this.logDb.put(id, JSON.stringify(Object.assign(Object.assign({}, log), { id })));
|
|
903
|
-
// 清除缓存
|
|
904
|
-
this.logsCountCache = null;
|
|
905
|
-
});
|
|
906
|
-
}
|
|
907
|
-
getLogs() {
|
|
908
|
-
return __awaiter(this, arguments, void 0, function* (limit = 100, offset = 0) {
|
|
909
|
-
var _a, e_1, _b, _c;
|
|
910
|
-
const allLogs = [];
|
|
911
|
-
try {
|
|
912
|
-
for (var _d = true, _e = __asyncValues(this.logDb.iterator()), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
|
|
913
|
-
_c = _f.value;
|
|
914
|
-
_d = false;
|
|
915
|
-
const [, value] = _c;
|
|
916
|
-
allLogs.push(JSON.parse(value));
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
920
|
-
finally {
|
|
921
|
-
try {
|
|
922
|
-
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
|
|
923
|
-
}
|
|
924
|
-
finally { if (e_1) throw e_1.error; }
|
|
925
|
-
}
|
|
926
|
-
// Sort by timestamp in descending order (newest first)
|
|
927
|
-
allLogs.sort((a, b) => b.timestamp - a.timestamp);
|
|
928
|
-
// Apply offset and limit
|
|
929
|
-
return allLogs.slice(offset, offset + limit);
|
|
930
|
-
});
|
|
931
|
-
}
|
|
932
|
-
clearLogs() {
|
|
933
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
934
|
-
yield this.logDb.clear();
|
|
935
|
-
// 清除缓存
|
|
936
|
-
this.logsCountCache = null;
|
|
937
|
-
});
|
|
938
|
-
}
|
|
939
|
-
// Error log operations
|
|
940
|
-
addErrorLog(log) {
|
|
941
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
942
|
-
const id = crypto_1.default.randomUUID();
|
|
943
|
-
yield this.errorLogDb.put(id, JSON.stringify(Object.assign(Object.assign({}, log), { id })));
|
|
944
|
-
// 清除缓存
|
|
945
|
-
this.errorLogsCountCache = null;
|
|
946
|
-
});
|
|
947
|
-
}
|
|
948
|
-
getErrorLogs() {
|
|
949
|
-
return __awaiter(this, arguments, void 0, function* (limit = 100, offset = 0) {
|
|
950
|
-
var _a, e_2, _b, _c;
|
|
951
|
-
const allLogs = [];
|
|
952
|
-
try {
|
|
953
|
-
for (var _d = true, _e = __asyncValues(this.errorLogDb.iterator()), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
|
|
954
|
-
_c = _f.value;
|
|
955
|
-
_d = false;
|
|
956
|
-
const [, value] = _c;
|
|
957
|
-
allLogs.push(JSON.parse(value));
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
catch (e_2_1) { e_2 = { error: e_2_1 }; }
|
|
961
|
-
finally {
|
|
962
|
-
try {
|
|
963
|
-
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
|
|
964
|
-
}
|
|
965
|
-
finally { if (e_2) throw e_2.error; }
|
|
966
|
-
}
|
|
967
|
-
// Sort by timestamp in descending order (newest first)
|
|
968
|
-
allLogs.sort((a, b) => b.timestamp - a.timestamp);
|
|
969
|
-
// Apply offset and limit
|
|
970
|
-
return allLogs.slice(offset, offset + limit);
|
|
971
|
-
});
|
|
972
|
-
}
|
|
973
|
-
clearErrorLogs() {
|
|
974
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
975
|
-
yield this.errorLogDb.clear();
|
|
976
|
-
// 清除缓存
|
|
977
|
-
this.errorLogsCountCache = null;
|
|
978
|
-
});
|
|
979
|
-
}
|
|
980
|
-
/**
|
|
981
|
-
* 获取请求日志总数(带缓存)
|
|
982
|
-
*/
|
|
983
|
-
getLogsCount() {
|
|
984
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
985
|
-
var _a, e_3, _b, _c;
|
|
986
|
-
const now = Date.now();
|
|
987
|
-
if (this.logsCountCache && now - this.logsCountCache.timestamp < this.CACHE_TTL) {
|
|
988
|
-
return this.logsCountCache.count;
|
|
989
|
-
}
|
|
990
|
-
let count = 0;
|
|
991
|
-
try {
|
|
992
|
-
for (var _d = true, _e = __asyncValues(this.logDb.iterator()), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
|
|
993
|
-
_c = _f.value;
|
|
994
|
-
_d = false;
|
|
995
|
-
const _ = _c;
|
|
996
|
-
count++;
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
catch (e_3_1) { e_3 = { error: e_3_1 }; }
|
|
1000
|
-
finally {
|
|
1001
|
-
try {
|
|
1002
|
-
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
|
|
1003
|
-
}
|
|
1004
|
-
finally { if (e_3) throw e_3.error; }
|
|
1005
|
-
}
|
|
1006
|
-
this.logsCountCache = { count, timestamp: now };
|
|
1007
|
-
return count;
|
|
1008
|
-
});
|
|
1009
|
-
}
|
|
1010
|
-
/**
|
|
1011
|
-
* 获取错误日志总数(带缓存)
|
|
1012
|
-
*/
|
|
1013
|
-
getErrorLogsCount() {
|
|
1014
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
1015
|
-
var _a, e_4, _b, _c;
|
|
1016
|
-
const now = Date.now();
|
|
1017
|
-
if (this.errorLogsCountCache && now - this.errorLogsCountCache.timestamp < this.CACHE_TTL) {
|
|
1018
|
-
return this.errorLogsCountCache.count;
|
|
1019
|
-
}
|
|
1020
|
-
let count = 0;
|
|
1021
|
-
try {
|
|
1022
|
-
for (var _d = true, _e = __asyncValues(this.errorLogDb.iterator()), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
|
|
1023
|
-
_c = _f.value;
|
|
1024
|
-
_d = false;
|
|
1025
|
-
const _ = _c;
|
|
1026
|
-
count++;
|
|
1027
|
-
}
|
|
1028
|
-
}
|
|
1029
|
-
catch (e_4_1) { e_4 = { error: e_4_1 }; }
|
|
1030
|
-
finally {
|
|
1031
|
-
try {
|
|
1032
|
-
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
|
|
1033
|
-
}
|
|
1034
|
-
finally { if (e_4) throw e_4.error; }
|
|
1035
|
-
}
|
|
1036
|
-
this.errorLogsCountCache = { count, timestamp: now };
|
|
1037
|
-
return count;
|
|
1038
|
-
});
|
|
1039
|
-
}
|
|
1040
|
-
// Service blacklist operations
|
|
1041
|
-
isServiceBlacklisted(serviceId, routeId, contentType) {
|
|
1042
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
1043
|
-
const key = `${routeId}:${contentType}:${serviceId}`;
|
|
1044
|
-
try {
|
|
1045
|
-
const value = yield this.blacklistDb.get(key);
|
|
1046
|
-
const entry = JSON.parse(value);
|
|
1047
|
-
// 检查是否过期
|
|
1048
|
-
if (Date.now() > entry.expiresAt) {
|
|
1049
|
-
// 已过期,删除记录
|
|
1050
|
-
yield this.blacklistDb.del(key);
|
|
1051
|
-
return false;
|
|
1052
|
-
}
|
|
1053
|
-
return true;
|
|
1054
|
-
}
|
|
1055
|
-
catch (error) {
|
|
1056
|
-
if (error.code === 'LEVEL_NOT_FOUND') {
|
|
1057
|
-
return false;
|
|
1058
|
-
}
|
|
1059
|
-
throw error;
|
|
1060
|
-
}
|
|
1061
|
-
});
|
|
1062
|
-
}
|
|
1063
|
-
addToBlacklist(serviceId, routeId, contentType, errorMessage, statusCode, errorType) {
|
|
1064
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
1065
|
-
const key = `${routeId}:${contentType}:${serviceId}`;
|
|
1066
|
-
const now = Date.now();
|
|
1067
|
-
try {
|
|
1068
|
-
// 尝试读取现有记录
|
|
1069
|
-
const existing = yield this.blacklistDb.get(key);
|
|
1070
|
-
const entry = JSON.parse(existing);
|
|
1071
|
-
// 更新现有记录
|
|
1072
|
-
entry.blacklistedAt = now;
|
|
1073
|
-
entry.expiresAt = now + 10 * 60 * 1000; // 10分钟
|
|
1074
|
-
entry.errorCount++;
|
|
1075
|
-
entry.lastError = errorMessage;
|
|
1076
|
-
entry.lastStatusCode = statusCode;
|
|
1077
|
-
entry.errorType = errorType;
|
|
1078
|
-
yield this.blacklistDb.put(key, JSON.stringify(entry));
|
|
1079
|
-
}
|
|
1080
|
-
catch (error) {
|
|
1081
|
-
if (error.code === 'LEVEL_NOT_FOUND') {
|
|
1082
|
-
// 创建新记录
|
|
1083
|
-
const entry = {
|
|
1084
|
-
serviceId,
|
|
1085
|
-
routeId,
|
|
1086
|
-
contentType,
|
|
1087
|
-
blacklistedAt: now,
|
|
1088
|
-
expiresAt: now + 10 * 60 * 1000,
|
|
1089
|
-
errorCount: 1,
|
|
1090
|
-
lastError: errorMessage,
|
|
1091
|
-
lastStatusCode: statusCode,
|
|
1092
|
-
errorType,
|
|
1093
|
-
};
|
|
1094
|
-
yield this.blacklistDb.put(key, JSON.stringify(entry));
|
|
1095
|
-
}
|
|
1096
|
-
else {
|
|
1097
|
-
throw error;
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
1100
|
-
});
|
|
1101
|
-
}
|
|
1102
|
-
removeFromBlacklist(serviceId, routeId, contentType) {
|
|
1103
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
1104
|
-
const key = `${routeId}:${contentType}:${serviceId}`;
|
|
1105
|
-
yield this.blacklistDb.del(key);
|
|
1106
|
-
});
|
|
1107
|
-
}
|
|
1108
|
-
cleanupExpiredBlacklist() {
|
|
1109
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
1110
|
-
var _a, e_5, _b, _c;
|
|
1111
|
-
const now = Date.now();
|
|
1112
|
-
let count = 0;
|
|
1113
|
-
try {
|
|
1114
|
-
for (var _d = true, _e = __asyncValues(this.blacklistDb.iterator()), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
|
|
1115
|
-
_c = _f.value;
|
|
1116
|
-
_d = false;
|
|
1117
|
-
const [key, value] = _c;
|
|
1118
|
-
const entry = JSON.parse(value);
|
|
1119
|
-
if (now > entry.expiresAt) {
|
|
1120
|
-
yield this.blacklistDb.del(key);
|
|
1121
|
-
count++;
|
|
1122
|
-
}
|
|
1123
|
-
}
|
|
1124
|
-
}
|
|
1125
|
-
catch (e_5_1) { e_5 = { error: e_5_1 }; }
|
|
1126
|
-
finally {
|
|
1127
|
-
try {
|
|
1128
|
-
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
|
|
1129
|
-
}
|
|
1130
|
-
finally { if (e_5) throw e_5.error; }
|
|
1131
|
-
}
|
|
1132
|
-
return count;
|
|
1133
|
-
});
|
|
1134
|
-
}
|
|
1135
|
-
// Config operations
|
|
1136
|
-
getConfig() {
|
|
1137
|
-
const row = this.db.prepare('SELECT value FROM config WHERE key = ?').get('app_config');
|
|
1138
|
-
return row ? JSON.parse(row.value) : null;
|
|
1139
|
-
}
|
|
1140
|
-
updateConfig(config) {
|
|
1141
|
-
const result = this.db
|
|
1142
|
-
.prepare('UPDATE config SET value = ? WHERE key = ?')
|
|
1143
|
-
.run(JSON.stringify(config), 'app_config');
|
|
1144
|
-
return result.changes > 0;
|
|
1145
|
-
}
|
|
1146
|
-
// Export/Import operations
|
|
1147
|
-
exportData(password) {
|
|
1148
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
1149
|
-
const exportData = {
|
|
1150
|
-
version: '1.0.0',
|
|
1151
|
-
exportDate: Date.now(),
|
|
1152
|
-
vendors: this.getVendors(),
|
|
1153
|
-
apiServices: this.getAPIServices(),
|
|
1154
|
-
routes: this.getRoutes(),
|
|
1155
|
-
rules: this.getRules(),
|
|
1156
|
-
config: this.getConfig(),
|
|
1157
|
-
};
|
|
1158
|
-
const jsonData = JSON.stringify(exportData);
|
|
1159
|
-
const encrypted = crypto_js_1.default.AES.encrypt(jsonData, password).toString();
|
|
1160
|
-
return encrypted;
|
|
1161
|
-
});
|
|
1162
|
-
}
|
|
1163
|
-
importData(encryptedData, password) {
|
|
1164
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
1165
|
-
try {
|
|
1166
|
-
const decrypted = crypto_js_1.default.AES.decrypt(encryptedData, password);
|
|
1167
|
-
const jsonData = decrypted.toString(crypto_js_1.default.enc.Utf8);
|
|
1168
|
-
const importData = JSON.parse(jsonData);
|
|
1169
|
-
// Clear existing data
|
|
1170
|
-
this.db.prepare('DELETE FROM rules').run();
|
|
1171
|
-
this.db.prepare('DELETE FROM routes').run();
|
|
1172
|
-
this.db.prepare('DELETE FROM api_services').run();
|
|
1173
|
-
this.db.prepare('DELETE FROM vendors').run();
|
|
1174
|
-
// Import vendors
|
|
1175
|
-
for (const vendor of importData.vendors) {
|
|
1176
|
-
this.db
|
|
1177
|
-
.prepare('INSERT INTO vendors (id, name, description, sort_order, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)')
|
|
1178
|
-
.run(vendor.id, vendor.name, vendor.description || null, vendor.sortOrder || 0, vendor.createdAt, vendor.updatedAt);
|
|
1179
|
-
}
|
|
1180
|
-
// Import API services
|
|
1181
|
-
for (const service of importData.apiServices) {
|
|
1182
|
-
this.db
|
|
1183
|
-
.prepare('INSERT INTO api_services (id, vendor_id, name, api_url, api_key, source_type, auth_type, supported_models, model_limits, enable_proxy, enable_token_limit, token_limit, token_reset_interval, token_reset_base_time, enable_request_limit, request_count_limit, request_reset_interval, request_reset_base_time, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)')
|
|
1184
|
-
.run(service.id, service.vendorId, service.name, service.apiUrl, service.apiKey, service.sourceType || null, service.authType || null, service.supportedModels ? service.supportedModels.join(',') : null, service.modelLimits ? JSON.stringify(service.modelLimits) : null, service.enableProxy ? 1 : 0, service.enableTokenLimit ? 1 : 0, service.tokenLimit || null, service.tokenResetInterval || null, service.tokenResetBaseTime || null, service.enableRequestLimit ? 1 : 0, service.requestCountLimit || null, service.requestResetInterval || null, service.requestResetBaseTime || null, service.createdAt, service.updatedAt);
|
|
1185
|
-
}
|
|
1186
|
-
// Import routes
|
|
1187
|
-
for (const route of importData.routes) {
|
|
1188
|
-
this.db
|
|
1189
|
-
.prepare('INSERT INTO routes (id, name, description, target_type, is_active, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)')
|
|
1190
|
-
.run(route.id, route.name, route.description || null, route.targetType, route.isActive ? 1 : 0, route.createdAt, route.updatedAt);
|
|
1191
|
-
}
|
|
1192
|
-
// Import rules
|
|
1193
|
-
for (const rule of importData.rules) {
|
|
1194
|
-
this.db
|
|
1195
|
-
.prepare('INSERT INTO rules (id, route_id, content_type, target_service_id, target_model, replaced_model, sort_order, timeout, token_limit, total_tokens_used, reset_interval, last_reset_at, token_reset_base_time, request_count_limit, total_requests_used, request_reset_interval, request_last_reset_at, request_reset_base_time, is_disabled, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)')
|
|
1196
|
-
.run(rule.id, rule.routeId, rule.contentType || 'default', rule.targetServiceId, rule.targetModel || null, rule.replacedModel || null, rule.sortOrder || 0, rule.timeout || null, rule.tokenLimit || null, rule.totalTokensUsed || 0, rule.resetInterval || null, rule.lastResetAt || null, rule.tokenResetBaseTime || null, rule.requestCountLimit || null, rule.totalRequestsUsed || 0, rule.requestResetInterval || null, rule.requestLastResetAt || null, rule.requestResetBaseTime || null, rule.isDisabled ? 1 : 0, rule.createdAt, rule.updatedAt);
|
|
1197
|
-
}
|
|
1198
|
-
// Update config
|
|
1199
|
-
this.updateConfig(importData.config);
|
|
1200
|
-
return true;
|
|
1201
|
-
}
|
|
1202
|
-
catch (error) {
|
|
1203
|
-
console.error('Import error:', error);
|
|
1204
|
-
return false;
|
|
1205
|
-
}
|
|
1206
|
-
});
|
|
1207
|
-
}
|
|
1208
|
-
// Statistics operations
|
|
1209
|
-
getStatistics() {
|
|
1210
|
-
return __awaiter(this, arguments, void 0, function* (days = 30) {
|
|
1211
|
-
var _a, e_6, _b, _c, _d, e_7, _e, _f;
|
|
1212
|
-
var _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y;
|
|
1213
|
-
const now = Date.now();
|
|
1214
|
-
const startTime = now - days * 24 * 60 * 60 * 1000;
|
|
1215
|
-
// Get all logs within the time period
|
|
1216
|
-
const allLogs = [];
|
|
1217
|
-
try {
|
|
1218
|
-
for (var _z = true, _0 = __asyncValues(this.logDb.iterator()), _1; _1 = yield _0.next(), _a = _1.done, !_a; _z = true) {
|
|
1219
|
-
_c = _1.value;
|
|
1220
|
-
_z = false;
|
|
1221
|
-
const [, value] = _c;
|
|
1222
|
-
const log = JSON.parse(value);
|
|
1223
|
-
if (log.timestamp >= startTime) {
|
|
1224
|
-
allLogs.push(log);
|
|
1225
|
-
}
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1228
|
-
catch (e_6_1) { e_6 = { error: e_6_1 }; }
|
|
1229
|
-
finally {
|
|
1230
|
-
try {
|
|
1231
|
-
if (!_z && !_a && (_b = _0.return)) yield _b.call(_0);
|
|
1232
|
-
}
|
|
1233
|
-
finally { if (e_6) throw e_6.error; }
|
|
1234
|
-
}
|
|
1235
|
-
// Get all error logs
|
|
1236
|
-
const errorLogs = [];
|
|
1237
|
-
const recentErrorLogs = [];
|
|
1238
|
-
const recentTime = now - 24 * 60 * 60 * 1000; // 24 hours ago
|
|
1239
|
-
try {
|
|
1240
|
-
for (var _2 = true, _3 = __asyncValues(this.errorLogDb.iterator()), _4; _4 = yield _3.next(), _d = _4.done, !_d; _2 = true) {
|
|
1241
|
-
_f = _4.value;
|
|
1242
|
-
_2 = false;
|
|
1243
|
-
const [, value] = _f;
|
|
1244
|
-
const log = JSON.parse(value);
|
|
1245
|
-
errorLogs.push(log);
|
|
1246
|
-
if (log.timestamp >= recentTime) {
|
|
1247
|
-
recentErrorLogs.push(log);
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
}
|
|
1251
|
-
catch (e_7_1) { e_7 = { error: e_7_1 }; }
|
|
1252
|
-
finally {
|
|
1253
|
-
try {
|
|
1254
|
-
if (!_2 && !_d && (_e = _3.return)) yield _e.call(_3);
|
|
1255
|
-
}
|
|
1256
|
-
finally { if (e_7) throw e_7.error; }
|
|
1257
|
-
}
|
|
1258
|
-
// Get vendors and services for mapping
|
|
1259
|
-
const vendors = this.getVendors();
|
|
1260
|
-
const vendorMap = new Map(vendors.map(v => [v.id, v.name]));
|
|
1261
|
-
const services = this.getAPIServices();
|
|
1262
|
-
const serviceMap = new Map(services.map(s => [s.id, { name: s.name, vendorId: s.vendorId }]));
|
|
1263
|
-
// Calculate overview
|
|
1264
|
-
const totalRequests = allLogs.length;
|
|
1265
|
-
const successRequests = allLogs.filter(log => log.statusCode && log.statusCode >= 200 && log.statusCode < 400).length;
|
|
1266
|
-
const totalInputTokens = allLogs.reduce((sum, log) => { var _a; return sum + (((_a = log.usage) === null || _a === void 0 ? void 0 : _a.inputTokens) || 0); }, 0);
|
|
1267
|
-
const totalOutputTokens = allLogs.reduce((sum, log) => { var _a; return sum + (((_a = log.usage) === null || _a === void 0 ? void 0 : _a.outputTokens) || 0); }, 0);
|
|
1268
|
-
const totalCacheReadTokens = allLogs.reduce((sum, log) => { var _a; return sum + (((_a = log.usage) === null || _a === void 0 ? void 0 : _a.cacheReadInputTokens) || 0); }, 0);
|
|
1269
|
-
const totalTokens = allLogs.reduce((sum, log) => {
|
|
1270
|
-
var _a, _b, _c;
|
|
1271
|
-
if ((_a = log.usage) === null || _a === void 0 ? void 0 : _a.totalTokens)
|
|
1272
|
-
return sum + log.usage.totalTokens;
|
|
1273
|
-
return sum + (((_b = log.usage) === null || _b === void 0 ? void 0 : _b.inputTokens) || 0) + (((_c = log.usage) === null || _c === void 0 ? void 0 : _c.outputTokens) || 0);
|
|
1274
|
-
}, 0);
|
|
1275
|
-
const avgResponseTime = allLogs.length > 0
|
|
1276
|
-
? allLogs.reduce((sum, log) => sum + (log.responseTime || 0), 0) / allLogs.length
|
|
1277
|
-
: 0;
|
|
1278
|
-
const successRate = totalRequests > 0 ? (successRequests / totalRequests) * 100 : 0;
|
|
1279
|
-
// Calculate coding time (estimate based on tokens and requests)
|
|
1280
|
-
// Assume average reading speed: 250 tokens/minute, coding speed: 100 tokens/minute
|
|
1281
|
-
const totalCodingTime = Math.round(totalInputTokens / 250 + totalOutputTokens / 100);
|
|
1282
|
-
// Group by target type
|
|
1283
|
-
const byTargetTypeMap = new Map();
|
|
1284
|
-
for (const log of allLogs) {
|
|
1285
|
-
const key = log.targetType || 'unknown';
|
|
1286
|
-
if (!byTargetTypeMap.has(key)) {
|
|
1287
|
-
byTargetTypeMap.set(key, { requests: 0, tokens: 0, responseTime: 0 });
|
|
1288
|
-
}
|
|
1289
|
-
const stats = byTargetTypeMap.get(key);
|
|
1290
|
-
stats.requests++;
|
|
1291
|
-
stats.tokens += ((_g = log.usage) === null || _g === void 0 ? void 0 : _g.totalTokens) || (((_h = log.usage) === null || _h === void 0 ? void 0 : _h.inputTokens) || 0) + (((_j = log.usage) === null || _j === void 0 ? void 0 : _j.outputTokens) || 0);
|
|
1292
|
-
stats.responseTime += log.responseTime || 0;
|
|
1293
|
-
}
|
|
1294
|
-
const byTargetType = Array.from(byTargetTypeMap.entries()).map(([targetType, stats]) => ({
|
|
1295
|
-
targetType: targetType,
|
|
1296
|
-
totalRequests: stats.requests,
|
|
1297
|
-
totalTokens: stats.tokens,
|
|
1298
|
-
avgResponseTime: stats.requests > 0 ? Math.round(stats.responseTime / stats.requests) : 0,
|
|
1299
|
-
}));
|
|
1300
|
-
// Group by vendor
|
|
1301
|
-
const byVendorMap = new Map();
|
|
1302
|
-
for (const log of allLogs) {
|
|
1303
|
-
const key = log.vendorId || 'unknown';
|
|
1304
|
-
if (!byVendorMap.has(key)) {
|
|
1305
|
-
byVendorMap.set(key, { requests: 0, tokens: 0, responseTime: 0 });
|
|
1306
|
-
}
|
|
1307
|
-
const stats = byVendorMap.get(key);
|
|
1308
|
-
stats.requests++;
|
|
1309
|
-
stats.tokens += ((_k = log.usage) === null || _k === void 0 ? void 0 : _k.totalTokens) || (((_l = log.usage) === null || _l === void 0 ? void 0 : _l.inputTokens) || 0) + (((_m = log.usage) === null || _m === void 0 ? void 0 : _m.outputTokens) || 0);
|
|
1310
|
-
stats.responseTime += log.responseTime || 0;
|
|
1311
|
-
}
|
|
1312
|
-
const byVendor = Array.from(byVendorMap.entries()).map(([vendorId, stats]) => ({
|
|
1313
|
-
vendorId,
|
|
1314
|
-
vendorName: vendorMap.get(vendorId) || 'Unknown',
|
|
1315
|
-
totalRequests: stats.requests,
|
|
1316
|
-
totalTokens: stats.tokens,
|
|
1317
|
-
avgResponseTime: stats.requests > 0 ? Math.round(stats.responseTime / stats.requests) : 0,
|
|
1318
|
-
}));
|
|
1319
|
-
// Group by service
|
|
1320
|
-
const byServiceMap = new Map();
|
|
1321
|
-
for (const log of allLogs) {
|
|
1322
|
-
const key = log.targetServiceId || 'unknown';
|
|
1323
|
-
if (!byServiceMap.has(key)) {
|
|
1324
|
-
byServiceMap.set(key, { requests: 0, tokens: 0, responseTime: 0 });
|
|
1325
|
-
}
|
|
1326
|
-
const stats = byServiceMap.get(key);
|
|
1327
|
-
stats.requests++;
|
|
1328
|
-
stats.tokens += ((_o = log.usage) === null || _o === void 0 ? void 0 : _o.totalTokens) || (((_p = log.usage) === null || _p === void 0 ? void 0 : _p.inputTokens) || 0) + (((_q = log.usage) === null || _q === void 0 ? void 0 : _q.outputTokens) || 0);
|
|
1329
|
-
stats.responseTime += log.responseTime || 0;
|
|
1330
|
-
}
|
|
1331
|
-
const byService = Array.from(byServiceMap.entries()).map(([serviceId, stats]) => {
|
|
1332
|
-
const serviceInfo = serviceMap.get(serviceId);
|
|
1333
|
-
return {
|
|
1334
|
-
serviceId,
|
|
1335
|
-
serviceName: (serviceInfo === null || serviceInfo === void 0 ? void 0 : serviceInfo.name) || 'Unknown',
|
|
1336
|
-
vendorName: serviceInfo && serviceInfo.vendorId ? (vendorMap.get(serviceInfo.vendorId) || 'Unknown') : 'Unknown',
|
|
1337
|
-
totalRequests: stats.requests,
|
|
1338
|
-
totalTokens: stats.tokens,
|
|
1339
|
-
avgResponseTime: stats.requests > 0 ? Math.round(stats.responseTime / stats.requests) : 0,
|
|
1340
|
-
};
|
|
1341
|
-
});
|
|
1342
|
-
// Group by model
|
|
1343
|
-
const byModelMap = new Map();
|
|
1344
|
-
for (const log of allLogs) {
|
|
1345
|
-
const key = log.targetModel || 'unknown';
|
|
1346
|
-
if (!byModelMap.has(key)) {
|
|
1347
|
-
byModelMap.set(key, { requests: 0, tokens: 0, responseTime: 0 });
|
|
1348
|
-
}
|
|
1349
|
-
const stats = byModelMap.get(key);
|
|
1350
|
-
stats.requests++;
|
|
1351
|
-
stats.tokens += ((_r = log.usage) === null || _r === void 0 ? void 0 : _r.totalTokens) || (((_s = log.usage) === null || _s === void 0 ? void 0 : _s.inputTokens) || 0) + (((_t = log.usage) === null || _t === void 0 ? void 0 : _t.outputTokens) || 0);
|
|
1352
|
-
stats.responseTime += log.responseTime || 0;
|
|
1353
|
-
}
|
|
1354
|
-
const byModel = Array.from(byModelMap.entries()).map(([modelName, stats]) => ({
|
|
1355
|
-
modelName,
|
|
1356
|
-
totalRequests: stats.requests,
|
|
1357
|
-
totalTokens: stats.tokens,
|
|
1358
|
-
avgResponseTime: stats.requests > 0 ? Math.round(stats.responseTime / stats.requests) : 0,
|
|
1359
|
-
}));
|
|
1360
|
-
// Timeline data (by day)
|
|
1361
|
-
const timelineMap = new Map();
|
|
1362
|
-
for (const log of allLogs) {
|
|
1363
|
-
const date = new Date(log.timestamp).toISOString().split('T')[0];
|
|
1364
|
-
if (!timelineMap.has(date)) {
|
|
1365
|
-
timelineMap.set(date, { requests: 0, tokens: 0, inputTokens: 0, outputTokens: 0 });
|
|
1366
|
-
}
|
|
1367
|
-
const stats = timelineMap.get(date);
|
|
1368
|
-
stats.requests++;
|
|
1369
|
-
stats.inputTokens += ((_u = log.usage) === null || _u === void 0 ? void 0 : _u.inputTokens) || 0;
|
|
1370
|
-
stats.outputTokens += ((_v = log.usage) === null || _v === void 0 ? void 0 : _v.outputTokens) || 0;
|
|
1371
|
-
stats.tokens += ((_w = log.usage) === null || _w === void 0 ? void 0 : _w.totalTokens) || (((_x = log.usage) === null || _x === void 0 ? void 0 : _x.inputTokens) || 0) + (((_y = log.usage) === null || _y === void 0 ? void 0 : _y.outputTokens) || 0);
|
|
1372
|
-
}
|
|
1373
|
-
const timeline = Array.from(timelineMap.entries())
|
|
1374
|
-
.map(([date, stats]) => ({
|
|
1375
|
-
date,
|
|
1376
|
-
totalRequests: stats.requests,
|
|
1377
|
-
totalTokens: stats.tokens,
|
|
1378
|
-
totalInputTokens: stats.inputTokens,
|
|
1379
|
-
totalOutputTokens: stats.outputTokens,
|
|
1380
|
-
}))
|
|
1381
|
-
.sort((a, b) => a.date.localeCompare(b.date));
|
|
1382
|
-
// Content type distribution (infer from request patterns)
|
|
1383
|
-
const contentTypeMap = new Map();
|
|
1384
|
-
for (const log of allLogs) {
|
|
1385
|
-
// Infer content type from request characteristics
|
|
1386
|
-
let contentType = 'default';
|
|
1387
|
-
if (log.body && (log.body.includes('image') || log.body.includes('base64'))) {
|
|
1388
|
-
contentType = 'image-understanding';
|
|
1389
|
-
}
|
|
1390
|
-
else if (log.requestModel && log.requestModel.toLowerCase().includes('think')) {
|
|
1391
|
-
contentType = 'thinking';
|
|
1392
|
-
}
|
|
1393
|
-
else if (log.usage && log.usage.inputTokens > 12000) {
|
|
1394
|
-
contentType = 'long-context';
|
|
1395
|
-
}
|
|
1396
|
-
contentTypeMap.set(contentType, (contentTypeMap.get(contentType) || 0) + 1);
|
|
1397
|
-
}
|
|
1398
|
-
const contentTypeDistribution = Array.from(contentTypeMap.entries()).map(([contentType, count]) => ({
|
|
1399
|
-
contentType,
|
|
1400
|
-
count,
|
|
1401
|
-
percentage: totalRequests > 0 ? Math.round((count / totalRequests) * 100) : 0,
|
|
1402
|
-
}));
|
|
1403
|
-
return {
|
|
1404
|
-
overview: {
|
|
1405
|
-
totalRequests,
|
|
1406
|
-
totalTokens,
|
|
1407
|
-
totalInputTokens,
|
|
1408
|
-
totalOutputTokens,
|
|
1409
|
-
totalCacheReadTokens,
|
|
1410
|
-
totalVendors: vendors.length,
|
|
1411
|
-
totalServices: services.length,
|
|
1412
|
-
totalRoutes: this.getRoutes().length,
|
|
1413
|
-
totalRules: this.getRules().length,
|
|
1414
|
-
avgResponseTime: Math.round(avgResponseTime),
|
|
1415
|
-
successRate: Math.round(successRate * 10) / 10,
|
|
1416
|
-
totalCodingTime,
|
|
1417
|
-
},
|
|
1418
|
-
byTargetType,
|
|
1419
|
-
byVendor,
|
|
1420
|
-
byService,
|
|
1421
|
-
byModel,
|
|
1422
|
-
timeline,
|
|
1423
|
-
contentTypeDistribution,
|
|
1424
|
-
errors: {
|
|
1425
|
-
totalErrors: errorLogs.length,
|
|
1426
|
-
recentErrors: recentErrorLogs.length,
|
|
1427
|
-
},
|
|
1428
|
-
};
|
|
1429
|
-
});
|
|
1430
|
-
}
|
|
1431
|
-
getRuleBlacklistStatus(serviceId, routeId, contentType) {
|
|
1432
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
1433
|
-
const key = `${routeId}:${contentType}:${serviceId}`;
|
|
1434
|
-
try {
|
|
1435
|
-
const value = yield this.blacklistDb.get(key);
|
|
1436
|
-
const entry = JSON.parse(value);
|
|
1437
|
-
// 检查是否过期
|
|
1438
|
-
if (Date.now() > entry.expiresAt) {
|
|
1439
|
-
yield this.blacklistDb.del(key);
|
|
1440
|
-
return null;
|
|
1441
|
-
}
|
|
1442
|
-
return entry;
|
|
1443
|
-
}
|
|
1444
|
-
catch (error) {
|
|
1445
|
-
if (error.code === 'LEVEL_NOT_FOUND') {
|
|
1446
|
-
return null;
|
|
1447
|
-
}
|
|
1448
|
-
throw error;
|
|
1449
|
-
}
|
|
1450
|
-
});
|
|
1451
|
-
}
|
|
1452
|
-
// Session operations
|
|
1453
|
-
/**
|
|
1454
|
-
* 创建或更新 session
|
|
1455
|
-
* 当有新的请求日志时调用此方法来更新 session 信息
|
|
1456
|
-
*/
|
|
1457
|
-
upsertSession(session) {
|
|
1458
|
-
const now = Date.now();
|
|
1459
|
-
const existing = this.db.prepare('SELECT * FROM sessions WHERE id = ?').get(session.id);
|
|
1460
|
-
if (existing) {
|
|
1461
|
-
// 更新现有 session
|
|
1462
|
-
this.db.prepare(`
|
|
1463
|
-
UPDATE sessions SET
|
|
1464
|
-
last_request_at = ?,
|
|
1465
|
-
request_count = request_count + 1,
|
|
1466
|
-
total_tokens = total_tokens + ?,
|
|
1467
|
-
vendor_id = ?,
|
|
1468
|
-
vendor_name = ?,
|
|
1469
|
-
service_id = ?,
|
|
1470
|
-
service_name = ?,
|
|
1471
|
-
model = ?
|
|
1472
|
-
WHERE id = ?
|
|
1473
|
-
`).run(now, session.totalTokens || 0, session.vendorId || null, session.vendorName || null, session.serviceId || null, session.serviceName || null, session.model || null, session.id);
|
|
1474
|
-
}
|
|
1475
|
-
else {
|
|
1476
|
-
// 创建新 session
|
|
1477
|
-
this.db.prepare(`
|
|
1478
|
-
INSERT INTO sessions (id, target_type, title, first_request_at, last_request_at, request_count, total_tokens, vendor_id, vendor_name, service_id, service_name, model)
|
|
1479
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1480
|
-
`).run(session.id, session.targetType, session.title || null, session.firstRequestAt, now, 1, session.totalTokens || 0, session.vendorId || null, session.vendorName || null, session.serviceId || null, session.serviceName || null, session.model || null);
|
|
1481
|
-
}
|
|
1482
|
-
}
|
|
1483
|
-
/**
|
|
1484
|
-
* 获取所有 sessions
|
|
1485
|
-
*/
|
|
1486
|
-
getSessions(limit = 100, offset = 0) {
|
|
1487
|
-
const rows = this.db.prepare('SELECT * FROM sessions ORDER BY last_request_at DESC LIMIT ? OFFSET ?').all(limit, offset);
|
|
1488
|
-
return rows.map((row) => ({
|
|
1489
|
-
id: row.id,
|
|
1490
|
-
targetType: row.target_type,
|
|
1491
|
-
title: row.title,
|
|
1492
|
-
firstRequestAt: row.first_request_at,
|
|
1493
|
-
lastRequestAt: row.last_request_at,
|
|
1494
|
-
requestCount: row.request_count,
|
|
1495
|
-
totalTokens: row.total_tokens,
|
|
1496
|
-
vendorId: row.vendor_id,
|
|
1497
|
-
vendorName: row.vendor_name,
|
|
1498
|
-
serviceId: row.service_id,
|
|
1499
|
-
serviceName: row.service_name,
|
|
1500
|
-
model: row.model,
|
|
1501
|
-
}));
|
|
1502
|
-
}
|
|
1503
|
-
/**
|
|
1504
|
-
* 根据 session ID 获取 session
|
|
1505
|
-
*/
|
|
1506
|
-
getSession(id) {
|
|
1507
|
-
const row = this.db.prepare('SELECT * FROM sessions WHERE id = ?').get(id);
|
|
1508
|
-
if (!row)
|
|
1509
|
-
return null;
|
|
1510
|
-
return {
|
|
1511
|
-
id: row.id,
|
|
1512
|
-
targetType: row.target_type,
|
|
1513
|
-
title: row.title,
|
|
1514
|
-
firstRequestAt: row.first_request_at,
|
|
1515
|
-
lastRequestAt: row.last_request_at,
|
|
1516
|
-
requestCount: row.request_count,
|
|
1517
|
-
totalTokens: row.total_tokens,
|
|
1518
|
-
vendorId: row.vendor_id,
|
|
1519
|
-
vendorName: row.vendor_name,
|
|
1520
|
-
serviceId: row.service_id,
|
|
1521
|
-
serviceName: row.service_name,
|
|
1522
|
-
model: row.model,
|
|
1523
|
-
};
|
|
1524
|
-
}
|
|
1525
|
-
/**
|
|
1526
|
-
* 获取 sessions 总数
|
|
1527
|
-
*/
|
|
1528
|
-
getSessionsCount() {
|
|
1529
|
-
const result = this.db.prepare('SELECT COUNT(*) as count FROM sessions').get();
|
|
1530
|
-
return result.count;
|
|
1531
|
-
}
|
|
1532
|
-
/**
|
|
1533
|
-
* 删除指定 session
|
|
1534
|
-
*/
|
|
1535
|
-
deleteSession(id) {
|
|
1536
|
-
const result = this.db.prepare('DELETE FROM sessions WHERE id = ?').run(id);
|
|
1537
|
-
return result.changes > 0;
|
|
1538
|
-
}
|
|
1539
|
-
/**
|
|
1540
|
-
* 清空所有 sessions
|
|
1541
|
-
*/
|
|
1542
|
-
clearSessions() {
|
|
1543
|
-
this.db.prepare('DELETE FROM sessions').run();
|
|
1544
|
-
}
|
|
1545
|
-
/**
|
|
1546
|
-
* 获取指定 session 的请求日志
|
|
1547
|
-
* @param sessionId session ID
|
|
1548
|
-
* @param limit 限制数量
|
|
1549
|
-
*/
|
|
1550
|
-
getLogsBySessionId(sessionId_1) {
|
|
1551
|
-
return __awaiter(this, arguments, void 0, function* (sessionId, limit = 100) {
|
|
1552
|
-
var _a, e_8, _b, _c;
|
|
1553
|
-
// 从 LevelDB 中读取所有日志
|
|
1554
|
-
const allLogs = [];
|
|
1555
|
-
try {
|
|
1556
|
-
for (var _d = true, _e = __asyncValues(this.logDb.iterator()), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
|
|
1557
|
-
_c = _f.value;
|
|
1558
|
-
_d = false;
|
|
1559
|
-
const [, value] = _c;
|
|
1560
|
-
const log = JSON.parse(value);
|
|
1561
|
-
// 检查日志是否属于该 session(通过 headers 中的 session_id 或 body 中的 metadata.user_id)
|
|
1562
|
-
if (this.isLogBelongsToSession(log, sessionId)) {
|
|
1563
|
-
allLogs.push(log);
|
|
1564
|
-
}
|
|
1565
|
-
}
|
|
1566
|
-
}
|
|
1567
|
-
catch (e_8_1) { e_8 = { error: e_8_1 }; }
|
|
1568
|
-
finally {
|
|
1569
|
-
try {
|
|
1570
|
-
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
|
|
1571
|
-
}
|
|
1572
|
-
finally { if (e_8) throw e_8.error; }
|
|
1573
|
-
}
|
|
1574
|
-
// 按时间倒序排序并限制数量
|
|
1575
|
-
allLogs.sort((a, b) => b.timestamp - a.timestamp);
|
|
1576
|
-
return allLogs.slice(0, limit);
|
|
1577
|
-
});
|
|
1578
|
-
}
|
|
1579
|
-
/**
|
|
1580
|
-
* 检查日志是否属于指定 session
|
|
1581
|
-
*/
|
|
1582
|
-
isLogBelongsToSession(log, sessionId) {
|
|
1583
|
-
var _a, _b;
|
|
1584
|
-
// 检查 headers 中的 session_id(Codex)
|
|
1585
|
-
if (((_a = log.headers) === null || _a === void 0 ? void 0 : _a['session_id']) === sessionId) {
|
|
1586
|
-
return true;
|
|
1587
|
-
}
|
|
1588
|
-
// 检查 body 中的 metadata.user_id(Claude Code)
|
|
1589
|
-
if (log.body) {
|
|
1590
|
-
try {
|
|
1591
|
-
const body = JSON.parse(log.body);
|
|
1592
|
-
if (((_b = body.metadata) === null || _b === void 0 ? void 0 : _b.user_id) === sessionId) {
|
|
1593
|
-
return true;
|
|
1594
|
-
}
|
|
1595
|
-
}
|
|
1596
|
-
catch (_c) {
|
|
1597
|
-
// 忽略解析错误
|
|
1598
|
-
}
|
|
1599
|
-
}
|
|
1600
|
-
return false;
|
|
1601
|
-
}
|
|
1602
|
-
close() {
|
|
1603
|
-
this.db.close();
|
|
1604
|
-
this.logDb.close();
|
|
1605
|
-
this.errorLogDb.close();
|
|
1606
|
-
this.blacklistDb.close();
|
|
1607
|
-
}
|
|
1608
|
-
}
|
|
1609
|
-
exports.DatabaseManager = DatabaseManager;
|