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.
- 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 +209 -72
- package/dist/server/main.js +365 -283
- package/dist/server/original-config-reader.js +154 -88
- package/dist/server/proxy-server.js +993 -387
- 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-BqSYpNgU.js +0 -511
package/dist/server/main.js
CHANGED
|
@@ -30,6 +30,8 @@ const websocket_service_1 = require("./websocket-service");
|
|
|
30
30
|
const rules_status_service_1 = require("./rules-status-service");
|
|
31
31
|
const type_migration_1 = require("./type-migration");
|
|
32
32
|
const config_metadata_1 = require("./config-metadata");
|
|
33
|
+
const config_merge_1 = require("./config-merge");
|
|
34
|
+
const config_managed_fields_1 = require("./config-managed-fields");
|
|
33
35
|
const config_1 = require("./config");
|
|
34
36
|
const appDir = path_1.default.join(os_1.default.homedir(), '.aicodeswitch');
|
|
35
37
|
const legacyDataDir = path_1.default.join(appDir, 'data');
|
|
@@ -104,32 +106,13 @@ const asyncHandler = (handler) => (req, res, next) => {
|
|
|
104
106
|
next(err);
|
|
105
107
|
});
|
|
106
108
|
};
|
|
107
|
-
const
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
if (typeof service.apiUrl !== 'string') {
|
|
113
|
-
return null;
|
|
114
|
-
}
|
|
115
|
-
if (!OPENAI_V1_SUFFIX_RE.test(service.apiUrl.trim())) {
|
|
116
|
-
return null;
|
|
117
|
-
}
|
|
118
|
-
return 'OpenAI 数据源请填写不包含 /v1 的 base URL,例如:https://api.openai.com';
|
|
109
|
+
const VALID_CLAUDE_EFFORT_LEVELS = ['low', 'medium', 'high', 'max'];
|
|
110
|
+
const DEFAULT_CLAUDE_EFFORT_LEVEL = 'medium';
|
|
111
|
+
const isClaudeEffortLevel = (value) => {
|
|
112
|
+
return typeof value === 'string' && VALID_CLAUDE_EFFORT_LEVELS.includes(value);
|
|
119
113
|
};
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
return null;
|
|
123
|
-
}
|
|
124
|
-
for (const service of vendorBody.services) {
|
|
125
|
-
const error = validateOpenAIServiceBaseUrl(service);
|
|
126
|
-
if (error) {
|
|
127
|
-
return error;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
return null;
|
|
131
|
-
};
|
|
132
|
-
const writeClaudeConfig = (dbManager, enableAgentTeams, enableBypassPermissionsSupport) => __awaiter(void 0, void 0, void 0, function* () {
|
|
114
|
+
const writeClaudeConfig = (dbManager_1, enableAgentTeams_1, enableBypassPermissionsSupport_1, effortLevel_1, defaultModel_1, ...args_1) => __awaiter(void 0, [dbManager_1, enableAgentTeams_1, enableBypassPermissionsSupport_1, effortLevel_1, defaultModel_1, ...args_1], void 0, function* (dbManager, enableAgentTeams, enableBypassPermissionsSupport, effortLevel, defaultModel, options = {}) {
|
|
115
|
+
var _a;
|
|
133
116
|
try {
|
|
134
117
|
const homeDir = os_1.default.homedir();
|
|
135
118
|
const port = process.env.PORT ? parseInt(process.env.PORT, 10) : 4567;
|
|
@@ -141,29 +124,44 @@ const writeClaudeConfig = (dbManager, enableAgentTeams, enableBypassPermissionsS
|
|
|
141
124
|
const claudeJsonBakPath = path_1.default.join(homeDir, '.claude.json.aicodeswitch_backup');
|
|
142
125
|
// 使用新的配置状态检测来判断是否可以写入
|
|
143
126
|
const configStatus = (0, config_metadata_1.checkClaudeConfigStatus)();
|
|
127
|
+
const isRuntimeRefresh = options.allowOverwriteRefresh === true && configStatus.isOverwritten;
|
|
144
128
|
// 只有当当前配置已经是代理配置时,才拒绝写入
|
|
145
|
-
if (configStatus.isOverwritten) {
|
|
129
|
+
if (configStatus.isOverwritten && !isRuntimeRefresh) {
|
|
146
130
|
console.error('Claude config has already been overwritten. Please restore the original config first.');
|
|
147
131
|
return false;
|
|
148
132
|
}
|
|
149
133
|
// 如果 .aicodeswitch_backup 文件不存在,才进行备份(避免覆盖已有备份)
|
|
150
|
-
let originalSettingsHash =
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
//
|
|
156
|
-
fs_1.default.
|
|
134
|
+
let originalSettingsHash = isRuntimeRefresh
|
|
135
|
+
? (_a = configStatus.metadata) === null || _a === void 0 ? void 0 : _a.originalHash
|
|
136
|
+
: undefined;
|
|
137
|
+
if (!isRuntimeRefresh) {
|
|
138
|
+
if (!fs_1.default.existsSync(claudeSettingsBakPath)) {
|
|
139
|
+
// 计算原始配置文件的 hash(如果存在)
|
|
140
|
+
if (fs_1.default.existsSync(claudeSettingsPath)) {
|
|
141
|
+
originalSettingsHash = (0, crypto_1.createHash)('sha256').update(fs_1.default.readFileSync(claudeSettingsPath, 'utf-8')).digest('hex');
|
|
142
|
+
// 备份当前配置文件
|
|
143
|
+
fs_1.default.renameSync(claudeSettingsPath, claudeSettingsBakPath);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
// .aicodeswitch_backup 已存在,直接使用现有的备份文件
|
|
148
|
+
console.log('Backup file already exists, skipping backup step');
|
|
157
149
|
}
|
|
158
|
-
}
|
|
159
|
-
else {
|
|
160
|
-
// .aicodeswitch_backup 已存在,直接使用现有的备份文件
|
|
161
|
-
console.log('Backup file already exists, skipping backup step');
|
|
162
150
|
}
|
|
163
151
|
if (!fs_1.default.existsSync(claudeDir)) {
|
|
164
152
|
fs_1.default.mkdirSync(claudeDir, { recursive: true });
|
|
165
153
|
}
|
|
166
|
-
//
|
|
154
|
+
// 读取当前配置(如果存在),保留工具运行时写入的内容
|
|
155
|
+
let currentSettings = {};
|
|
156
|
+
if (fs_1.default.existsSync(claudeSettingsPath)) {
|
|
157
|
+
try {
|
|
158
|
+
currentSettings = JSON.parse(fs_1.default.readFileSync(claudeSettingsPath, 'utf-8'));
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
console.warn('Failed to parse current settings.json, using empty object:', error);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// 构建代理配置
|
|
167
165
|
const claudeSettingsEnv = {
|
|
168
166
|
ANTHROPIC_AUTH_TOKEN: config.apiKey || "api_key",
|
|
169
167
|
ANTHROPIC_API_KEY: "",
|
|
@@ -175,32 +173,56 @@ const writeClaudeConfig = (dbManager, enableAgentTeams, enableBypassPermissionsS
|
|
|
175
173
|
if (enableAgentTeams) {
|
|
176
174
|
claudeSettingsEnv.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS = "1";
|
|
177
175
|
}
|
|
178
|
-
const
|
|
176
|
+
const proxySettings = {
|
|
179
177
|
env: claudeSettingsEnv
|
|
180
178
|
};
|
|
181
179
|
// 如果开启对bypassPermissions的支持,添加对应的配置项
|
|
182
180
|
if (enableBypassPermissionsSupport) {
|
|
183
|
-
|
|
181
|
+
proxySettings.permissions = {
|
|
184
182
|
defaultMode: "bypassPermissions"
|
|
185
183
|
};
|
|
186
|
-
|
|
184
|
+
proxySettings.skipDangerousModePermissionPrompt = true;
|
|
185
|
+
}
|
|
186
|
+
// 如果设置了 effortLevel,添加对应的配置项
|
|
187
|
+
if (effortLevel && isClaudeEffortLevel(effortLevel)) {
|
|
188
|
+
proxySettings.effortLevel = effortLevel;
|
|
187
189
|
}
|
|
188
|
-
|
|
190
|
+
// 如果设置了默认模型,添加对应的配置项
|
|
191
|
+
if (defaultModel && typeof defaultModel === 'string' && defaultModel.trim()) {
|
|
192
|
+
proxySettings.model = defaultModel.trim();
|
|
193
|
+
}
|
|
194
|
+
// 使用智能合并:将代理配置的管理字段写入,保留当前配置的非管理字段
|
|
195
|
+
const mergedSettings = (0, config_merge_1.mergeJsonConfig)(proxySettings, currentSettings, config_managed_fields_1.CLAUDE_SETTINGS_MANAGED_FIELDS);
|
|
196
|
+
// 原子性写入合并后的配置
|
|
197
|
+
(0, config_merge_1.atomicWriteFile)(claudeSettingsPath, JSON.stringify(mergedSettings, null, 2));
|
|
189
198
|
// Claude Code .claude.json
|
|
190
199
|
const claudeJsonPath = path_1.default.join(homeDir, '.claude.json');
|
|
191
|
-
//
|
|
192
|
-
let
|
|
200
|
+
// 读取当前配置(如果存在),保留工具运行时写入的内容
|
|
201
|
+
let currentClaudeJson = {};
|
|
193
202
|
if (fs_1.default.existsSync(claudeJsonPath)) {
|
|
194
|
-
|
|
203
|
+
try {
|
|
204
|
+
currentClaudeJson = JSON.parse(fs_1.default.readFileSync(claudeJsonPath, 'utf-8'));
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
console.warn('Failed to parse current .claude.json, using empty object:', error);
|
|
208
|
+
}
|
|
195
209
|
}
|
|
196
210
|
// 然后处理备份
|
|
197
|
-
if (!
|
|
198
|
-
if (fs_1.default.existsSync(
|
|
199
|
-
fs_1.default.
|
|
211
|
+
if (!isRuntimeRefresh) {
|
|
212
|
+
if (!fs_1.default.existsSync(claudeJsonBakPath)) {
|
|
213
|
+
if (fs_1.default.existsSync(claudeJsonPath)) {
|
|
214
|
+
fs_1.default.renameSync(claudeJsonPath, claudeJsonBakPath);
|
|
215
|
+
}
|
|
200
216
|
}
|
|
201
217
|
}
|
|
202
|
-
|
|
203
|
-
|
|
218
|
+
// 构建代理配置
|
|
219
|
+
const proxyClaudeJson = {
|
|
220
|
+
hasCompletedOnboarding: true
|
|
221
|
+
};
|
|
222
|
+
// 使用智能合并
|
|
223
|
+
const mergedClaudeJson = (0, config_merge_1.mergeJsonConfig)(proxyClaudeJson, currentClaudeJson, config_managed_fields_1.CLAUDE_JSON_MANAGED_FIELDS);
|
|
224
|
+
// 原子性写入合并后的配置
|
|
225
|
+
(0, config_merge_1.atomicWriteFile)(claudeJsonPath, JSON.stringify(mergedClaudeJson, null, 2));
|
|
204
226
|
// 保存元数据
|
|
205
227
|
const currentSettingsHash = (0, crypto_1.createHash)('sha256').update(fs_1.default.readFileSync(claudeSettingsPath, 'utf-8')).digest('hex');
|
|
206
228
|
const metadata = {
|
|
@@ -228,125 +250,13 @@ const writeClaudeConfig = (dbManager, enableAgentTeams, enableBypassPermissionsS
|
|
|
228
250
|
return false;
|
|
229
251
|
}
|
|
230
252
|
});
|
|
231
|
-
|
|
232
|
-
* 更新Claude Code配置中的Agent Teams设置
|
|
233
|
-
* 此函数假设配置文件已经被代理覆盖,直接修改环境变量而不重新备份
|
|
234
|
-
*/
|
|
235
|
-
const updateClaudeAgentTeamsConfig = (enableAgentTeams) => __awaiter(void 0, void 0, void 0, function* () {
|
|
236
|
-
try {
|
|
237
|
-
const homeDir = os_1.default.homedir();
|
|
238
|
-
const claudeSettingsPath = path_1.default.join(homeDir, '.claude/settings.json');
|
|
239
|
-
// 检查配置文件是否存在
|
|
240
|
-
if (!fs_1.default.existsSync(claudeSettingsPath)) {
|
|
241
|
-
console.error('Claude settings.json does not exist');
|
|
242
|
-
return false;
|
|
243
|
-
}
|
|
244
|
-
// 读取当前配置
|
|
245
|
-
const currentContent = fs_1.default.readFileSync(claudeSettingsPath, 'utf-8');
|
|
246
|
-
const currentConfig = JSON.parse(currentContent);
|
|
247
|
-
// 检查是否是代理配置
|
|
248
|
-
const configStatus = (0, config_metadata_1.checkClaudeConfigStatus)();
|
|
249
|
-
if (!configStatus.isOverwritten) {
|
|
250
|
-
console.error('Claude config is not overwritten by proxy. Please activate a route first.');
|
|
251
|
-
return false;
|
|
252
|
-
}
|
|
253
|
-
// 更新或删除Agent Teams环境变量
|
|
254
|
-
if (enableAgentTeams) {
|
|
255
|
-
currentConfig.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS = "1";
|
|
256
|
-
}
|
|
257
|
-
else {
|
|
258
|
-
delete currentConfig.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS;
|
|
259
|
-
}
|
|
260
|
-
// 写入更新后的配置
|
|
261
|
-
fs_1.default.writeFileSync(claudeSettingsPath, JSON.stringify(currentConfig, null, 2));
|
|
262
|
-
// 更新元数据中的当前配置hash
|
|
263
|
-
const metadata = (0, config_metadata_1.loadMetadata)('claude');
|
|
264
|
-
if (metadata && metadata.files[0]) {
|
|
265
|
-
metadata.files[0].currentHash = (0, crypto_1.createHash)('sha256')
|
|
266
|
-
.update(fs_1.default.readFileSync(claudeSettingsPath, 'utf-8'))
|
|
267
|
-
.digest('hex');
|
|
268
|
-
metadata.timestamp = Date.now();
|
|
269
|
-
(0, config_metadata_1.saveMetadata)(metadata);
|
|
270
|
-
}
|
|
271
|
-
return true;
|
|
272
|
-
}
|
|
273
|
-
catch (error) {
|
|
274
|
-
console.error('Failed to update Claude Agent Teams config:', error);
|
|
275
|
-
return false;
|
|
276
|
-
}
|
|
277
|
-
});
|
|
278
|
-
/**
|
|
279
|
-
* 更新Claude Code配置中的bypassPermissions支持设置
|
|
280
|
-
* 此函数假设配置文件已经被代理覆盖,直接修改配置而不重新备份
|
|
281
|
-
*/
|
|
282
|
-
const updateClaudeBypassPermissionsSupportConfig = (enableBypassPermissionsSupport) => __awaiter(void 0, void 0, void 0, function* () {
|
|
283
|
-
try {
|
|
284
|
-
const homeDir = os_1.default.homedir();
|
|
285
|
-
const claudeSettingsPath = path_1.default.join(homeDir, '.claude/settings.json');
|
|
286
|
-
// 检查配置文件是否存在
|
|
287
|
-
if (!fs_1.default.existsSync(claudeSettingsPath)) {
|
|
288
|
-
console.error('Claude settings.json does not exist');
|
|
289
|
-
return false;
|
|
290
|
-
}
|
|
291
|
-
// 读取当前配置
|
|
292
|
-
const currentContent = fs_1.default.readFileSync(claudeSettingsPath, 'utf-8');
|
|
293
|
-
const currentConfig = JSON.parse(currentContent);
|
|
294
|
-
// 检查是否是代理配置
|
|
295
|
-
const configStatus = (0, config_metadata_1.checkClaudeConfigStatus)();
|
|
296
|
-
if (!configStatus.isOverwritten) {
|
|
297
|
-
console.error('Claude config is not overwritten by proxy. Please activate a route first.');
|
|
298
|
-
return false;
|
|
299
|
-
}
|
|
300
|
-
// 更新或删除bypassPermissions支持配置项
|
|
301
|
-
if (enableBypassPermissionsSupport) {
|
|
302
|
-
currentConfig.permissions = {
|
|
303
|
-
defaultMode: "bypassPermissions"
|
|
304
|
-
};
|
|
305
|
-
currentConfig.skipDangerousModePermissionPrompt = true;
|
|
306
|
-
}
|
|
307
|
-
else {
|
|
308
|
-
delete currentConfig.permissions;
|
|
309
|
-
delete currentConfig.skipDangerousModePermissionPrompt;
|
|
310
|
-
}
|
|
311
|
-
// 写入更新后的配置
|
|
312
|
-
fs_1.default.writeFileSync(claudeSettingsPath, JSON.stringify(currentConfig, null, 2));
|
|
313
|
-
// 更新元数据中的当前配置hash
|
|
314
|
-
const metadata = (0, config_metadata_1.loadMetadata)('claude');
|
|
315
|
-
if (metadata && metadata.files[0]) {
|
|
316
|
-
metadata.files[0].currentHash = (0, crypto_1.createHash)('sha256')
|
|
317
|
-
.update(fs_1.default.readFileSync(claudeSettingsPath, 'utf-8'))
|
|
318
|
-
.digest('hex');
|
|
319
|
-
metadata.timestamp = Date.now();
|
|
320
|
-
(0, config_metadata_1.saveMetadata)(metadata);
|
|
321
|
-
}
|
|
322
|
-
return true;
|
|
323
|
-
}
|
|
324
|
-
catch (error) {
|
|
325
|
-
console.error('Failed to update Claude bypassPermissions support config:', error);
|
|
326
|
-
return false;
|
|
327
|
-
}
|
|
328
|
-
});
|
|
329
|
-
const VALID_CODEX_REASONING_EFFORTS = ['low', 'medium', 'high'];
|
|
253
|
+
const VALID_CODEX_REASONING_EFFORTS = ['low', 'medium', 'high', 'xhigh'];
|
|
330
254
|
const DEFAULT_CODEX_REASONING_EFFORT = 'high';
|
|
331
255
|
const isCodexReasoningEffort = (value) => {
|
|
332
256
|
return typeof value === 'string' && VALID_CODEX_REASONING_EFFORTS.includes(value);
|
|
333
257
|
};
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
return `model_provider = "aicodeswitch"
|
|
337
|
-
model = "gpt-5.1-codex"
|
|
338
|
-
model_reasoning_effort = "${modelReasoningEffort}"
|
|
339
|
-
disable_response_storage = true
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
[model_providers.aicodeswitch]
|
|
343
|
-
name = "aicodeswitch"
|
|
344
|
-
base_url = "http://${host}:${localPort}/codex"
|
|
345
|
-
wire_api = "responses"
|
|
346
|
-
requires_openai_auth = true
|
|
347
|
-
`;
|
|
348
|
-
};
|
|
349
|
-
const writeCodexConfig = (dbManager_1, ...args_1) => __awaiter(void 0, [dbManager_1, ...args_1], void 0, function* (dbManager, modelReasoningEffort = DEFAULT_CODEX_REASONING_EFFORT) {
|
|
258
|
+
const writeCodexConfig = (dbManager_1, ...args_1) => __awaiter(void 0, [dbManager_1, ...args_1], void 0, function* (dbManager, modelReasoningEffort = DEFAULT_CODEX_REASONING_EFFORT, codexDefaultModel, options = {}) {
|
|
259
|
+
var _a;
|
|
350
260
|
try {
|
|
351
261
|
const homeDir = os_1.default.homedir();
|
|
352
262
|
const config = dbManager.getConfig();
|
|
@@ -357,41 +267,92 @@ const writeCodexConfig = (dbManager_1, ...args_1) => __awaiter(void 0, [dbManage
|
|
|
357
267
|
const codexAuthBakPath = path_1.default.join(codexDir, 'auth.json.aicodeswitch_backup');
|
|
358
268
|
// 使用新的配置状态检测来判断是否可以写入
|
|
359
269
|
const configStatus = (0, config_metadata_1.checkCodexConfigStatus)();
|
|
270
|
+
const isRuntimeRefresh = options.allowOverwriteRefresh === true && configStatus.isOverwritten;
|
|
360
271
|
// 只有当当前配置已经是代理配置时,才拒绝写入
|
|
361
|
-
if (configStatus.isOverwritten) {
|
|
272
|
+
if (configStatus.isOverwritten && !isRuntimeRefresh) {
|
|
362
273
|
console.error('Codex config has already been overwritten. Please restore the original config first.');
|
|
363
274
|
return false;
|
|
364
275
|
}
|
|
365
276
|
// 如果 .aicodeswitch_backup 文件不存在,才进行备份(避免覆盖已有备份)
|
|
366
|
-
let originalConfigHash =
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
//
|
|
372
|
-
fs_1.default.
|
|
277
|
+
let originalConfigHash = isRuntimeRefresh
|
|
278
|
+
? (_a = configStatus.metadata) === null || _a === void 0 ? void 0 : _a.originalHash
|
|
279
|
+
: undefined;
|
|
280
|
+
if (!isRuntimeRefresh) {
|
|
281
|
+
if (!fs_1.default.existsSync(codexConfigBakPath)) {
|
|
282
|
+
// 计算原始配置文件的 hash(如果存在)
|
|
283
|
+
if (fs_1.default.existsSync(codexConfigPath)) {
|
|
284
|
+
originalConfigHash = (0, crypto_1.createHash)('sha256').update(fs_1.default.readFileSync(codexConfigPath, 'utf-8')).digest('hex');
|
|
285
|
+
// 备份当前配置文件
|
|
286
|
+
fs_1.default.renameSync(codexConfigPath, codexConfigBakPath);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
// .aicodeswitch_backup 已存在,直接使用现有的备份文件
|
|
291
|
+
console.log('Backup file already exists, skipping backup step');
|
|
373
292
|
}
|
|
374
|
-
}
|
|
375
|
-
else {
|
|
376
|
-
// .aicodeswitch_backup 已存在,直接使用现有的备份文件
|
|
377
|
-
console.log('Backup file already exists, skipping backup step');
|
|
378
293
|
}
|
|
379
294
|
if (!fs_1.default.existsSync(codexDir)) {
|
|
380
295
|
fs_1.default.mkdirSync(codexDir, { recursive: true });
|
|
381
296
|
}
|
|
382
|
-
|
|
297
|
+
// 读取当前配置(如果存在),保留工具运行时写入的内容
|
|
298
|
+
let currentConfig = {};
|
|
299
|
+
if (fs_1.default.existsSync(codexConfigPath)) {
|
|
300
|
+
try {
|
|
301
|
+
currentConfig = (0, config_merge_1.parseToml)(fs_1.default.readFileSync(codexConfigPath, 'utf-8'));
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
console.warn('Failed to parse current config.toml, using empty object:', error);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
// 构建代理配置
|
|
308
|
+
const proxyConfig = {
|
|
309
|
+
model_provider: "aicodeswitch",
|
|
310
|
+
model: codexDefaultModel || "gpt-5.3-codex", // 使用配置的默认模型,否则使用默认值
|
|
311
|
+
model_reasoning_effort: modelReasoningEffort,
|
|
312
|
+
disable_response_storage: true,
|
|
313
|
+
preferred_auth_method: "apikey",
|
|
314
|
+
requires_openai_auth: true,
|
|
315
|
+
enableRouteSelection: true,
|
|
316
|
+
model_providers: {
|
|
317
|
+
aicodeswitch: {
|
|
318
|
+
name: "aicodeswitch",
|
|
319
|
+
base_url: `http://${host}:${process.env.PORT ? parseInt(process.env.PORT, 10) : 4567}/codex`,
|
|
320
|
+
wire_api: "responses"
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
// 使用智能合并
|
|
325
|
+
const mergedConfig = (0, config_merge_1.mergeTomlConfig)(proxyConfig, currentConfig, config_managed_fields_1.CODEX_CONFIG_MANAGED_FIELDS);
|
|
326
|
+
// 原子性写入合并后的配置
|
|
327
|
+
(0, config_merge_1.atomicWriteFile)(codexConfigPath, (0, config_merge_1.stringifyToml)(mergedConfig));
|
|
383
328
|
// Codex auth.json
|
|
384
329
|
const codexAuthPath = path_1.default.join(codexDir, 'auth.json');
|
|
330
|
+
// 读取当前配置(如果存在),保留工具运行时写入的内容
|
|
331
|
+
let currentAuth = {};
|
|
332
|
+
if (fs_1.default.existsSync(codexAuthPath)) {
|
|
333
|
+
try {
|
|
334
|
+
currentAuth = JSON.parse(fs_1.default.readFileSync(codexAuthPath, 'utf-8'));
|
|
335
|
+
}
|
|
336
|
+
catch (error) {
|
|
337
|
+
console.warn('Failed to parse current auth.json, using empty object:', error);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
385
340
|
// 同样处理 auth.json 的备份
|
|
386
|
-
if (!
|
|
387
|
-
if (fs_1.default.existsSync(
|
|
388
|
-
fs_1.default.
|
|
341
|
+
if (!isRuntimeRefresh) {
|
|
342
|
+
if (!fs_1.default.existsSync(codexAuthBakPath)) {
|
|
343
|
+
if (fs_1.default.existsSync(codexAuthPath)) {
|
|
344
|
+
fs_1.default.renameSync(codexAuthPath, codexAuthBakPath);
|
|
345
|
+
}
|
|
389
346
|
}
|
|
390
347
|
}
|
|
391
|
-
|
|
348
|
+
// 构建代理配置
|
|
349
|
+
const proxyAuth = {
|
|
392
350
|
OPENAI_API_KEY: config.apiKey || "api_key"
|
|
393
351
|
};
|
|
394
|
-
|
|
352
|
+
// 使用智能合并
|
|
353
|
+
const mergedAuth = (0, config_merge_1.mergeJsonConfig)(proxyAuth, currentAuth, config_managed_fields_1.CODEX_AUTH_MANAGED_FIELDS);
|
|
354
|
+
// 原子性写入合并后的配置
|
|
355
|
+
(0, config_merge_1.atomicWriteFile)(codexAuthPath, JSON.stringify(mergedAuth, null, 2));
|
|
395
356
|
// 保存元数据
|
|
396
357
|
const currentConfigHash = (0, crypto_1.createHash)('sha256').update(fs_1.default.readFileSync(codexConfigPath, 'utf-8')).digest('hex');
|
|
397
358
|
const metadata = {
|
|
@@ -419,60 +380,62 @@ const writeCodexConfig = (dbManager_1, ...args_1) => __awaiter(void 0, [dbManage
|
|
|
419
380
|
return false;
|
|
420
381
|
}
|
|
421
382
|
});
|
|
422
|
-
const updateCodexReasoningEffortConfig = (modelReasoningEffort) => __awaiter(void 0, void 0, void 0, function* () {
|
|
423
|
-
try {
|
|
424
|
-
const homeDir = os_1.default.homedir();
|
|
425
|
-
const codexConfigPath = path_1.default.join(homeDir, '.codex/config.toml');
|
|
426
|
-
if (!fs_1.default.existsSync(codexConfigPath)) {
|
|
427
|
-
console.error('Codex config.toml does not exist');
|
|
428
|
-
return false;
|
|
429
|
-
}
|
|
430
|
-
const configStatus = (0, config_metadata_1.checkCodexConfigStatus)();
|
|
431
|
-
if (!configStatus.isOverwritten) {
|
|
432
|
-
console.error('Codex config is not overwritten by proxy. Please activate a route first.');
|
|
433
|
-
return false;
|
|
434
|
-
}
|
|
435
|
-
fs_1.default.writeFileSync(codexConfigPath, buildCodexConfigToml(modelReasoningEffort));
|
|
436
|
-
const metadata = (0, config_metadata_1.loadMetadata)('codex');
|
|
437
|
-
if (metadata && metadata.files[0]) {
|
|
438
|
-
metadata.files[0].currentHash = (0, crypto_1.createHash)('sha256')
|
|
439
|
-
.update(fs_1.default.readFileSync(codexConfigPath, 'utf-8'))
|
|
440
|
-
.digest('hex');
|
|
441
|
-
metadata.timestamp = Date.now();
|
|
442
|
-
(0, config_metadata_1.saveMetadata)(metadata);
|
|
443
|
-
}
|
|
444
|
-
return true;
|
|
445
|
-
}
|
|
446
|
-
catch (error) {
|
|
447
|
-
console.error('Failed to update Codex reasoning effort config:', error);
|
|
448
|
-
return false;
|
|
449
|
-
}
|
|
450
|
-
});
|
|
451
383
|
const restoreClaudeConfig = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
452
384
|
try {
|
|
453
385
|
const homeDir = os_1.default.homedir();
|
|
386
|
+
let restoredAnyFile = false;
|
|
454
387
|
// Restore Claude Code settings.json
|
|
455
388
|
const claudeDir = path_1.default.join(homeDir, '.claude');
|
|
456
389
|
const claudeSettingsPath = path_1.default.join(claudeDir, 'settings.json');
|
|
457
390
|
const claudeSettingsBakPath = path_1.default.join(claudeDir, 'settings.json.aicodeswitch_backup');
|
|
458
391
|
if (fs_1.default.existsSync(claudeSettingsBakPath)) {
|
|
392
|
+
// 读取备份配置
|
|
393
|
+
const backupSettings = JSON.parse(fs_1.default.readFileSync(claudeSettingsBakPath, 'utf-8'));
|
|
394
|
+
// 读取当前配置(可能包含工具运行时写入的新内容)
|
|
395
|
+
let currentSettings = {};
|
|
459
396
|
if (fs_1.default.existsSync(claudeSettingsPath)) {
|
|
460
|
-
|
|
397
|
+
try {
|
|
398
|
+
currentSettings = JSON.parse(fs_1.default.readFileSync(claudeSettingsPath, 'utf-8'));
|
|
399
|
+
}
|
|
400
|
+
catch (error) {
|
|
401
|
+
console.warn('Failed to parse current settings.json during restore, using empty object:', error);
|
|
402
|
+
}
|
|
461
403
|
}
|
|
462
|
-
|
|
404
|
+
// 生成合并后的配置(备份作为基础,合并当前的非管理字段)
|
|
405
|
+
const mergedSettings = (0, config_merge_1.mergeJsonConfig)(backupSettings, currentSettings, config_managed_fields_1.CLAUDE_SETTINGS_MANAGED_FIELDS);
|
|
406
|
+
// 原子性写入合并后的配置
|
|
407
|
+
(0, config_merge_1.atomicWriteFile)(claudeSettingsPath, JSON.stringify(mergedSettings, null, 2));
|
|
408
|
+
// 删除备份文件
|
|
409
|
+
fs_1.default.unlinkSync(claudeSettingsBakPath);
|
|
410
|
+
restoredAnyFile = true;
|
|
463
411
|
}
|
|
464
412
|
// Restore Claude Code .claude.json
|
|
465
413
|
const claudeJsonPath = path_1.default.join(homeDir, '.claude.json');
|
|
466
414
|
const claudeJsonBakPath = path_1.default.join(homeDir, '.claude.json.aicodeswitch_backup');
|
|
467
415
|
if (fs_1.default.existsSync(claudeJsonBakPath)) {
|
|
416
|
+
// 读取备份配置
|
|
417
|
+
const backupClaudeJson = JSON.parse(fs_1.default.readFileSync(claudeJsonBakPath, 'utf-8'));
|
|
418
|
+
// 读取当前配置(可能包含工具运行时写入的新内容)
|
|
419
|
+
let currentClaudeJson = {};
|
|
468
420
|
if (fs_1.default.existsSync(claudeJsonPath)) {
|
|
469
|
-
|
|
421
|
+
try {
|
|
422
|
+
currentClaudeJson = JSON.parse(fs_1.default.readFileSync(claudeJsonPath, 'utf-8'));
|
|
423
|
+
}
|
|
424
|
+
catch (error) {
|
|
425
|
+
console.warn('Failed to parse current .claude.json during restore, using empty object:', error);
|
|
426
|
+
}
|
|
470
427
|
}
|
|
471
|
-
|
|
428
|
+
// 生成合并后的配置
|
|
429
|
+
const mergedClaudeJson = (0, config_merge_1.mergeJsonConfig)(backupClaudeJson, currentClaudeJson, config_managed_fields_1.CLAUDE_JSON_MANAGED_FIELDS);
|
|
430
|
+
// 原子性写入合并后的配置
|
|
431
|
+
(0, config_merge_1.atomicWriteFile)(claudeJsonPath, JSON.stringify(mergedClaudeJson, null, 2));
|
|
432
|
+
// 删除备份文件
|
|
433
|
+
fs_1.default.unlinkSync(claudeJsonBakPath);
|
|
434
|
+
restoredAnyFile = true;
|
|
472
435
|
}
|
|
473
436
|
// 删除元数据
|
|
474
437
|
(0, config_metadata_1.deleteMetadata)('claude');
|
|
475
|
-
return
|
|
438
|
+
return restoredAnyFile;
|
|
476
439
|
}
|
|
477
440
|
catch (error) {
|
|
478
441
|
console.error('Failed to restore Claude config files:', error);
|
|
@@ -482,28 +445,59 @@ const restoreClaudeConfig = () => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
482
445
|
const restoreCodexConfig = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
483
446
|
try {
|
|
484
447
|
const homeDir = os_1.default.homedir();
|
|
448
|
+
let restoredAnyFile = false;
|
|
485
449
|
// Restore Codex config.toml
|
|
486
450
|
const codexDir = path_1.default.join(homeDir, '.codex');
|
|
487
451
|
const codexConfigPath = path_1.default.join(codexDir, 'config.toml');
|
|
488
452
|
const codexConfigBakPath = path_1.default.join(codexDir, 'config.toml.aicodeswitch_backup');
|
|
489
453
|
if (fs_1.default.existsSync(codexConfigBakPath)) {
|
|
454
|
+
// 读取备份配置
|
|
455
|
+
const backupConfig = (0, config_merge_1.parseToml)(fs_1.default.readFileSync(codexConfigBakPath, 'utf-8'));
|
|
456
|
+
// 读取当前配置(可能包含工具运行时写入的新内容)
|
|
457
|
+
let currentConfig = {};
|
|
490
458
|
if (fs_1.default.existsSync(codexConfigPath)) {
|
|
491
|
-
|
|
459
|
+
try {
|
|
460
|
+
currentConfig = (0, config_merge_1.parseToml)(fs_1.default.readFileSync(codexConfigPath, 'utf-8'));
|
|
461
|
+
}
|
|
462
|
+
catch (error) {
|
|
463
|
+
console.warn('Failed to parse current config.toml during restore, using empty object:', error);
|
|
464
|
+
}
|
|
492
465
|
}
|
|
493
|
-
|
|
466
|
+
// 生成合并后的配置(备份作为基础,合并当前的非管理字段)
|
|
467
|
+
const mergedConfig = (0, config_merge_1.mergeTomlConfig)(backupConfig, currentConfig, config_managed_fields_1.CODEX_CONFIG_MANAGED_FIELDS);
|
|
468
|
+
// 原子性写入合并后的配置
|
|
469
|
+
(0, config_merge_1.atomicWriteFile)(codexConfigPath, (0, config_merge_1.stringifyToml)(mergedConfig));
|
|
470
|
+
// 删除备份文件
|
|
471
|
+
fs_1.default.unlinkSync(codexConfigBakPath);
|
|
472
|
+
restoredAnyFile = true;
|
|
494
473
|
}
|
|
495
474
|
// Restore Codex auth.json
|
|
496
475
|
const codexAuthPath = path_1.default.join(codexDir, 'auth.json');
|
|
497
476
|
const codexAuthBakPath = path_1.default.join(codexDir, 'auth.json.aicodeswitch_backup');
|
|
498
477
|
if (fs_1.default.existsSync(codexAuthBakPath)) {
|
|
478
|
+
// 读取备份配置
|
|
479
|
+
const backupAuth = JSON.parse(fs_1.default.readFileSync(codexAuthBakPath, 'utf-8'));
|
|
480
|
+
// 读取当前配置(可能包含工具运行时写入的新内容)
|
|
481
|
+
let currentAuth = {};
|
|
499
482
|
if (fs_1.default.existsSync(codexAuthPath)) {
|
|
500
|
-
|
|
483
|
+
try {
|
|
484
|
+
currentAuth = JSON.parse(fs_1.default.readFileSync(codexAuthPath, 'utf-8'));
|
|
485
|
+
}
|
|
486
|
+
catch (error) {
|
|
487
|
+
console.warn('Failed to parse current auth.json during restore, using empty object:', error);
|
|
488
|
+
}
|
|
501
489
|
}
|
|
502
|
-
|
|
490
|
+
// 生成合并后的配置
|
|
491
|
+
const mergedAuth = (0, config_merge_1.mergeJsonConfig)(backupAuth, currentAuth, config_managed_fields_1.CODEX_AUTH_MANAGED_FIELDS);
|
|
492
|
+
// 原子性写入合并后的配置
|
|
493
|
+
(0, config_merge_1.atomicWriteFile)(codexAuthPath, JSON.stringify(mergedAuth, null, 2));
|
|
494
|
+
// 删除备份文件
|
|
495
|
+
fs_1.default.unlinkSync(codexAuthBakPath);
|
|
496
|
+
restoredAnyFile = true;
|
|
503
497
|
}
|
|
504
498
|
// 删除元数据
|
|
505
499
|
(0, config_metadata_1.deleteMetadata)('codex');
|
|
506
|
-
return
|
|
500
|
+
return restoredAnyFile;
|
|
507
501
|
}
|
|
508
502
|
catch (error) {
|
|
509
503
|
console.error('Failed to restore Codex config files:', error);
|
|
@@ -538,6 +532,33 @@ const checkCodexBackupExists = () => {
|
|
|
538
532
|
return false;
|
|
539
533
|
}
|
|
540
534
|
};
|
|
535
|
+
const syncConfigsOnServerStartup = (dbManager) => __awaiter(void 0, void 0, void 0, function* () {
|
|
536
|
+
const config = dbManager.getConfig();
|
|
537
|
+
// 服务启动即执行写入,参数来源改为全局配置
|
|
538
|
+
const claudeEffortLevel = isClaudeEffortLevel(config.claudeEffortLevel)
|
|
539
|
+
? config.claudeEffortLevel
|
|
540
|
+
: DEFAULT_CLAUDE_EFFORT_LEVEL;
|
|
541
|
+
const claudeWritten = yield writeClaudeConfig(dbManager, config.enableAgentTeams, config.enableBypassPermissionsSupport, claudeEffortLevel, config.claudeDefaultModel);
|
|
542
|
+
console.log(`[Startup Config Sync] Claude Code config ${claudeWritten ? 'written' : 'skipped'}`);
|
|
543
|
+
const modelReasoningEffort = isCodexReasoningEffort(config.codexModelReasoningEffort)
|
|
544
|
+
? config.codexModelReasoningEffort
|
|
545
|
+
: DEFAULT_CODEX_REASONING_EFFORT;
|
|
546
|
+
const codexWritten = yield writeCodexConfig(dbManager, modelReasoningEffort, config.codexDefaultModel);
|
|
547
|
+
console.log(`[Startup Config Sync] Codex config ${codexWritten ? 'written' : 'skipped'}`);
|
|
548
|
+
});
|
|
549
|
+
const syncConfigsOnGlobalConfigUpdate = (dbManager) => __awaiter(void 0, void 0, void 0, function* () {
|
|
550
|
+
const config = dbManager.getConfig();
|
|
551
|
+
const claudeEffortLevel = isClaudeEffortLevel(config.claudeEffortLevel)
|
|
552
|
+
? config.claudeEffortLevel
|
|
553
|
+
: DEFAULT_CLAUDE_EFFORT_LEVEL;
|
|
554
|
+
const claudeUpdated = yield writeClaudeConfig(dbManager, config.enableAgentTeams, config.enableBypassPermissionsSupport, claudeEffortLevel, config.claudeDefaultModel, { allowOverwriteRefresh: true });
|
|
555
|
+
console.log(`[Config Update Sync] Claude Code config ${claudeUpdated ? 'written' : 'skipped'}`);
|
|
556
|
+
const modelReasoningEffort = isCodexReasoningEffort(config.codexModelReasoningEffort)
|
|
557
|
+
? config.codexModelReasoningEffort
|
|
558
|
+
: DEFAULT_CODEX_REASONING_EFFORT;
|
|
559
|
+
const codexUpdated = yield writeCodexConfig(dbManager, modelReasoningEffort, config.codexDefaultModel, { allowOverwriteRefresh: true });
|
|
560
|
+
console.log(`[Config Update Sync] Codex config ${codexUpdated ? 'written' : 'skipped'}`);
|
|
561
|
+
});
|
|
541
562
|
const getCentralSkillsDir = () => {
|
|
542
563
|
return path_1.default.join(os_1.default.homedir(), '.aicodeswitch', 'skills');
|
|
543
564
|
};
|
|
@@ -928,19 +949,9 @@ const registerRoutes = (dbManager, proxyServer) => {
|
|
|
928
949
|
});
|
|
929
950
|
app.get('/api/vendors', (_req, res) => res.json(dbManager.getVendors()));
|
|
930
951
|
app.post('/api/vendors', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
931
|
-
const error = validateOpenAIServiceBaseUrlsInVendorPayload(req.body);
|
|
932
|
-
if (error) {
|
|
933
|
-
res.status(400).json({ error });
|
|
934
|
-
return;
|
|
935
|
-
}
|
|
936
952
|
res.json(yield dbManager.createVendor(req.body));
|
|
937
953
|
})));
|
|
938
954
|
app.put('/api/vendors/:id', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
939
|
-
const error = validateOpenAIServiceBaseUrlsInVendorPayload(req.body);
|
|
940
|
-
if (error) {
|
|
941
|
-
res.status(400).json({ error });
|
|
942
|
-
return;
|
|
943
|
-
}
|
|
944
955
|
res.json(yield dbManager.updateVendor(req.params.id, req.body));
|
|
945
956
|
})));
|
|
946
957
|
app.delete('/api/vendors/:id', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -960,11 +971,6 @@ const registerRoutes = (dbManager, proxyServer) => {
|
|
|
960
971
|
});
|
|
961
972
|
app.post('/api/services', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
962
973
|
console.log('[创建服务] 请求数据:', JSON.stringify(req.body, null, 2));
|
|
963
|
-
const error = validateOpenAIServiceBaseUrl(req.body);
|
|
964
|
-
if (error) {
|
|
965
|
-
res.status(400).json({ error });
|
|
966
|
-
return;
|
|
967
|
-
}
|
|
968
974
|
const result = yield dbManager.createAPIService(req.body);
|
|
969
975
|
console.log('[创建服务] 创建结果:', JSON.stringify(result, null, 2));
|
|
970
976
|
res.json(result);
|
|
@@ -975,12 +981,6 @@ const registerRoutes = (dbManager, proxyServer) => {
|
|
|
975
981
|
res.status(404).json({ error: '服务不存在' });
|
|
976
982
|
return;
|
|
977
983
|
}
|
|
978
|
-
const mergedService = Object.assign(Object.assign({}, existingService), req.body);
|
|
979
|
-
const error = validateOpenAIServiceBaseUrl(mergedService);
|
|
980
|
-
if (error) {
|
|
981
|
-
res.status(400).json({ error });
|
|
982
|
-
return;
|
|
983
|
-
}
|
|
984
984
|
res.json(yield dbManager.updateAPIService(req.params.id, req.body));
|
|
985
985
|
})));
|
|
986
986
|
app.delete('/api/services/:id', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -1027,26 +1027,8 @@ const registerRoutes = (dbManager, proxyServer) => {
|
|
|
1027
1027
|
})));
|
|
1028
1028
|
// 批量停用所有激活的路由(用于应用关闭时清理)
|
|
1029
1029
|
app.post('/api/routes/deactivate-all', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1030
|
-
console.log('[Deactivate All Routes] Starting
|
|
1031
|
-
//
|
|
1032
|
-
try {
|
|
1033
|
-
console.log('[Deactivate All Routes] Restoring Claude Code config...');
|
|
1034
|
-
const claudeRestored = yield restoreClaudeConfig();
|
|
1035
|
-
console.log(`[Deactivate All Routes] Claude Code config ${claudeRestored ? 'restored' : 'was not modified'}`);
|
|
1036
|
-
}
|
|
1037
|
-
catch (error) {
|
|
1038
|
-
console.error('[Deactivate All Routes] Failed to restore Claude config:', error);
|
|
1039
|
-
}
|
|
1040
|
-
// 步骤2:恢复 Codex 配置文件
|
|
1041
|
-
try {
|
|
1042
|
-
console.log('[Deactivate All Routes] Restoring Codex config...');
|
|
1043
|
-
const codexRestored = yield restoreCodexConfig();
|
|
1044
|
-
console.log(`[Deactivate All Routes] Codex config ${codexRestored ? 'restored' : 'was not modified'}`);
|
|
1045
|
-
}
|
|
1046
|
-
catch (error) {
|
|
1047
|
-
console.error('[Deactivate All Routes] Failed to restore Codex config:', error);
|
|
1048
|
-
}
|
|
1049
|
-
// 步骤3:停用所有激活的路由
|
|
1030
|
+
console.log('[Deactivate All Routes] Starting route deactivation...');
|
|
1031
|
+
// 仅停用路由,不再在路由接口内处理配置文件恢复
|
|
1050
1032
|
console.log('[Deactivate All Routes] Deactivating all active routes...');
|
|
1051
1033
|
const deactivatedCount = yield dbManager.deactivateAllRoutes();
|
|
1052
1034
|
if (deactivatedCount > 0) {
|
|
@@ -1057,7 +1039,7 @@ const registerRoutes = (dbManager, proxyServer) => {
|
|
|
1057
1039
|
else {
|
|
1058
1040
|
console.log('[Deactivate All Routes] No active routes to deactivate');
|
|
1059
1041
|
}
|
|
1060
|
-
console.log('[Deactivate All Routes]
|
|
1042
|
+
console.log('[Deactivate All Routes] Route deactivation completed');
|
|
1061
1043
|
res.json({
|
|
1062
1044
|
success: true,
|
|
1063
1045
|
deactivatedCount
|
|
@@ -1100,6 +1082,28 @@ const registerRoutes = (dbManager, proxyServer) => {
|
|
|
1100
1082
|
res.status(500).json({ error: 'Failed to clear blacklist' });
|
|
1101
1083
|
}
|
|
1102
1084
|
})));
|
|
1085
|
+
// 清除规则的错误状态(广播 idle 状态给所有客户端)
|
|
1086
|
+
app.post('/api/rules/:id/clear-status', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1087
|
+
const { id } = req.params;
|
|
1088
|
+
const rule = dbManager.getRule(id);
|
|
1089
|
+
if (!rule) {
|
|
1090
|
+
res.status(404).json({ error: 'Rule not found' });
|
|
1091
|
+
return;
|
|
1092
|
+
}
|
|
1093
|
+
// 找到该规则所属的路由
|
|
1094
|
+
const routes = dbManager.getRoutes();
|
|
1095
|
+
const route = routes.find(r => {
|
|
1096
|
+
const rules = dbManager.getRules(r.id);
|
|
1097
|
+
return rules.some(r => r.id === id);
|
|
1098
|
+
});
|
|
1099
|
+
if (!route) {
|
|
1100
|
+
res.status(404).json({ error: 'Route not found' });
|
|
1101
|
+
return;
|
|
1102
|
+
}
|
|
1103
|
+
// 广播 idle 状态给所有客户端
|
|
1104
|
+
rules_status_service_1.rulesStatusBroadcaster.markRuleIdle(route.id, id);
|
|
1105
|
+
res.json({ success: true });
|
|
1106
|
+
})));
|
|
1103
1107
|
// 获取规则的黑名单状态
|
|
1104
1108
|
app.get('/api/rules/:routeId/blacklist-status', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1105
1109
|
const { routeId } = req.params;
|
|
@@ -1189,8 +1193,10 @@ const registerRoutes = (dbManager, proxyServer) => {
|
|
|
1189
1193
|
const config = req.body;
|
|
1190
1194
|
const result = yield dbManager.updateConfig(config);
|
|
1191
1195
|
if (result) {
|
|
1192
|
-
|
|
1193
|
-
|
|
1196
|
+
const latestConfig = dbManager.getConfig();
|
|
1197
|
+
yield proxyServer.updateConfig(latestConfig);
|
|
1198
|
+
updateProxyConfig(latestConfig);
|
|
1199
|
+
yield syncConfigsOnGlobalConfigUpdate(dbManager);
|
|
1194
1200
|
}
|
|
1195
1201
|
res.json(result);
|
|
1196
1202
|
})));
|
|
@@ -1531,29 +1537,53 @@ ${instruction}
|
|
|
1531
1537
|
res.json({ success: true });
|
|
1532
1538
|
})));
|
|
1533
1539
|
app.post('/api/write-config/claude', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1534
|
-
const
|
|
1535
|
-
const
|
|
1536
|
-
const
|
|
1540
|
+
const appConfig = dbManager.getConfig();
|
|
1541
|
+
const requestedEnableAgentTeams = req.body.enableAgentTeams;
|
|
1542
|
+
const requestedBypass = req.body.enableBypassPermissionsSupport;
|
|
1543
|
+
const enableAgentTeams = typeof requestedEnableAgentTeams === 'boolean'
|
|
1544
|
+
? requestedEnableAgentTeams
|
|
1545
|
+
: appConfig.enableAgentTeams;
|
|
1546
|
+
const enableBypassPermissionsSupport = typeof requestedBypass === 'boolean'
|
|
1547
|
+
? requestedBypass
|
|
1548
|
+
: appConfig.enableBypassPermissionsSupport;
|
|
1549
|
+
const result = yield writeClaudeConfig(dbManager, enableAgentTeams, enableBypassPermissionsSupport, undefined, appConfig.claudeDefaultModel);
|
|
1537
1550
|
res.json(result);
|
|
1538
1551
|
})));
|
|
1539
1552
|
app.post('/api/write-config/codex', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1553
|
+
const appConfig = dbManager.getConfig();
|
|
1540
1554
|
const requestedEffort = req.body.modelReasoningEffort;
|
|
1541
1555
|
const modelReasoningEffort = isCodexReasoningEffort(requestedEffort)
|
|
1542
1556
|
? requestedEffort
|
|
1543
|
-
:
|
|
1544
|
-
|
|
1557
|
+
: isCodexReasoningEffort(appConfig.codexModelReasoningEffort)
|
|
1558
|
+
? appConfig.codexModelReasoningEffort
|
|
1559
|
+
: DEFAULT_CODEX_REASONING_EFFORT;
|
|
1560
|
+
const result = yield writeCodexConfig(dbManager, modelReasoningEffort, appConfig.codexDefaultModel);
|
|
1545
1561
|
res.json(result);
|
|
1546
1562
|
})));
|
|
1547
|
-
//
|
|
1563
|
+
// 兼容接口:更新全局 Agent Teams 配置
|
|
1548
1564
|
app.post('/api/update-claude-agent-teams', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1549
1565
|
const { enableAgentTeams } = req.body;
|
|
1550
|
-
const
|
|
1566
|
+
const current = dbManager.getConfig();
|
|
1567
|
+
const result = yield dbManager.updateConfig(Object.assign(Object.assign({}, current), { enableAgentTeams: !!enableAgentTeams }));
|
|
1568
|
+
if (result) {
|
|
1569
|
+
const latestConfig = dbManager.getConfig();
|
|
1570
|
+
yield proxyServer.updateConfig(latestConfig);
|
|
1571
|
+
updateProxyConfig(latestConfig);
|
|
1572
|
+
yield syncConfigsOnGlobalConfigUpdate(dbManager);
|
|
1573
|
+
}
|
|
1551
1574
|
res.json(result);
|
|
1552
1575
|
})));
|
|
1553
|
-
//
|
|
1576
|
+
// 兼容接口:更新全局 bypassPermissions 支持配置
|
|
1554
1577
|
app.post('/api/update-claude-bypass-permissions-support', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1555
1578
|
const { enableBypassPermissionsSupport } = req.body;
|
|
1556
|
-
const
|
|
1579
|
+
const current = dbManager.getConfig();
|
|
1580
|
+
const result = yield dbManager.updateConfig(Object.assign(Object.assign({}, current), { enableBypassPermissionsSupport: !!enableBypassPermissionsSupport }));
|
|
1581
|
+
if (result) {
|
|
1582
|
+
const latestConfig = dbManager.getConfig();
|
|
1583
|
+
yield proxyServer.updateConfig(latestConfig);
|
|
1584
|
+
updateProxyConfig(latestConfig);
|
|
1585
|
+
yield syncConfigsOnGlobalConfigUpdate(dbManager);
|
|
1586
|
+
}
|
|
1557
1587
|
res.json(result);
|
|
1558
1588
|
})));
|
|
1559
1589
|
app.post('/api/update-codex-reasoning-effort', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -1562,7 +1592,14 @@ ${instruction}
|
|
|
1562
1592
|
res.status(400).json({ error: 'Invalid modelReasoningEffort' });
|
|
1563
1593
|
return;
|
|
1564
1594
|
}
|
|
1565
|
-
const
|
|
1595
|
+
const current = dbManager.getConfig();
|
|
1596
|
+
const result = yield dbManager.updateConfig(Object.assign(Object.assign({}, current), { codexModelReasoningEffort: requestedEffort }));
|
|
1597
|
+
if (result) {
|
|
1598
|
+
const latestConfig = dbManager.getConfig();
|
|
1599
|
+
yield proxyServer.updateConfig(latestConfig);
|
|
1600
|
+
updateProxyConfig(latestConfig);
|
|
1601
|
+
yield syncConfigsOnGlobalConfigUpdate(dbManager);
|
|
1602
|
+
}
|
|
1566
1603
|
res.json(result);
|
|
1567
1604
|
})));
|
|
1568
1605
|
app.post('/api/restore-config/claude', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -1872,6 +1909,15 @@ const start = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
1872
1909
|
console.log('[Server] Initializing database...');
|
|
1873
1910
|
const dbManager = yield database_factory_1.DatabaseFactory.createAuto(dataDir, legacyDataDir);
|
|
1874
1911
|
console.log('[Server] Database initialized successfully');
|
|
1912
|
+
// 服务启动时自动同步配置文件(适用于 CLI 和 dev:server)
|
|
1913
|
+
console.log('[Server] Syncing tool configs with global settings...');
|
|
1914
|
+
try {
|
|
1915
|
+
yield syncConfigsOnServerStartup(dbManager);
|
|
1916
|
+
console.log('[Server] Tool config sync completed');
|
|
1917
|
+
}
|
|
1918
|
+
catch (error) {
|
|
1919
|
+
console.error('[Server] Tool config sync failed:', error);
|
|
1920
|
+
}
|
|
1875
1921
|
const proxyServer = new proxy_server_1.ProxyServer(dbManager, app);
|
|
1876
1922
|
// Initialize proxy server and register proxy routes last
|
|
1877
1923
|
proxyServer.initialize();
|
|
@@ -1902,6 +1948,12 @@ const start = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
1902
1948
|
const toolInstallWss = (0, websocket_service_1.createToolInstallationWSServer)();
|
|
1903
1949
|
// 创建 WebSocket 服务器用于规则状态
|
|
1904
1950
|
const rulesStatusWss = (0, rules_status_service_1.createRulesStatusWSServer)();
|
|
1951
|
+
// 设置黑名单检查函数,用于在规则状态同步时检查黑名单是否已过期
|
|
1952
|
+
rules_status_service_1.rulesStatusBroadcaster.setBlacklistChecker((serviceId, routeId, contentType) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1953
|
+
// 检查服务��否在黑名单中
|
|
1954
|
+
const isBlacklisted = yield dbManager.isServiceBlacklisted(serviceId, routeId, contentType);
|
|
1955
|
+
return isBlacklisted;
|
|
1956
|
+
}));
|
|
1905
1957
|
// 将 WebSocket 服务器附加到 HTTP 服务器
|
|
1906
1958
|
server.on('upgrade', (request, socket, head) => {
|
|
1907
1959
|
if (request.url === '/api/tools/install') {
|
|
@@ -1920,16 +1972,46 @@ const start = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
1920
1972
|
});
|
|
1921
1973
|
console.log(`WebSocket server for tool installation attached to ws://${host}:${port}/api/tools/install`);
|
|
1922
1974
|
console.log(`WebSocket server for rules status attached to ws://${host}:${port}/api/rules/status`);
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1975
|
+
let isShuttingDown = false;
|
|
1976
|
+
let shutdownPromise = null;
|
|
1977
|
+
const shutdown = (signal) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1978
|
+
if (isShuttingDown) {
|
|
1979
|
+
return shutdownPromise !== null && shutdownPromise !== void 0 ? shutdownPromise : Promise.resolve();
|
|
1980
|
+
}
|
|
1981
|
+
isShuttingDown = true;
|
|
1982
|
+
shutdownPromise = (() => __awaiter(void 0, void 0, void 0, function* () {
|
|
1983
|
+
console.log(`[Server] Received ${signal}, shutting down...`);
|
|
1984
|
+
// 服务终止前恢复配置文件(适用于 aicos stop 与 Ctrl+C)
|
|
1985
|
+
try {
|
|
1986
|
+
const claudeRestored = yield restoreClaudeConfig();
|
|
1987
|
+
console.log(`[Shutdown ...] Claude Code config ${claudeRestored ? 'restored' : 'was not modified'}`);
|
|
1988
|
+
}
|
|
1989
|
+
catch (error) {
|
|
1990
|
+
console.error('[Shutdown ...] Failed to restore Claude config:', error);
|
|
1991
|
+
}
|
|
1992
|
+
try {
|
|
1993
|
+
const codexRestored = yield restoreCodexConfig();
|
|
1994
|
+
console.log(`[Shutdown ...] Codex config ${codexRestored ? 'restored' : 'was not modified'}`);
|
|
1995
|
+
}
|
|
1996
|
+
catch (error) {
|
|
1997
|
+
console.error('[Shutdown ...] Failed to restore Codex config:', error);
|
|
1998
|
+
}
|
|
1999
|
+
dbManager.close();
|
|
2000
|
+
yield Promise.race([
|
|
2001
|
+
new Promise((resolve) => {
|
|
2002
|
+
server.close(() => resolve());
|
|
2003
|
+
}),
|
|
2004
|
+
new Promise((resolve) => {
|
|
2005
|
+
setTimeout(resolve, 5000);
|
|
2006
|
+
})
|
|
2007
|
+
]);
|
|
1927
2008
|
console.log('Server stopped.');
|
|
1928
2009
|
process.exit(0);
|
|
1929
|
-
});
|
|
2010
|
+
}))();
|
|
2011
|
+
return shutdownPromise;
|
|
1930
2012
|
});
|
|
1931
|
-
process.on('SIGINT', shutdown);
|
|
1932
|
-
process.on('SIGTERM', shutdown);
|
|
2013
|
+
process.on('SIGINT', () => { void shutdown('SIGINT'); });
|
|
2014
|
+
process.on('SIGTERM', () => { void shutdown('SIGTERM'); });
|
|
1933
2015
|
});
|
|
1934
2016
|
// 全局未捕获异常处理 - 防止服务崩溃
|
|
1935
2017
|
process.on('uncaughtException', (error) => {
|