aicodeswitch 3.9.4 → 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.
@@ -1,37 +1,4 @@
1
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
2
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
3
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
4
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -41,170 +8,33 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
41
8
  step((generator = generator.apply(thisArg, _arguments || [])).next());
42
9
  });
43
10
  };
44
- var __importDefault = (this && this.__importDefault) || function (mod) {
45
- return (mod && mod.__esModule) ? mod : { "default": mod };
46
- };
47
11
  Object.defineProperty(exports, "__esModule", { value: true });
48
12
  exports.DatabaseFactory = void 0;
49
- const path_1 = __importDefault(require("path"));
50
13
  const fs_database_1 = require("./fs-database");
51
- const migrateToFs = __importStar(require("./migrate-to-fs"));
52
14
  /**
53
15
  * 数据库工厂
54
- * 根据配置创建相应的数据库实例
16
+ * 创建文件系统数据库实例
55
17
  */
56
18
  class DatabaseFactory {
57
19
  /**
58
20
  * 创建数据库实例
59
21
  * @param dataPath 数据存储路径
60
- * @param type 数据库类型,默认使用文件系统
61
22
  */
62
- static create(dataPath_1) {
63
- return __awaiter(this, arguments, void 0, function* (dataPath, type = 'filesystem') {
64
- if (type === 'filesystem') {
65
- const db = new fs_database_1.FileSystemDatabaseManager(dataPath);
66
- yield db.initialize();
67
- return db;
68
- }
69
- // 如果用户明确要求使用 SQLite,尝试加载
70
- if (type === 'sqlite') {
71
- try {
72
- const { DatabaseManager } = yield Promise.resolve().then(() => __importStar(require('./database.js')));
73
- const db = new DatabaseManager(dataPath);
74
- yield db.initialize();
75
- return db;
76
- }
77
- catch (error) {
78
- console.error('[Database] Failed to load SQLite database, falling back to filesystem:', error);
79
- console.log('[Database] Using filesystem database instead');
80
- const db = new fs_database_1.FileSystemDatabaseManager(dataPath);
81
- yield db.initialize();
82
- return db;
83
- }
84
- }
85
- throw new Error(`Unknown database type: ${type}`);
23
+ static create(dataPath) {
24
+ return __awaiter(this, void 0, void 0, function* () {
25
+ const db = new fs_database_1.FileSystemDatabaseManager(dataPath);
26
+ yield db.initialize();
27
+ return db;
86
28
  });
87
29
  }
88
30
  /**
89
- * 自动检测并创建数据库实例
90
- * 优先使用文件系统数据库,如果存在旧的 SQLite 数据库则自动迁移
31
+ * 自动创建数据库实例(兼容旧 API)
32
+ * @param dataPath 数据存储路径
33
+ * @param _legacyDataPath 已弃用,不再使用
91
34
  */
92
- static createAuto(dataPath, legacyDataPath) {
35
+ static createAuto(dataPath, _legacyDataPath) {
93
36
  return __awaiter(this, void 0, void 0, function* () {
94
- const fs = yield Promise.resolve().then(() => __importStar(require('fs/promises')));
95
- const legacyPath = legacyDataPath && legacyDataPath !== dataPath ? legacyDataPath : null;
96
- // 检查迁移完成标记文件
97
- const migrationMarkerPath = path_1.default.join(dataPath, '.migration-completed');
98
- const hasMigrationMarker = yield fs.access(migrationMarkerPath)
99
- .then(() => true)
100
- .catch(() => false);
101
- // 检查是否存在文件系统数据库
102
- const fsDbExists = yield fs.access(path_1.default.join(dataPath, 'config.json'))
103
- .then(() => true)
104
- .catch(() => false);
105
- // 如果存在迁移标记且文件系统数据库存在,使用文件系统数据库
106
- if (hasMigrationMarker && fsDbExists) {
107
- console.log('[Database] Migration marker found, using filesystem database');
108
- return this.create(dataPath, 'filesystem');
109
- }
110
- // 如果不存在迁移标记但存在文件系统数据库,可能是用户手动删除了标记
111
- // 这种情况下仍然使用文件系统数据库(避免数据丢失)
112
- if (fsDbExists) {
113
- console.log('[Database] Using existing filesystem database (no migration marker)');
114
- return this.create(dataPath, 'filesystem');
115
- }
116
- // 如果新目录为空,尝试从旧 data 目录迁移文件系统数据库
117
- if (legacyPath) {
118
- const legacyFsDbExists = yield fs.access(path_1.default.join(legacyPath, 'config.json'))
119
- .then(() => true)
120
- .catch(() => false);
121
- if (legacyFsDbExists) {
122
- console.log('[Database] Found legacy filesystem database, migrating to new directory...');
123
- try {
124
- yield migrateToFs.migrateLegacyFsData(legacyPath, dataPath);
125
- yield fs.writeFile(migrationMarkerPath, new Date().toISOString(), 'utf-8');
126
- console.log('[Database] Legacy filesystem database migration completed');
127
- return this.create(dataPath, 'filesystem');
128
- }
129
- catch (error) {
130
- console.error('[Database] Legacy filesystem migration failed:', error);
131
- console.log('[Database] Falling back to create new filesystem database');
132
- }
133
- }
134
- }
135
- // 如果新目录为空,检查旧 data 目录是否存在 SQLite 数据库
136
- if (legacyPath) {
137
- const legacySqliteExists = yield fs.access(path_1.default.join(legacyPath, 'app.db'))
138
- .then(() => true)
139
- .catch(() => false);
140
- if (legacySqliteExists) {
141
- console.log('[Database] Found legacy SQLite database, migrating to new filesystem directory...');
142
- try {
143
- yield migrateToFs.migrateToFileSystem(legacyPath, dataPath);
144
- console.log('[Database] Verifying migration...');
145
- const verification = yield migrateToFs.verifyMigration(dataPath);
146
- if (verification.success) {
147
- console.log('[Database] ✅ Migration verified successfully');
148
- if (verification.warnings.length > 0) {
149
- console.log('[Database] Warnings:', verification.warnings);
150
- }
151
- yield fs.writeFile(migrationMarkerPath, new Date().toISOString(), 'utf-8');
152
- console.log('[Database] Migration marker file created:', migrationMarkerPath);
153
- }
154
- else {
155
- console.error('[Database] ❌ Migration verification failed');
156
- console.error('[Database] Errors:', verification.errors);
157
- throw new Error('Migration verification failed');
158
- }
159
- console.log('[Database] Migration completed, using filesystem database');
160
- return this.create(dataPath, 'filesystem');
161
- }
162
- catch (error) {
163
- console.error('[Database] Migration failed:', error);
164
- console.log('[Database] Creating new filesystem database');
165
- }
166
- }
167
- }
168
- // 检查是否存在旧的 SQLite 数据库
169
- const sqliteDbExists = yield fs.access(path_1.default.join(dataPath, 'app.db'))
170
- .then(() => true)
171
- .catch(() => false);
172
- if (sqliteDbExists) {
173
- console.log('[Database] Found old SQLite database, migrating to filesystem...');
174
- try {
175
- // 执行迁移
176
- yield migrateToFs.migrateToFileSystem(dataPath);
177
- // 验证迁移结果
178
- console.log('[Database] Verifying migration...');
179
- const verification = yield migrateToFs.verifyMigration(dataPath);
180
- if (verification.success) {
181
- console.log('[Database] ✅ Migration verified successfully');
182
- if (verification.warnings.length > 0) {
183
- console.log('[Database] Warnings:', verification.warnings);
184
- }
185
- // 只有在验证成功后才创建标记文件
186
- const migrationMarkerPath = path_1.default.join(dataPath, '.migration-completed');
187
- yield fs.writeFile(migrationMarkerPath, new Date().toISOString(), 'utf-8');
188
- console.log('[Database] Migration marker file created:', migrationMarkerPath);
189
- }
190
- else {
191
- console.error('[Database] ❌ Migration verification failed');
192
- console.error('[Database] Errors:', verification.errors);
193
- throw new Error('Migration verification failed');
194
- }
195
- console.log('[Database] Migration completed, using filesystem database');
196
- }
197
- catch (error) {
198
- console.error('[Database] Migration failed:', error);
199
- console.log('[Database] Creating new filesystem database');
200
- // 迁移失败时,原始数据库文件保持不变
201
- // 用户可以使用老版本继续运行,或手动重新配置
202
- }
203
- }
204
- else {
205
- console.log('[Database] No existing database found, creating new filesystem database');
206
- }
207
- return this.create(dataPath, 'filesystem');
37
+ return this.create(dataPath);
208
38
  });
209
39
  }
210
40
  }
@@ -29,6 +29,19 @@ const promises_1 = __importDefault(require("fs/promises"));
29
29
  const crypto_1 = __importDefault(require("crypto"));
30
30
  const crypto_js_1 = __importDefault(require("crypto-js"));
31
31
  const type_migration_1 = require("./type-migration");
32
+ const VALID_CODEX_REASONING_EFFORTS = ['low', 'medium', 'high', 'xhigh'];
33
+ const DEFAULT_CODEX_REASONING_EFFORT = 'high';
34
+ const DEFAULT_FAILOVER_RECOVERY_SECONDS = 30;
35
+ const isCodexReasoningEffort = (value) => {
36
+ return typeof value === 'string' && VALID_CODEX_REASONING_EFFORTS.includes(value);
37
+ };
38
+ const normalizeFailoverRecoverySeconds = (value) => {
39
+ const parsed = typeof value === 'number' ? value : Number(value);
40
+ if (!Number.isFinite(parsed) || parsed <= 0) {
41
+ return DEFAULT_FAILOVER_RECOVERY_SECONDS;
42
+ }
43
+ return Math.floor(parsed);
44
+ };
32
45
  /**
33
46
  * 基于文件系统的数据库管理器
34
47
  * 使用 JSON 文件存储数据,无需编译依赖
@@ -209,8 +222,8 @@ class FileSystemDatabaseManager {
209
222
  yield this.loadAllData();
210
223
  // 执行数据源类型迁移(在加载数据之后)
211
224
  yield this.migrateSourceTypes();
212
- // OpenAI base URL 迁移:将末尾 /v1 自动移除
213
- yield this.migrateOpenAIBaseUrls();
225
+ // 路由级工具配置迁移到全局配置(兼容旧版本)
226
+ yield this.migrateRouteToolSettingsToGlobalConfig();
214
227
  // 确保默认配置
215
228
  yield this.ensureDefaultConfig();
216
229
  });
@@ -233,15 +246,37 @@ class FileSystemDatabaseManager {
233
246
  }
234
247
  loadVendors() {
235
248
  return __awaiter(this, void 0, void 0, function* () {
249
+ let needSave = false;
236
250
  try {
237
251
  const data = yield promises_1.default.readFile(this.vendorsFile, 'utf-8');
238
- this.vendors = JSON.parse(data);
252
+ const parsed = JSON.parse(data);
253
+ this.vendors = Array.isArray(parsed) ? parsed.map((vendor) => {
254
+ const normalizedServices = Array.isArray(vendor.services)
255
+ ? vendor.services.map((service) => {
256
+ const normalizedService = Object.assign(Object.assign({}, service), { apiKey: typeof service.apiKey === 'string' ? service.apiKey : '', inheritVendorApiKey: service.inheritVendorApiKey === true });
257
+ if (normalizedService.apiKey !== service.apiKey ||
258
+ normalizedService.inheritVendorApiKey !== service.inheritVendorApiKey) {
259
+ needSave = true;
260
+ }
261
+ return normalizedService;
262
+ })
263
+ : [];
264
+ const normalizedVendor = Object.assign(Object.assign({}, vendor), { apiKey: typeof vendor.apiKey === 'string' ? vendor.apiKey : '', services: normalizedServices });
265
+ if (normalizedVendor.apiKey !== vendor.apiKey ||
266
+ !Array.isArray(vendor.services)) {
267
+ needSave = true;
268
+ }
269
+ return normalizedVendor;
270
+ }) : [];
239
271
  }
240
272
  catch (_a) {
241
273
  this.vendors = [];
242
274
  }
243
275
  // 兼容性检查:如果存在旧的 services.json,自动迁移
244
276
  yield this.migrateServicesIfNeeded();
277
+ if (needSave) {
278
+ yield this.saveVendors();
279
+ }
245
280
  });
246
281
  }
247
282
  /**
@@ -359,41 +394,6 @@ class FileSystemDatabaseManager {
359
394
  console.log(`[TypeMigration] Migration completed. Migrated ${migratedCount} services.`);
360
395
  });
361
396
  }
362
- /**
363
- * 迁移 OpenAI base URL(在初始化时执行)
364
- * 仅处理 sourceType=openai 且 apiUrl 末尾为 /v1 的服务
365
- */
366
- migrateOpenAIBaseUrls() {
367
- return __awaiter(this, void 0, void 0, function* () {
368
- console.log('[OpenAIBaseUrlMigration] Checking for OpenAI base URL migration...');
369
- let migratedCount = 0;
370
- for (const vendor of this.vendors) {
371
- if (!vendor.services)
372
- continue;
373
- for (const service of vendor.services) {
374
- if (service.sourceType !== 'openai' || typeof service.apiUrl !== 'string') {
375
- continue;
376
- }
377
- const trimmedUrl = service.apiUrl.trim();
378
- if (!/\/v1\/?$/i.test(trimmedUrl)) {
379
- continue;
380
- }
381
- const migratedUrl = trimmedUrl.replace(/\/v1\/?$/i, '');
382
- if (migratedUrl && migratedUrl !== service.apiUrl) {
383
- console.log(`[OpenAIBaseUrlMigration] Migrated service "${service.name}": ${service.apiUrl} -> ${migratedUrl}`);
384
- service.apiUrl = migratedUrl;
385
- migratedCount++;
386
- }
387
- }
388
- }
389
- if (migratedCount === 0) {
390
- console.log('[OpenAIBaseUrlMigration] No migration needed');
391
- return;
392
- }
393
- yield this.saveVendors();
394
- console.log(`[OpenAIBaseUrlMigration] Migration completed. Migrated ${migratedCount} services.`);
395
- });
396
- }
397
397
  /**
398
398
  * 迁移导入数据中的类型
399
399
  * 用于导入功能,自动将旧类型转换为新类型
@@ -402,11 +402,7 @@ class FileSystemDatabaseManager {
402
402
  return vendors.map(vendor => {
403
403
  var _a;
404
404
  return (Object.assign(Object.assign({}, vendor), { services: (_a = vendor.services) === null || _a === void 0 ? void 0 : _a.map(service => {
405
- const normalizedSourceType = service.sourceType ? (0, type_migration_1.normalizeSourceType)(service.sourceType) : undefined;
406
- const normalizedApiUrl = normalizedSourceType === 'openai' && typeof service.apiUrl === 'string'
407
- ? service.apiUrl.trim().replace(/\/v1\/?$/i, '')
408
- : service.apiUrl;
409
- return Object.assign(Object.assign({}, service), { sourceType: normalizedSourceType, apiUrl: normalizedApiUrl });
405
+ return Object.assign(Object.assign({}, service), { sourceType: service.sourceType ? (0, type_migration_1.normalizeSourceType)(service.sourceType) : undefined });
410
406
  }) }));
411
407
  });
412
408
  }
@@ -827,22 +823,89 @@ class FileSystemDatabaseManager {
827
823
  }
828
824
  ensureDefaultConfig() {
829
825
  return __awaiter(this, void 0, void 0, function* () {
830
- if (!this.config) {
831
- this.config = {
832
- enableLogging: true,
833
- logRetentionDays: 30,
834
- maxLogSize: 100000,
835
- apiKey: '',
836
- enableFailover: true,
837
- proxyEnabled: false,
838
- proxyUrl: '',
839
- proxyUsername: '',
840
- proxyPassword: '',
841
- };
826
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
827
+ const current = this.config;
828
+ this.config = {
829
+ enableLogging: (_a = current === null || current === void 0 ? void 0 : current.enableLogging) !== null && _a !== void 0 ? _a : true,
830
+ logRetentionDays: (_b = current === null || current === void 0 ? void 0 : current.logRetentionDays) !== null && _b !== void 0 ? _b : 30,
831
+ maxLogSize: (_c = current === null || current === void 0 ? void 0 : current.maxLogSize) !== null && _c !== void 0 ? _c : 100000,
832
+ apiKey: (_d = current === null || current === void 0 ? void 0 : current.apiKey) !== null && _d !== void 0 ? _d : '',
833
+ enableFailover: (_e = current === null || current === void 0 ? void 0 : current.enableFailover) !== null && _e !== void 0 ? _e : true,
834
+ failoverRecoverySeconds: normalizeFailoverRecoverySeconds(current === null || current === void 0 ? void 0 : current.failoverRecoverySeconds),
835
+ enableAgentTeams: (_f = current === null || current === void 0 ? void 0 : current.enableAgentTeams) !== null && _f !== void 0 ? _f : false,
836
+ enableBypassPermissionsSupport: (_g = current === null || current === void 0 ? void 0 : current.enableBypassPermissionsSupport) !== null && _g !== void 0 ? _g : false,
837
+ codexModelReasoningEffort: isCodexReasoningEffort(current === null || current === void 0 ? void 0 : current.codexModelReasoningEffort)
838
+ ? current.codexModelReasoningEffort
839
+ : DEFAULT_CODEX_REASONING_EFFORT,
840
+ proxyEnabled: (_h = current === null || current === void 0 ? void 0 : current.proxyEnabled) !== null && _h !== void 0 ? _h : false,
841
+ proxyUrl: (_j = current === null || current === void 0 ? void 0 : current.proxyUrl) !== null && _j !== void 0 ? _j : '',
842
+ proxyUsername: (_k = current === null || current === void 0 ? void 0 : current.proxyUsername) !== null && _k !== void 0 ? _k : '',
843
+ proxyPassword: (_l = current === null || current === void 0 ? void 0 : current.proxyPassword) !== null && _l !== void 0 ? _l : '',
844
+ };
845
+ // 仅在首次创建或存在字段补齐时落盘
846
+ if (!current || JSON.stringify(current) !== JSON.stringify(this.config)) {
842
847
  yield this.saveConfig();
843
848
  }
844
849
  });
845
850
  }
851
+ migrateRouteToolSettingsToGlobalConfig() {
852
+ return __awaiter(this, void 0, void 0, function* () {
853
+ const hasGlobalToolConfig = !!this.config &&
854
+ (Object.prototype.hasOwnProperty.call(this.config, 'enableAgentTeams') ||
855
+ Object.prototype.hasOwnProperty.call(this.config, 'enableBypassPermissionsSupport') ||
856
+ Object.prototype.hasOwnProperty.call(this.config, 'codexModelReasoningEffort'));
857
+ const getPreferredRoute = (targetType) => {
858
+ const activeRoute = this.routes.find(route => route.targetType === targetType && route.isActive);
859
+ if (activeRoute) {
860
+ return activeRoute;
861
+ }
862
+ return this.routes.find(route => route.targetType === targetType);
863
+ };
864
+ let configUpdated = false;
865
+ if (!hasGlobalToolConfig) {
866
+ const preferredClaudeRoute = getPreferredRoute('claude-code');
867
+ const preferredCodexRoute = getPreferredRoute('codex');
868
+ const nextConfig = Object.assign({}, (this.config || {}));
869
+ if (typeof (preferredClaudeRoute === null || preferredClaudeRoute === void 0 ? void 0 : preferredClaudeRoute.enableAgentTeams) === 'boolean') {
870
+ nextConfig.enableAgentTeams = preferredClaudeRoute.enableAgentTeams;
871
+ configUpdated = true;
872
+ }
873
+ if (typeof (preferredClaudeRoute === null || preferredClaudeRoute === void 0 ? void 0 : preferredClaudeRoute.enableBypassPermissionsSupport) === 'boolean') {
874
+ nextConfig.enableBypassPermissionsSupport = preferredClaudeRoute.enableBypassPermissionsSupport;
875
+ configUpdated = true;
876
+ }
877
+ if (isCodexReasoningEffort(preferredCodexRoute === null || preferredCodexRoute === void 0 ? void 0 : preferredCodexRoute.codexModelReasoningEffort)) {
878
+ nextConfig.codexModelReasoningEffort = preferredCodexRoute.codexModelReasoningEffort;
879
+ configUpdated = true;
880
+ }
881
+ if (configUpdated) {
882
+ this.config = nextConfig;
883
+ yield this.saveConfig();
884
+ console.log('[ConfigMigration] Migrated route-level tool settings to global app config');
885
+ }
886
+ }
887
+ // 清理路由中的旧字段,避免后续重复歧义
888
+ let routesUpdated = false;
889
+ this.routes = this.routes.map((route) => {
890
+ const hasLegacyFields = Object.prototype.hasOwnProperty.call(route, 'enableAgentTeams') ||
891
+ Object.prototype.hasOwnProperty.call(route, 'enableBypassPermissionsSupport') ||
892
+ Object.prototype.hasOwnProperty.call(route, 'codexModelReasoningEffort');
893
+ if (!hasLegacyFields) {
894
+ return route;
895
+ }
896
+ routesUpdated = true;
897
+ const cleanedRoute = Object.assign({}, route);
898
+ delete cleanedRoute.enableAgentTeams;
899
+ delete cleanedRoute.enableBypassPermissionsSupport;
900
+ delete cleanedRoute.codexModelReasoningEffort;
901
+ return cleanedRoute;
902
+ });
903
+ if (routesUpdated) {
904
+ yield this.saveRoutes();
905
+ console.log('[ConfigMigration] Removed deprecated route-level tool settings');
906
+ }
907
+ });
908
+ }
846
909
  // Vendor operations
847
910
  getVendors() {
848
911
  return [...this.vendors].sort((a, b) => {
@@ -861,7 +924,7 @@ class FileSystemDatabaseManager {
861
924
  console.log('[数据库] 创建供应商,输入数据:', JSON.stringify(vendor, null, 2));
862
925
  const id = crypto_1.default.randomUUID();
863
926
  const now = Date.now();
864
- const newVendor = Object.assign(Object.assign({}, vendor), { id, services: vendor.services || [], createdAt: now, updatedAt: now });
927
+ const newVendor = Object.assign(Object.assign({}, vendor), { apiKey: typeof vendor.apiKey === 'string' ? vendor.apiKey : '', id, services: vendor.services || [], createdAt: now, updatedAt: now });
865
928
  console.log('[数据库] 创建供应商,返回数据:', JSON.stringify(newVendor, null, 2));
866
929
  this.vendors.push(newVendor);
867
930
  yield this.saveVendors();
@@ -874,7 +937,11 @@ class FileSystemDatabaseManager {
874
937
  if (index === -1)
875
938
  return false;
876
939
  const now = Date.now();
877
- this.vendors[index] = Object.assign(Object.assign(Object.assign({}, this.vendors[index]), vendor), { id, services: vendor.services !== undefined ? vendor.services : this.vendors[index].services, updatedAt: now });
940
+ this.vendors[index] = Object.assign(Object.assign(Object.assign({}, this.vendors[index]), vendor), { id, apiKey: typeof vendor.apiKey === 'string'
941
+ ? vendor.apiKey
942
+ : (this.vendors[index].apiKey || ''),
943
+ // 供应商服务应通过 create/update/deleteAPIService 单独维护,避免编辑供应商时误覆盖
944
+ services: this.vendors[index].services, updatedAt: now });
878
945
  yield this.saveVendors();
879
946
  return true;
880
947
  });
@@ -884,12 +951,15 @@ class FileSystemDatabaseManager {
884
951
  const index = this.vendors.findIndex(v => v.id === id);
885
952
  if (index === -1)
886
953
  return false;
887
- // 检查是否有服务被规则使用
954
+ // 级联删除:删除该供应商下服务关联的所有规则
888
955
  const vendor = this.vendors[index];
889
956
  const serviceIds = (vendor.services || []).map(s => s.id);
890
- const rulesUsingServices = this.rules.filter(r => serviceIds.includes(r.targetServiceId));
891
- if (rulesUsingServices.length > 0) {
892
- throw new Error(`无法删除供应商:有 ${rulesUsingServices.length} 个路由规则正在使用该供应商的服务`);
957
+ if (serviceIds.length > 0) {
958
+ const beforeCount = this.rules.length;
959
+ this.rules = this.rules.filter(r => !serviceIds.includes(r.targetServiceId));
960
+ if (this.rules.length !== beforeCount) {
961
+ yield this.saveRules();
962
+ }
893
963
  }
894
964
  this.vendors.splice(index, 1);
895
965
  yield this.saveVendors();
@@ -954,7 +1024,7 @@ class FileSystemDatabaseManager {
954
1024
  const _a = service, { vendorId: _ } = _a, serviceData = __rest(_a, ["vendorId"]);
955
1025
  const id = crypto_1.default.randomUUID();
956
1026
  const now = Date.now();
957
- const newService = Object.assign(Object.assign({}, serviceData), { id, createdAt: now, updatedAt: now });
1027
+ const newService = Object.assign(Object.assign({}, serviceData), { apiKey: typeof serviceData.apiKey === 'string' ? serviceData.apiKey : '', inheritVendorApiKey: serviceData.inheritVendorApiKey === true, id, createdAt: now, updatedAt: now });
958
1028
  console.log('[数据库] 创建服务,最终数据:', JSON.stringify(newService, null, 2));
959
1029
  if (!vendor.services) {
960
1030
  vendor.services = [];
@@ -977,7 +1047,9 @@ class FileSystemDatabaseManager {
977
1047
  if (index === -1)
978
1048
  return false;
979
1049
  const now = Date.now();
980
- vendor.services[index] = Object.assign(Object.assign(Object.assign({}, vendor.services[index]), service), { id, updatedAt: now });
1050
+ vendor.services[index] = Object.assign(Object.assign(Object.assign({}, vendor.services[index]), service), { id, apiKey: typeof service.apiKey === 'string' ? service.apiKey : (vendor.services[index].apiKey || ''), inheritVendorApiKey: service.inheritVendorApiKey !== undefined
1051
+ ? service.inheritVendorApiKey === true
1052
+ : vendor.services[index].inheritVendorApiKey === true, updatedAt: now });
981
1053
  // 更新供应商的 updatedAt 时间
982
1054
  vendor.updatedAt = now;
983
1055
  yield this.saveVendors();
@@ -995,10 +1067,11 @@ class FileSystemDatabaseManager {
995
1067
  const index = vendor.services.findIndex(s => s.id === id);
996
1068
  if (index === -1)
997
1069
  return false;
998
- // 检查是否有规则正在使用此服务
999
- const rulesUsingService = this.rules.filter(r => r.targetServiceId === id);
1000
- if (rulesUsingService.length > 0) {
1001
- throw new Error(`无法删除服务:有 ${rulesUsingService.length} 个路由规则正在使用此服务`);
1070
+ // 级联删除:删除使用该服务的所有规则
1071
+ const beforeCount = this.rules.length;
1072
+ this.rules = this.rules.filter(r => r.targetServiceId !== id);
1073
+ if (this.rules.length !== beforeCount) {
1074
+ yield this.saveRules();
1002
1075
  }
1003
1076
  vendor.services.splice(index, 1);
1004
1077
  // 更新供应商的 updatedAt 时间
@@ -1602,7 +1675,56 @@ class FileSystemDatabaseManager {
1602
1675
  }
1603
1676
  return false;
1604
1677
  }
1678
+ /**
1679
+ * 获取状态码为 499 的请求日志
1680
+ * @param limit 返回数量限制
1681
+ * @param offset 偏移量
1682
+ * @returns 匹配的请求日志列表
1683
+ */
1684
+ getClientClosedLogs() {
1685
+ return __awaiter(this, arguments, void 0, function* (limit = 100, offset = 0) {
1686
+ const allMatches = [];
1687
+ const sortedShards = [...this.logShardsIndex].sort((a, b) => b.endTime - a.endTime);
1688
+ // 递序遍历所有分片, collect 499 logs
1689
+ for (const shard of sortedShards) {
1690
+ const shardLogs = yield this.loadLogShard(shard.filename);
1691
+ for (const log of shardLogs) {
1692
+ if (log.statusCode === 499) {
1693
+ allMatches.push(log);
1694
+ }
1695
+ }
1696
+ }
1697
+ // 按时间倒序排列并分页
1698
+ return allMatches
1699
+ .sort((a, b) => b.timestamp - a.timestamp)
1700
+ .slice(offset, offset + limit);
1701
+ });
1702
+ }
1703
+ /**
1704
+ * 获取状态码为 499 的请求日志数量
1705
+ * @returns 匹配的请求数量
1706
+ */
1707
+ getClientClosedLogsCount() {
1708
+ return __awaiter(this, void 0, void 0, function* () {
1709
+ let count = 0;
1710
+ for (const shard of this.logShardsIndex) {
1711
+ const shardLogs = yield this.loadLogShard(shard.filename);
1712
+ for (const log of shardLogs) {
1713
+ if (log.statusCode === 499) {
1714
+ count++;
1715
+ }
1716
+ }
1717
+ }
1718
+ return count;
1719
+ });
1720
+ }
1721
+ // Service blacklist operations
1605
1722
  // Service blacklist operations
1723
+ getFailoverRecoveryMs() {
1724
+ var _a;
1725
+ const seconds = normalizeFailoverRecoverySeconds((_a = this.config) === null || _a === void 0 ? void 0 : _a.failoverRecoverySeconds);
1726
+ return seconds * 1000;
1727
+ }
1606
1728
  isServiceBlacklisted(serviceId, routeId, contentType) {
1607
1729
  return __awaiter(this, void 0, void 0, function* () {
1608
1730
  const key = `${routeId}:${contentType}:${serviceId}`;
@@ -1621,10 +1743,11 @@ class FileSystemDatabaseManager {
1621
1743
  return __awaiter(this, void 0, void 0, function* () {
1622
1744
  const key = `${routeId}:${contentType}:${serviceId}`;
1623
1745
  const now = Date.now();
1746
+ const recoveryMs = this.getFailoverRecoveryMs();
1624
1747
  const existing = this.blacklist.get(key);
1625
1748
  if (existing) {
1626
1749
  existing.blacklistedAt = now;
1627
- existing.expiresAt = now + 2 * 60 * 1000; // 2分钟黑名单(从10分钟缩短)
1750
+ existing.expiresAt = now + recoveryMs;
1628
1751
  existing.errorCount++;
1629
1752
  existing.lastError = errorMessage;
1630
1753
  existing.lastStatusCode = statusCode;
@@ -1636,7 +1759,7 @@ class FileSystemDatabaseManager {
1636
1759
  routeId,
1637
1760
  contentType,
1638
1761
  blacklistedAt: now,
1639
- expiresAt: now + 2 * 60 * 1000, // 2分钟黑名单(从10分钟缩短)
1762
+ expiresAt: now + recoveryMs,
1640
1763
  errorCount: 1,
1641
1764
  lastError: errorMessage,
1642
1765
  lastStatusCode: statusCode,
@@ -1675,7 +1798,12 @@ class FileSystemDatabaseManager {
1675
1798
  }
1676
1799
  updateConfig(config) {
1677
1800
  return __awaiter(this, void 0, void 0, function* () {
1678
- this.config = config;
1801
+ const merged = Object.assign(Object.assign({}, (this.config || {})), config);
1802
+ if (!isCodexReasoningEffort(merged.codexModelReasoningEffort)) {
1803
+ merged.codexModelReasoningEffort = DEFAULT_CODEX_REASONING_EFFORT;
1804
+ }
1805
+ merged.failoverRecoverySeconds = normalizeFailoverRecoverySeconds(merged.failoverRecoverySeconds);
1806
+ this.config = merged;
1679
1807
  yield this.saveConfig();
1680
1808
  return true;
1681
1809
  });
@@ -1693,6 +1821,9 @@ class FileSystemDatabaseManager {
1693
1821
  if (!vendor.name || typeof vendor.name !== 'string') {
1694
1822
  return { valid: false, error: `供应商[${index}](${vendor.id}) 缺少有效的 name 字段` };
1695
1823
  }
1824
+ if (vendor.apiKey !== undefined && typeof vendor.apiKey !== 'string') {
1825
+ return { valid: false, error: `供应商[${index}](${vendor.id}) 的 apiKey 必须是字符串` };
1826
+ }
1696
1827
  if (!Array.isArray(vendor.services)) {
1697
1828
  return { valid: false, error: `供应商[${index}](${vendor.id}) 的 services 不是数组` };
1698
1829
  }
@@ -1711,7 +1842,12 @@ class FileSystemDatabaseManager {
1711
1842
  return { valid: false, error: `供应商[${index}](${vendor.id}) 的服务[${i}] 缺少有效的 apiUrl 字段` };
1712
1843
  }
1713
1844
  if (!service.apiKey || typeof service.apiKey !== 'string') {
1714
- return { valid: false, error: `供应商[${index}](${vendor.id}) 的服务[${i}] 缺少有效的 apiKey 字段` };
1845
+ if (service.inheritVendorApiKey !== true) {
1846
+ return { valid: false, error: `供应商[${index}](${vendor.id}) 的服务[${i}] 缺少有效的 apiKey 字段` };
1847
+ }
1848
+ }
1849
+ if (service.inheritVendorApiKey !== undefined && typeof service.inheritVendorApiKey !== 'boolean') {
1850
+ return { valid: false, error: `供应商[${index}](${vendor.id}) 的服务[${i}] inheritVendorApiKey 必须是布尔值` };
1715
1851
  }
1716
1852
  }
1717
1853
  return { valid: true };
@@ -1898,6 +2034,7 @@ class FileSystemDatabaseManager {
1898
2034
  }
1899
2035
  importData(encryptedData, password) {
1900
2036
  return __awaiter(this, void 0, void 0, function* () {
2037
+ var _a;
1901
2038
  try {
1902
2039
  // 解密
1903
2040
  let jsonData;
@@ -1933,7 +2070,7 @@ class FileSystemDatabaseManager {
1933
2070
  this.vendors = importData.vendors.map((v) => (Object.assign(Object.assign({}, v), { updatedAt: now })));
1934
2071
  this.routes = importData.routes.map((r) => (Object.assign(Object.assign({}, r), { updatedAt: now })));
1935
2072
  this.rules = importData.rules.map((r) => (Object.assign(Object.assign({}, r), { updatedAt: now })));
1936
- this.config = Object.assign(Object.assign({}, importData.config), { updatedAt: now });
2073
+ this.config = Object.assign(Object.assign({}, importData.config), { failoverRecoverySeconds: normalizeFailoverRecoverySeconds((_a = importData.config) === null || _a === void 0 ? void 0 : _a.failoverRecoverySeconds), updatedAt: now });
1937
2074
  // 保存数据
1938
2075
  yield Promise.all([
1939
2076
  this.saveVendors(),