@adversity/coding-tool-x 3.1.0 → 3.1.2
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/CHANGELOG.md +39 -18
- package/README.md +8 -8
- package/dist/web/assets/ConfigTemplates-Bidwfdf2.css +1 -0
- package/dist/web/assets/ConfigTemplates-DvcbKKdS.js +1 -0
- package/dist/web/assets/Home-BJKPCBuk.css +1 -0
- package/dist/web/assets/Home-Cw-F_Wnu.js +1 -0
- package/dist/web/assets/PluginManager-ROyoZ-6m.css +1 -0
- package/dist/web/assets/PluginManager-jy_4GVxI.js +1 -0
- package/dist/web/assets/ProjectList-C1fQb9OW.css +1 -0
- package/dist/web/assets/ProjectList-Df1-NcNr.js +1 -0
- package/dist/web/assets/SessionList-BGJWyneI.css +1 -0
- package/dist/web/assets/SessionList-UWcZtC2r.js +1 -0
- package/dist/web/assets/SkillManager-D7pd-d_P.css +1 -0
- package/dist/web/assets/SkillManager-IRdseMKB.js +1 -0
- package/dist/web/assets/Terminal-BasTyDut.js +1 -0
- package/dist/web/assets/Terminal-DGNJeVtc.css +1 -0
- package/dist/web/assets/WorkspaceManager-CrwgQgmP.css +1 -0
- package/dist/web/assets/WorkspaceManager-D-D2kK1V.js +1 -0
- package/dist/web/assets/icons-kcfLIMBB.js +1 -0
- package/dist/web/assets/index-CoB3zF0K.css +1 -0
- package/dist/web/assets/index-CryrSLv8.js +2 -0
- package/dist/web/assets/markdown-BfC0goYb.css +10 -0
- package/dist/web/assets/markdown-C9MYpaSi.js +1 -0
- package/dist/web/assets/naive-ui-CSrLusZZ.js +1 -0
- package/dist/web/assets/{vendors-D2HHw_aW.js → vendors-CO3Upi1d.js} +2 -2
- package/dist/web/assets/vue-vendor-DqyWIXEb.js +45 -0
- package/dist/web/assets/xterm-6GBZ9nXN.css +32 -0
- package/dist/web/assets/xterm-BJzAjXCH.js +13 -0
- package/dist/web/index.html +8 -6
- package/package.json +4 -2
- package/src/commands/channels.js +48 -1
- package/src/commands/cli-type.js +4 -2
- package/src/commands/daemon.js +81 -12
- package/src/commands/doctor.js +10 -9
- package/src/commands/list.js +1 -1
- package/src/commands/logs.js +6 -4
- package/src/commands/port-config.js +24 -4
- package/src/commands/proxy-control.js +12 -6
- package/src/commands/search.js +1 -1
- package/src/commands/security.js +3 -2
- package/src/commands/stats.js +226 -52
- package/src/commands/switch.js +1 -1
- package/src/commands/toggle-proxy.js +31 -6
- package/src/commands/update.js +97 -0
- package/src/commands/workspace.js +1 -1
- package/src/config/default.js +41 -2
- package/src/config/loader.js +74 -8
- package/src/config/model-metadata.js +415 -0
- package/src/config/model-pricing.js +23 -93
- package/src/config/paths.js +105 -33
- package/src/index.js +64 -3
- package/src/plugins/constants.js +3 -2
- package/src/plugins/plugin-api.js +1 -1
- package/src/reset-config.js +4 -2
- package/src/server/api/agents.js +57 -14
- package/src/server/api/channels.js +112 -33
- package/src/server/api/codex-channels.js +111 -18
- package/src/server/api/codex-proxy.js +14 -8
- package/src/server/api/commands.js +71 -18
- package/src/server/api/config-export.js +0 -6
- package/src/server/api/config-registry.js +11 -3
- package/src/server/api/config.js +376 -5
- package/src/server/api/convert.js +133 -0
- package/src/server/api/dashboard.js +22 -6
- package/src/server/api/gemini-channels.js +107 -18
- package/src/server/api/gemini-proxy.js +14 -8
- package/src/server/api/gemini-sessions.js +1 -1
- package/src/server/api/health-check.js +4 -3
- package/src/server/api/mcp.js +3 -3
- package/src/server/api/opencode-channels.js +497 -0
- package/src/server/api/opencode-projects.js +99 -0
- package/src/server/api/opencode-proxy.js +207 -0
- package/src/server/api/opencode-sessions.js +345 -0
- package/src/server/api/opencode-statistics.js +57 -0
- package/src/server/api/plugins.js +66 -19
- package/src/server/api/prompts.js +2 -2
- package/src/server/api/proxy.js +7 -4
- package/src/server/api/sessions.js +3 -0
- package/src/server/api/settings.js +111 -0
- package/src/server/api/skills.js +69 -18
- package/src/server/api/workspaces.js +78 -6
- package/src/server/codex-proxy-server.js +36 -22
- package/src/server/dev-server.js +1 -1
- package/src/server/gemini-proxy-server.js +21 -7
- package/src/server/index.js +174 -58
- package/src/server/opencode-proxy-server.js +5486 -0
- package/src/server/proxy-server.js +33 -22
- package/src/server/services/agents-service.js +61 -24
- package/src/server/services/channel-scheduler.js +9 -5
- package/src/server/services/channels.js +64 -37
- package/src/server/services/codex-channels.js +56 -43
- package/src/server/services/codex-sessions.js +105 -6
- package/src/server/services/codex-settings-manager.js +271 -49
- package/src/server/services/codex-statistics-service.js +2 -2
- package/src/server/services/commands-service.js +84 -25
- package/src/server/services/config-export-service.js +7 -45
- package/src/server/services/config-registry-service.js +63 -17
- package/src/server/services/config-sync-manager.js +160 -7
- package/src/server/services/config-templates-service.js +204 -51
- package/src/server/services/env-checker.js +50 -13
- package/src/server/services/env-manager.js +155 -19
- package/src/server/services/favorites.js +5 -3
- package/src/server/services/gemini-channels.js +33 -44
- package/src/server/services/gemini-statistics-service.js +2 -2
- package/src/server/services/mcp-service.js +350 -9
- package/src/server/services/model-detector.js +707 -221
- package/src/server/services/network-access.js +80 -0
- package/src/server/services/opencode-channels.js +208 -0
- package/src/server/services/opencode-gateway-converter.js +639 -0
- package/src/server/services/opencode-sessions.js +931 -0
- package/src/server/services/opencode-settings-manager.js +478 -0
- package/src/server/services/opencode-statistics-service.js +255 -0
- package/src/server/services/plugins-service.js +479 -22
- package/src/server/services/prompts-service.js +53 -11
- package/src/server/services/proxy-runtime.js +1 -1
- package/src/server/services/repo-scanner-base.js +1 -1
- package/src/server/services/response-decoder.js +21 -0
- package/src/server/services/security-config.js +1 -1
- package/src/server/services/session-cache.js +1 -1
- package/src/server/services/skill-service.js +300 -46
- package/src/server/services/speed-test.js +464 -186
- package/src/server/services/statistics-service.js +2 -2
- package/src/server/services/terminal-commands.js +10 -3
- package/src/server/services/terminal-config.js +1 -1
- package/src/server/services/ui-config.js +1 -1
- package/src/server/services/workspace-service.js +57 -100
- package/src/server/websocket-server.js +156 -8
- package/src/ui/menu.js +49 -40
- package/src/utils/port-helper.js +22 -8
- package/src/utils/session.js +5 -4
- package/dist/web/assets/icons-CO_2OFES.js +0 -1
- package/dist/web/assets/index-DI8QOi-E.js +0 -14
- package/dist/web/assets/index-uLHGdeZh.css +0 -41
- package/dist/web/assets/naive-ui-B1re3c-e.js +0 -1
- package/dist/web/assets/vue-vendor-6JaYHOiI.js +0 -44
- package/src/server/api/oauth.js +0 -294
- package/src/server/api/permissions.js +0 -385
- package/src/server/config/oauth-providers.js +0 -68
- package/src/server/services/oauth-callback-server.js +0 -284
- package/src/server/services/oauth-service.js +0 -378
- package/src/server/services/oauth-token-storage.js +0 -135
- package/src/server/services/permission-templates-service.js +0 -308
|
@@ -6,15 +6,77 @@
|
|
|
6
6
|
|
|
7
7
|
const fs = require('fs');
|
|
8
8
|
const path = require('path');
|
|
9
|
-
const
|
|
9
|
+
const { PATHS, ensureStorageDirMigrated } = require('../../config/paths');
|
|
10
10
|
|
|
11
11
|
// 备份目录
|
|
12
|
-
const BACKUP_DIR =
|
|
12
|
+
const BACKUP_DIR = PATHS.envBackups;
|
|
13
|
+
|
|
14
|
+
function ensureParentDir(filePath) {
|
|
15
|
+
const dirPath = path.dirname(filePath);
|
|
16
|
+
if (!fs.existsSync(dirPath)) {
|
|
17
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function writeFileAtomic(filePath, content) {
|
|
22
|
+
ensureParentDir(filePath);
|
|
23
|
+
const tempPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
fs.writeFileSync(tempPath, content, 'utf-8');
|
|
27
|
+
fs.renameSync(tempPath, filePath);
|
|
28
|
+
} finally {
|
|
29
|
+
if (fs.existsSync(tempPath)) {
|
|
30
|
+
try {
|
|
31
|
+
fs.unlinkSync(tempPath);
|
|
32
|
+
} catch (cleanupErr) {
|
|
33
|
+
// ignore cleanup errors
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function escapeRegex(value) {
|
|
40
|
+
return String(value || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isPowerShellProfile(filePath) {
|
|
44
|
+
return String(filePath || '').toLowerCase().endsWith('.ps1');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function matchesVarAssignment(line, varName, usePowerShell) {
|
|
48
|
+
if (!line || !varName) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const escaped = escapeRegex(varName);
|
|
53
|
+
const regex = usePowerShell
|
|
54
|
+
? new RegExp(`^\\s*\\$env:${escaped}\\s*=`, 'i')
|
|
55
|
+
: new RegExp(`^\\s*(?:export\\s+)?${escaped}=`);
|
|
56
|
+
return regex.test(line.trim());
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function formatExportLine(varName, value, usePowerShell) {
|
|
60
|
+
if (usePowerShell) {
|
|
61
|
+
const escapedValue = String(value ?? '')
|
|
62
|
+
.replace(/`/g, '``')
|
|
63
|
+
.replace(/"/g, '`"');
|
|
64
|
+
return `$env:${varName} = "${escapedValue}"`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const escapedValue = String(value ?? '')
|
|
68
|
+
.replace(/\\/g, '\\\\')
|
|
69
|
+
.replace(/"/g, '\\"')
|
|
70
|
+
.replace(/\$/g, '\\$')
|
|
71
|
+
.replace(/`/g, '\\`');
|
|
72
|
+
return `export ${varName}="${escapedValue}"`;
|
|
73
|
+
}
|
|
13
74
|
|
|
14
75
|
/**
|
|
15
76
|
* 确保备份目录存在
|
|
16
77
|
*/
|
|
17
78
|
function ensureBackupDir() {
|
|
79
|
+
ensureStorageDirMigrated();
|
|
18
80
|
if (!fs.existsSync(BACKUP_DIR)) {
|
|
19
81
|
fs.mkdirSync(BACKUP_DIR, { recursive: true });
|
|
20
82
|
}
|
|
@@ -62,11 +124,15 @@ function deleteEnvVars(conflicts) {
|
|
|
62
124
|
}
|
|
63
125
|
}
|
|
64
126
|
|
|
127
|
+
// 同步清理当前服务进程中的同名变量,避免“删除后立即复检仍提示冲突”
|
|
128
|
+
const clearedProcessVars = clearProcessEnvVars(conflicts);
|
|
129
|
+
|
|
65
130
|
return {
|
|
66
131
|
backupPath: backupInfo.backupPath,
|
|
67
132
|
timestamp: backupInfo.timestamp,
|
|
68
133
|
results,
|
|
69
|
-
processConflictsSkipped: processConflicts.length
|
|
134
|
+
processConflictsSkipped: processConflicts.length,
|
|
135
|
+
clearedProcessVars
|
|
70
136
|
};
|
|
71
137
|
}
|
|
72
138
|
|
|
@@ -112,9 +178,14 @@ function getOriginalValue(conflict) {
|
|
|
112
178
|
|
|
113
179
|
if (line) {
|
|
114
180
|
const match = line.match(/^(?:export\s+)?[A-Z_][A-Z0-9_]*=(.*)$/);
|
|
115
|
-
if (match) {
|
|
181
|
+
if (match && match[1] !== undefined) {
|
|
116
182
|
return cleanValue(match[1]);
|
|
117
183
|
}
|
|
184
|
+
|
|
185
|
+
const psMatch = line.match(/^\s*\$env:[A-Z_][A-Z0-9_]*\s*=\s*(.*)$/i);
|
|
186
|
+
if (psMatch && psMatch[1] !== undefined) {
|
|
187
|
+
return cleanValue(psMatch[1]);
|
|
188
|
+
}
|
|
118
189
|
}
|
|
119
190
|
} catch (err) {
|
|
120
191
|
// 忽略
|
|
@@ -153,6 +224,30 @@ function groupByFile(conflicts) {
|
|
|
153
224
|
return groups;
|
|
154
225
|
}
|
|
155
226
|
|
|
227
|
+
/**
|
|
228
|
+
* 清理当前 Node 进程中的环境变量
|
|
229
|
+
*/
|
|
230
|
+
function clearProcessEnvVars(conflicts) {
|
|
231
|
+
const names = new Set();
|
|
232
|
+
|
|
233
|
+
for (const conflict of conflicts || []) {
|
|
234
|
+
const varName = String(conflict?.varName || '').trim();
|
|
235
|
+
if (varName) {
|
|
236
|
+
names.add(varName);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const cleared = [];
|
|
241
|
+
for (const varName of names) {
|
|
242
|
+
if (Object.prototype.hasOwnProperty.call(process.env, varName)) {
|
|
243
|
+
delete process.env[varName];
|
|
244
|
+
cleared.push(varName);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return cleared;
|
|
249
|
+
}
|
|
250
|
+
|
|
156
251
|
/**
|
|
157
252
|
* 从文件中移除环境变量
|
|
158
253
|
*/
|
|
@@ -162,18 +257,44 @@ function removeVarsFromFile(filePath, vars) {
|
|
|
162
257
|
}
|
|
163
258
|
|
|
164
259
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
165
|
-
const lines = content.split(
|
|
260
|
+
const lines = content.split(/\r?\n/);
|
|
261
|
+
const usePowerShell = isPowerShellProfile(filePath);
|
|
262
|
+
|
|
263
|
+
// 收集要删除的行号(优先使用行号,行号不匹配时按变量名回退)
|
|
264
|
+
const lineNumbersToRemove = new Set();
|
|
265
|
+
for (const item of vars) {
|
|
266
|
+
const varName = String(item.varName || '').trim();
|
|
267
|
+
if (!varName) continue;
|
|
268
|
+
|
|
269
|
+
const lineIndex = Number(item.lineNumber) - 1;
|
|
270
|
+
if (
|
|
271
|
+
Number.isInteger(lineIndex) &&
|
|
272
|
+
lineIndex >= 0 &&
|
|
273
|
+
lineIndex < lines.length &&
|
|
274
|
+
matchesVarAssignment(lines[lineIndex], varName, usePowerShell)
|
|
275
|
+
) {
|
|
276
|
+
lineNumbersToRemove.add(lineIndex);
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
166
279
|
|
|
167
|
-
|
|
168
|
-
|
|
280
|
+
const fallbackIndex = lines.findIndex(line => matchesVarAssignment(line, varName, usePowerShell));
|
|
281
|
+
if (fallbackIndex >= 0) {
|
|
282
|
+
lineNumbersToRemove.add(fallbackIndex);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
169
285
|
|
|
170
|
-
|
|
171
|
-
|
|
286
|
+
if (lineNumbersToRemove.size === 0) {
|
|
287
|
+
console.log(`[EnvManager] No matching vars found in ${filePath}, skip writing`);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
172
290
|
|
|
173
|
-
|
|
174
|
-
|
|
291
|
+
const newLines = lines.filter((_, index) => !lineNumbersToRemove.has(index));
|
|
292
|
+
const nextContent = newLines.join('\n');
|
|
293
|
+
if (nextContent !== content) {
|
|
294
|
+
writeFileAtomic(filePath, nextContent);
|
|
295
|
+
}
|
|
175
296
|
|
|
176
|
-
console.log(`[EnvManager] Removed ${
|
|
297
|
+
console.log(`[EnvManager] Removed ${lineNumbersToRemove.size} var(s) from ${filePath}`);
|
|
177
298
|
}
|
|
178
299
|
|
|
179
300
|
/**
|
|
@@ -255,21 +376,36 @@ function restoreFromBackup(backupPath) {
|
|
|
255
376
|
* 恢复环境变量到文件
|
|
256
377
|
*/
|
|
257
378
|
function restoreVarToFile(filePath, varName, value) {
|
|
379
|
+
const usePowerShell = isPowerShellProfile(filePath);
|
|
380
|
+
const exportLine = formatExportLine(varName, value, usePowerShell);
|
|
258
381
|
let content = '';
|
|
259
382
|
|
|
260
383
|
if (fs.existsSync(filePath)) {
|
|
261
384
|
content = fs.readFileSync(filePath, 'utf-8');
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const lines = content ? content.split(/\r?\n/) : [];
|
|
388
|
+
let replaced = false;
|
|
389
|
+
|
|
390
|
+
for (let i = 0; i < lines.length; i++) {
|
|
391
|
+
if (matchesVarAssignment(lines[i], varName, usePowerShell)) {
|
|
392
|
+
lines[i] = exportLine;
|
|
393
|
+
replaced = true;
|
|
265
394
|
}
|
|
266
395
|
}
|
|
267
396
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
397
|
+
if (!replaced) {
|
|
398
|
+
while (lines.length > 0 && lines[lines.length - 1].trim() === '') {
|
|
399
|
+
lines.pop();
|
|
400
|
+
}
|
|
401
|
+
if (lines.length > 0) {
|
|
402
|
+
lines.push('');
|
|
403
|
+
}
|
|
404
|
+
lines.push(exportLine);
|
|
405
|
+
}
|
|
271
406
|
|
|
272
|
-
|
|
407
|
+
const nextContent = `${lines.join('\n')}\n`;
|
|
408
|
+
writeFileAtomic(filePath, nextContent);
|
|
273
409
|
|
|
274
410
|
console.log(`[EnvManager] Restored ${varName} to ${filePath}`);
|
|
275
411
|
}
|
|
@@ -2,7 +2,7 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const os = require('os');
|
|
4
4
|
|
|
5
|
-
const FAVORITES_DIR = path.join(os.homedir(), '.
|
|
5
|
+
const FAVORITES_DIR = path.join(os.homedir(), '.cc-tool');
|
|
6
6
|
const FAVORITES_FILE = path.join(FAVORITES_DIR, 'favorites.json');
|
|
7
7
|
|
|
8
8
|
// 内存缓存
|
|
@@ -12,7 +12,8 @@ let cacheInitialized = false;
|
|
|
12
12
|
const DEFAULT_FAVORITES = {
|
|
13
13
|
claude: [],
|
|
14
14
|
codex: [],
|
|
15
|
-
gemini: []
|
|
15
|
+
gemini: [],
|
|
16
|
+
opencode: []
|
|
16
17
|
};
|
|
17
18
|
|
|
18
19
|
// Ensure favorites directory exists
|
|
@@ -36,7 +37,8 @@ function readFavoritesFromFile() {
|
|
|
36
37
|
return {
|
|
37
38
|
claude: data.claude || [],
|
|
38
39
|
codex: data.codex || [],
|
|
39
|
-
gemini: data.gemini || []
|
|
40
|
+
gemini: data.gemini || [],
|
|
41
|
+
opencode: data.opencode || []
|
|
40
42
|
};
|
|
41
43
|
} catch (error) {
|
|
42
44
|
console.error('Error reading favorites file:', error);
|
|
@@ -16,6 +16,14 @@ const crypto = require('crypto');
|
|
|
16
16
|
* - 使用 weight 和 maxConcurrency 控制负载均衡
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
+
function normalizeGatewaySourceType(value, fallback = 'gemini') {
|
|
20
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
21
|
+
if (normalized === 'claude') return 'claude';
|
|
22
|
+
if (normalized === 'codex') return 'codex';
|
|
23
|
+
if (normalized === 'gemini') return 'gemini';
|
|
24
|
+
return fallback;
|
|
25
|
+
}
|
|
26
|
+
|
|
19
27
|
// 获取 Gemini 配置目录
|
|
20
28
|
function getGeminiDir() {
|
|
21
29
|
return path.join(os.homedir(), '.gemini');
|
|
@@ -23,7 +31,7 @@ function getGeminiDir() {
|
|
|
23
31
|
|
|
24
32
|
// 获取渠道存储文件路径
|
|
25
33
|
function getChannelsFilePath() {
|
|
26
|
-
const ccToolDir = path.join(os.homedir(), '.
|
|
34
|
+
const ccToolDir = path.join(os.homedir(), '.cc-tool');
|
|
27
35
|
if (!fs.existsSync(ccToolDir)) {
|
|
28
36
|
fs.mkdirSync(ccToolDir, { recursive: true });
|
|
29
37
|
}
|
|
@@ -66,15 +74,17 @@ function loadChannels() {
|
|
|
66
74
|
const data = JSON.parse(content);
|
|
67
75
|
// 确保渠道有 enabled 字段(兼容旧数据)
|
|
68
76
|
if (data.channels) {
|
|
69
|
-
data.channels = data.channels.map(ch =>
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
data.channels = data.channels.map(ch => {
|
|
78
|
+
return {
|
|
79
|
+
...ch,
|
|
80
|
+
enabled: ch.enabled !== false, // 默认启用
|
|
81
|
+
weight: ch.weight || 1,
|
|
82
|
+
maxConcurrency: ch.maxConcurrency || null,
|
|
83
|
+
modelRedirects: ch.modelRedirects || [],
|
|
84
|
+
speedTestModel: ch.speedTestModel || null,
|
|
85
|
+
gatewaySourceType: normalizeGatewaySourceType(ch.gatewaySourceType, 'gemini')
|
|
86
|
+
};
|
|
87
|
+
});
|
|
78
88
|
}
|
|
79
89
|
return data;
|
|
80
90
|
} catch (err) {
|
|
@@ -119,7 +129,7 @@ function initializeFromEnv() {
|
|
|
119
129
|
enabled: true,
|
|
120
130
|
weight: 1,
|
|
121
131
|
maxConcurrency: null,
|
|
122
|
-
|
|
132
|
+
gatewaySourceType: 'gemini',
|
|
123
133
|
createdAt: Date.now(),
|
|
124
134
|
updatedAt: Date.now()
|
|
125
135
|
};
|
|
@@ -177,9 +187,7 @@ function createChannel(name, baseUrl, apiKey, model = 'gemini-2.5-pro', extraCon
|
|
|
177
187
|
maxConcurrency: extraConfig.maxConcurrency || null,
|
|
178
188
|
modelRedirects: extraConfig.modelRedirects || [],
|
|
179
189
|
speedTestModel: extraConfig.speedTestModel || null,
|
|
180
|
-
|
|
181
|
-
oauthProvider: extraConfig.oauthProvider || null,
|
|
182
|
-
oauthTokenId: extraConfig.oauthTokenId || null,
|
|
190
|
+
gatewaySourceType: normalizeGatewaySourceType(extraConfig.gatewaySourceType, 'gemini'),
|
|
183
191
|
createdAt: Date.now(),
|
|
184
192
|
updatedAt: Date.now()
|
|
185
193
|
};
|
|
@@ -212,15 +220,17 @@ function updateChannel(channelId, updates) {
|
|
|
212
220
|
}
|
|
213
221
|
}
|
|
214
222
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
...
|
|
223
|
+
const merged = { ...oldChannel, ...updates };
|
|
224
|
+
const nextChannel = {
|
|
225
|
+
...merged,
|
|
218
226
|
id: channelId, // 保持 ID 不变
|
|
219
227
|
createdAt: oldChannel.createdAt, // 保持创建时间
|
|
220
228
|
modelRedirects: updates.modelRedirects !== undefined ? updates.modelRedirects : (oldChannel.modelRedirects || []),
|
|
221
229
|
speedTestModel: updates.speedTestModel !== undefined ? updates.speedTestModel : (oldChannel.speedTestModel || null),
|
|
230
|
+
gatewaySourceType: normalizeGatewaySourceType(merged.gatewaySourceType, 'gemini'),
|
|
222
231
|
updatedAt: Date.now()
|
|
223
232
|
};
|
|
233
|
+
data.channels[index] = nextChannel;
|
|
224
234
|
|
|
225
235
|
// Get proxy status
|
|
226
236
|
const { getGeminiProxyStatus } = require('../gemini-proxy-server');
|
|
@@ -284,8 +294,9 @@ function applyChannelToSettings(channelId, channels = null) {
|
|
|
284
294
|
const envPath = path.join(geminiDir, '.env');
|
|
285
295
|
|
|
286
296
|
// 构建 .env 内容
|
|
297
|
+
const effectiveApiKey = getEffectiveApiKey(channel) || '';
|
|
287
298
|
const envContent = `GOOGLE_GEMINI_BASE_URL=${channel.baseUrl}
|
|
288
|
-
GEMINI_API_KEY=${
|
|
299
|
+
GEMINI_API_KEY=${effectiveApiKey}
|
|
289
300
|
GEMINI_MODEL=${channel.model}
|
|
290
301
|
`;
|
|
291
302
|
|
|
@@ -320,7 +331,7 @@ GEMINI_MODEL=${channel.model}
|
|
|
320
331
|
}
|
|
321
332
|
|
|
322
333
|
// 删除渠道
|
|
323
|
-
function deleteChannel(channelId) {
|
|
334
|
+
async function deleteChannel(channelId) {
|
|
324
335
|
const data = loadChannels();
|
|
325
336
|
|
|
326
337
|
const index = data.channels.findIndex(c => c.id === channelId);
|
|
@@ -359,8 +370,9 @@ function writeGeminiConfigForMultiChannel(allChannels) {
|
|
|
359
370
|
}
|
|
360
371
|
|
|
361
372
|
// 构建 .env 内容
|
|
373
|
+
const effectiveApiKey = getEffectiveApiKey(defaultChannel) || '';
|
|
362
374
|
const envContent = `GOOGLE_GEMINI_BASE_URL=${defaultChannel.baseUrl}
|
|
363
|
-
GEMINI_API_KEY=${
|
|
375
|
+
GEMINI_API_KEY=${effectiveApiKey}
|
|
364
376
|
GEMINI_MODEL=${defaultChannel.model}
|
|
365
377
|
`;
|
|
366
378
|
|
|
@@ -397,31 +409,8 @@ function getEnabledChannels() {
|
|
|
397
409
|
return data.channels.filter(c => c.enabled !== false);
|
|
398
410
|
}
|
|
399
411
|
|
|
400
|
-
/**
|
|
401
|
-
* 获取渠道的有效 API Key
|
|
402
|
-
* 如果是 OAuth 认证,尝试从 token 存储获取 access token
|
|
403
|
-
* 否则返回渠道配置的 apiKey
|
|
404
|
-
*
|
|
405
|
-
* @param {Object} channel - 渠道对象
|
|
406
|
-
* @returns {string|null} 有效的 API key 或 access token,OAuth 令牌无效/过期时返回 null
|
|
407
|
-
*/
|
|
408
412
|
function getEffectiveApiKey(channel) {
|
|
409
|
-
|
|
410
|
-
try {
|
|
411
|
-
const { getToken, isTokenExpired } = require('./oauth-token-storage');
|
|
412
|
-
const token = getToken(channel.oauthTokenId);
|
|
413
|
-
if (token && !isTokenExpired(token)) {
|
|
414
|
-
return token.accessToken;
|
|
415
|
-
}
|
|
416
|
-
// OAuth 令牌无效或已过期,返回 null(调用方应处理刷新或报错)
|
|
417
|
-
console.warn(`[Gemini Channels] OAuth token expired or not found for channel ${channel.name}`);
|
|
418
|
-
return null;
|
|
419
|
-
} catch (err) {
|
|
420
|
-
console.error('[Gemini Channels] Failed to get OAuth token:', err.message);
|
|
421
|
-
return null;
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
return channel.apiKey;
|
|
413
|
+
return channel.apiKey || null;
|
|
425
414
|
}
|
|
426
415
|
|
|
427
416
|
// 保存渠道顺序
|
|
@@ -6,7 +6,7 @@ const os = require('os');
|
|
|
6
6
|
* Gemini 统计服务 - 数据采集和存储
|
|
7
7
|
*
|
|
8
8
|
* 文件结构:
|
|
9
|
-
* ~/.
|
|
9
|
+
* ~/.cc-tool/
|
|
10
10
|
* ├── gemini-statistics.json # Gemini 总体统计
|
|
11
11
|
* └── gemini-daily-stats/
|
|
12
12
|
* ├── 2025-12-05.json # 每日汇总统计
|
|
@@ -15,7 +15,7 @@ const os = require('os');
|
|
|
15
15
|
|
|
16
16
|
// 获取基础目录
|
|
17
17
|
function getBaseDir() {
|
|
18
|
-
const dir = path.join(os.homedir(), '.
|
|
18
|
+
const dir = path.join(os.homedir(), '.cc-tool');
|
|
19
19
|
if (!fs.existsSync(dir)) {
|
|
20
20
|
fs.mkdirSync(dir, { recursive: true });
|
|
21
21
|
}
|