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.
@@ -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,6 +222,8 @@ class FileSystemDatabaseManager {
209
222
  yield this.loadAllData();
210
223
  // 执行数据源类型迁移(在加载数据之后)
211
224
  yield this.migrateSourceTypes();
225
+ // 路由级工具配置迁移到全局配置(兼容旧版本)
226
+ yield this.migrateRouteToolSettingsToGlobalConfig();
212
227
  // 确保默认配置
213
228
  yield this.ensureDefaultConfig();
214
229
  });
@@ -231,15 +246,37 @@ class FileSystemDatabaseManager {
231
246
  }
232
247
  loadVendors() {
233
248
  return __awaiter(this, void 0, void 0, function* () {
249
+ let needSave = false;
234
250
  try {
235
251
  const data = yield promises_1.default.readFile(this.vendorsFile, 'utf-8');
236
- 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
+ }) : [];
237
271
  }
238
272
  catch (_a) {
239
273
  this.vendors = [];
240
274
  }
241
275
  // 兼容性检查:如果存在旧的 services.json,自动迁移
242
276
  yield this.migrateServicesIfNeeded();
277
+ if (needSave) {
278
+ yield this.saveVendors();
279
+ }
243
280
  });
244
281
  }
245
282
  /**
@@ -364,7 +401,9 @@ class FileSystemDatabaseManager {
364
401
  migrateVendorsOnImport(vendors) {
365
402
  return vendors.map(vendor => {
366
403
  var _a;
367
- return (Object.assign(Object.assign({}, vendor), { services: (_a = vendor.services) === null || _a === void 0 ? void 0 : _a.map(service => (Object.assign(Object.assign({}, service), { sourceType: service.sourceType ? (0, type_migration_1.normalizeSourceType)(service.sourceType) : undefined }))) }));
404
+ return (Object.assign(Object.assign({}, vendor), { services: (_a = vendor.services) === null || _a === void 0 ? void 0 : _a.map(service => {
405
+ return Object.assign(Object.assign({}, service), { sourceType: service.sourceType ? (0, type_migration_1.normalizeSourceType)(service.sourceType) : undefined });
406
+ }) }));
368
407
  });
369
408
  }
370
409
  saveVendors() {
@@ -784,22 +823,89 @@ class FileSystemDatabaseManager {
784
823
  }
785
824
  ensureDefaultConfig() {
786
825
  return __awaiter(this, void 0, void 0, function* () {
787
- if (!this.config) {
788
- this.config = {
789
- enableLogging: true,
790
- logRetentionDays: 30,
791
- maxLogSize: 100000,
792
- apiKey: '',
793
- enableFailover: true,
794
- proxyEnabled: false,
795
- proxyUrl: '',
796
- proxyUsername: '',
797
- proxyPassword: '',
798
- };
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)) {
799
847
  yield this.saveConfig();
800
848
  }
801
849
  });
802
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
+ }
803
909
  // Vendor operations
804
910
  getVendors() {
805
911
  return [...this.vendors].sort((a, b) => {
@@ -818,7 +924,7 @@ class FileSystemDatabaseManager {
818
924
  console.log('[数据库] 创建供应商,输入数据:', JSON.stringify(vendor, null, 2));
819
925
  const id = crypto_1.default.randomUUID();
820
926
  const now = Date.now();
821
- 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 });
822
928
  console.log('[数据库] 创建供应商,返回数据:', JSON.stringify(newVendor, null, 2));
823
929
  this.vendors.push(newVendor);
824
930
  yield this.saveVendors();
@@ -831,7 +937,11 @@ class FileSystemDatabaseManager {
831
937
  if (index === -1)
832
938
  return false;
833
939
  const now = Date.now();
834
- 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 });
835
945
  yield this.saveVendors();
836
946
  return true;
837
947
  });
@@ -841,12 +951,15 @@ class FileSystemDatabaseManager {
841
951
  const index = this.vendors.findIndex(v => v.id === id);
842
952
  if (index === -1)
843
953
  return false;
844
- // 检查是否有服务被规则使用
954
+ // 级联删除:删除该供应商下服务关联的所有规则
845
955
  const vendor = this.vendors[index];
846
956
  const serviceIds = (vendor.services || []).map(s => s.id);
847
- const rulesUsingServices = this.rules.filter(r => serviceIds.includes(r.targetServiceId));
848
- if (rulesUsingServices.length > 0) {
849
- 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
+ }
850
963
  }
851
964
  this.vendors.splice(index, 1);
852
965
  yield this.saveVendors();
@@ -911,7 +1024,7 @@ class FileSystemDatabaseManager {
911
1024
  const _a = service, { vendorId: _ } = _a, serviceData = __rest(_a, ["vendorId"]);
912
1025
  const id = crypto_1.default.randomUUID();
913
1026
  const now = Date.now();
914
- 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 });
915
1028
  console.log('[数据库] 创建服务,最终数据:', JSON.stringify(newService, null, 2));
916
1029
  if (!vendor.services) {
917
1030
  vendor.services = [];
@@ -934,7 +1047,9 @@ class FileSystemDatabaseManager {
934
1047
  if (index === -1)
935
1048
  return false;
936
1049
  const now = Date.now();
937
- 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 });
938
1053
  // 更新供应商的 updatedAt 时间
939
1054
  vendor.updatedAt = now;
940
1055
  yield this.saveVendors();
@@ -952,10 +1067,11 @@ class FileSystemDatabaseManager {
952
1067
  const index = vendor.services.findIndex(s => s.id === id);
953
1068
  if (index === -1)
954
1069
  return false;
955
- // 检查是否有规则正在使用此服务
956
- const rulesUsingService = this.rules.filter(r => r.targetServiceId === id);
957
- if (rulesUsingService.length > 0) {
958
- 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();
959
1075
  }
960
1076
  vendor.services.splice(index, 1);
961
1077
  // 更新供应商的 updatedAt 时间
@@ -1559,7 +1675,56 @@ class FileSystemDatabaseManager {
1559
1675
  }
1560
1676
  return false;
1561
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
+ }
1562
1721
  // Service blacklist operations
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
+ }
1563
1728
  isServiceBlacklisted(serviceId, routeId, contentType) {
1564
1729
  return __awaiter(this, void 0, void 0, function* () {
1565
1730
  const key = `${routeId}:${contentType}:${serviceId}`;
@@ -1578,10 +1743,11 @@ class FileSystemDatabaseManager {
1578
1743
  return __awaiter(this, void 0, void 0, function* () {
1579
1744
  const key = `${routeId}:${contentType}:${serviceId}`;
1580
1745
  const now = Date.now();
1746
+ const recoveryMs = this.getFailoverRecoveryMs();
1581
1747
  const existing = this.blacklist.get(key);
1582
1748
  if (existing) {
1583
1749
  existing.blacklistedAt = now;
1584
- existing.expiresAt = now + 2 * 60 * 1000; // 2分钟黑名单(从10分钟缩短)
1750
+ existing.expiresAt = now + recoveryMs;
1585
1751
  existing.errorCount++;
1586
1752
  existing.lastError = errorMessage;
1587
1753
  existing.lastStatusCode = statusCode;
@@ -1593,7 +1759,7 @@ class FileSystemDatabaseManager {
1593
1759
  routeId,
1594
1760
  contentType,
1595
1761
  blacklistedAt: now,
1596
- expiresAt: now + 2 * 60 * 1000, // 2分钟黑名单(从10分钟缩短)
1762
+ expiresAt: now + recoveryMs,
1597
1763
  errorCount: 1,
1598
1764
  lastError: errorMessage,
1599
1765
  lastStatusCode: statusCode,
@@ -1632,7 +1798,12 @@ class FileSystemDatabaseManager {
1632
1798
  }
1633
1799
  updateConfig(config) {
1634
1800
  return __awaiter(this, void 0, void 0, function* () {
1635
- 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;
1636
1807
  yield this.saveConfig();
1637
1808
  return true;
1638
1809
  });
@@ -1650,6 +1821,9 @@ class FileSystemDatabaseManager {
1650
1821
  if (!vendor.name || typeof vendor.name !== 'string') {
1651
1822
  return { valid: false, error: `供应商[${index}](${vendor.id}) 缺少有效的 name 字段` };
1652
1823
  }
1824
+ if (vendor.apiKey !== undefined && typeof vendor.apiKey !== 'string') {
1825
+ return { valid: false, error: `供应商[${index}](${vendor.id}) 的 apiKey 必须是字符串` };
1826
+ }
1653
1827
  if (!Array.isArray(vendor.services)) {
1654
1828
  return { valid: false, error: `供应商[${index}](${vendor.id}) 的 services 不是数组` };
1655
1829
  }
@@ -1668,7 +1842,12 @@ class FileSystemDatabaseManager {
1668
1842
  return { valid: false, error: `供应商[${index}](${vendor.id}) 的服务[${i}] 缺少有效的 apiUrl 字段` };
1669
1843
  }
1670
1844
  if (!service.apiKey || typeof service.apiKey !== 'string') {
1671
- 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 必须是布尔值` };
1672
1851
  }
1673
1852
  }
1674
1853
  return { valid: true };
@@ -1855,6 +2034,7 @@ class FileSystemDatabaseManager {
1855
2034
  }
1856
2035
  importData(encryptedData, password) {
1857
2036
  return __awaiter(this, void 0, void 0, function* () {
2037
+ var _a;
1858
2038
  try {
1859
2039
  // 解密
1860
2040
  let jsonData;
@@ -1890,7 +2070,7 @@ class FileSystemDatabaseManager {
1890
2070
  this.vendors = importData.vendors.map((v) => (Object.assign(Object.assign({}, v), { updatedAt: now })));
1891
2071
  this.routes = importData.routes.map((r) => (Object.assign(Object.assign({}, r), { updatedAt: now })));
1892
2072
  this.rules = importData.rules.map((r) => (Object.assign(Object.assign({}, r), { updatedAt: now })));
1893
- 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 });
1894
2074
  // 保存数据
1895
2075
  yield Promise.all([
1896
2076
  this.saveVendors(),