@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
|
@@ -11,6 +11,7 @@ const DEFAULT_CONFIG = require('../config/default');
|
|
|
11
11
|
const { resolvePricing, resolveModelPricing } = require('./utils/pricing');
|
|
12
12
|
const { recordRequest } = require('./services/statistics-service');
|
|
13
13
|
const { saveProxyStartTime, clearProxyStartTime, getProxyStartTime, getProxyRuntime } = require('./services/proxy-runtime');
|
|
14
|
+
const { createDecodedStream } = require('./services/response-decoder');
|
|
14
15
|
const eventBus = require('../plugins/event-bus');
|
|
15
16
|
const { CLAUDE_MODEL_PRICING } = require('../config/model-pricing');
|
|
16
17
|
const { getEffectiveApiKey } = require('./services/channels');
|
|
@@ -160,7 +161,7 @@ async function startProxyServer(options = {}) {
|
|
|
160
161
|
|
|
161
162
|
try {
|
|
162
163
|
const config = loadConfig();
|
|
163
|
-
const port = config.ports?.proxy ||
|
|
164
|
+
const port = config.ports?.proxy || 20088;
|
|
164
165
|
currentPort = port;
|
|
165
166
|
|
|
166
167
|
proxyApp = express();
|
|
@@ -186,7 +187,7 @@ async function startProxyServer(options = {}) {
|
|
|
186
187
|
});
|
|
187
188
|
|
|
188
189
|
proxyReq.removeHeader('x-api-key');
|
|
189
|
-
const effectiveKey =
|
|
190
|
+
const effectiveKey = req.effectiveApiKey;
|
|
190
191
|
proxyReq.setHeader('x-api-key', effectiveKey);
|
|
191
192
|
proxyReq.removeHeader('authorization');
|
|
192
193
|
proxyReq.setHeader('authorization', `Bearer ${effectiveKey}`);
|
|
@@ -221,6 +222,30 @@ async function startProxyServer(options = {}) {
|
|
|
221
222
|
|
|
222
223
|
req.selectedChannel = channel;
|
|
223
224
|
req.sessionId = sessionId || null;
|
|
225
|
+
let released = false;
|
|
226
|
+
|
|
227
|
+
const release = () => {
|
|
228
|
+
if (released) return;
|
|
229
|
+
released = true;
|
|
230
|
+
releaseChannel(channel.id, 'claude');
|
|
231
|
+
// 广播调度状态(请求结束)
|
|
232
|
+
broadcastSchedulerState('claude', getSchedulerState('claude'));
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
req.__releaseChannel = release;
|
|
236
|
+
|
|
237
|
+
res.on('close', release);
|
|
238
|
+
res.on('error', release);
|
|
239
|
+
|
|
240
|
+
const effectiveKey = getEffectiveApiKey(channel);
|
|
241
|
+
if (!effectiveKey) {
|
|
242
|
+
release();
|
|
243
|
+
return res.status(401).json({
|
|
244
|
+
error: 'API key not configured or expired. Please update your channel key.',
|
|
245
|
+
type: 'authentication_error'
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
req.effectiveApiKey = effectiveKey;
|
|
224
249
|
|
|
225
250
|
// 应用模型重定向(当 proxy 开启时)
|
|
226
251
|
if (req.body && req.body.model) {
|
|
@@ -242,21 +267,6 @@ async function startProxyServer(options = {}) {
|
|
|
242
267
|
}
|
|
243
268
|
}
|
|
244
269
|
|
|
245
|
-
let released = false;
|
|
246
|
-
|
|
247
|
-
const release = () => {
|
|
248
|
-
if (released) return;
|
|
249
|
-
released = true;
|
|
250
|
-
releaseChannel(channel.id, 'claude');
|
|
251
|
-
// 广播调度状态(请求结束)
|
|
252
|
-
broadcastSchedulerState('claude', getSchedulerState('claude'));
|
|
253
|
-
};
|
|
254
|
-
|
|
255
|
-
req.__releaseChannel = release;
|
|
256
|
-
|
|
257
|
-
res.on('close', release);
|
|
258
|
-
res.on('error', release);
|
|
259
|
-
|
|
260
270
|
const proxyOptions = {
|
|
261
271
|
target: channel.baseUrl,
|
|
262
272
|
changeOrigin: true,
|
|
@@ -325,11 +335,12 @@ async function startProxyServer(options = {}) {
|
|
|
325
335
|
cacheRead: 0,
|
|
326
336
|
model: ''
|
|
327
337
|
};
|
|
338
|
+
const parsedStream = createDecodedStream(proxyRes);
|
|
328
339
|
|
|
329
|
-
|
|
340
|
+
parsedStream.on('data', (chunk) => {
|
|
330
341
|
if (isResponseClosed) return;
|
|
331
342
|
|
|
332
|
-
buffer += chunk.toString();
|
|
343
|
+
buffer += chunk.toString('utf8');
|
|
333
344
|
|
|
334
345
|
const events = buffer.split('\n\n');
|
|
335
346
|
buffer = events.pop() || '';
|
|
@@ -439,9 +450,9 @@ async function startProxyServer(options = {}) {
|
|
|
439
450
|
}
|
|
440
451
|
};
|
|
441
452
|
|
|
442
|
-
|
|
453
|
+
parsedStream.on('end', finalize);
|
|
443
454
|
|
|
444
|
-
|
|
455
|
+
parsedStream.on('error', (err) => {
|
|
445
456
|
if (err.code !== 'EPIPE' && err.code !== 'ECONNRESET') {
|
|
446
457
|
console.error('Proxy response error:', err);
|
|
447
458
|
}
|
|
@@ -533,7 +544,7 @@ function getProxyStatus() {
|
|
|
533
544
|
return {
|
|
534
545
|
running: !!proxyServer,
|
|
535
546
|
port: currentPort,
|
|
536
|
-
defaultPort: config.ports?.proxy ||
|
|
547
|
+
defaultPort: config.ports?.proxy || 20088,
|
|
537
548
|
startTime,
|
|
538
549
|
runtime
|
|
539
550
|
};
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agents 服务
|
|
3
3
|
*
|
|
4
|
-
* 管理 Claude
|
|
5
|
-
* 代理目录:
|
|
6
|
-
* - 用户级: ~/.claude/agents/
|
|
7
|
-
* - 项目级: .claude/agents/
|
|
8
|
-
*
|
|
4
|
+
* 管理 Claude/OpenCode 自定义代理的 CRUD 操作
|
|
9
5
|
* 支持从 GitHub 仓库扫描和安装代理
|
|
10
6
|
*/
|
|
11
7
|
|
|
@@ -13,12 +9,37 @@ const fs = require('fs');
|
|
|
13
9
|
const path = require('path');
|
|
14
10
|
const os = require('os');
|
|
15
11
|
const { RepoScannerBase } = require('./repo-scanner-base');
|
|
16
|
-
|
|
17
|
-
// 代理目录路径
|
|
18
|
-
const USER_AGENTS_DIR = path.join(os.homedir(), '.claude', 'agents');
|
|
12
|
+
const { NATIVE_PATHS } = require('../../config/paths');
|
|
19
13
|
|
|
20
14
|
// 默认仓库源
|
|
21
15
|
const DEFAULT_REPOS = [];
|
|
16
|
+
const SUPPORTED_PLATFORMS = ['claude', 'opencode'];
|
|
17
|
+
const OPENCODE_CONFIG_DIR = NATIVE_PATHS.opencode.config;
|
|
18
|
+
|
|
19
|
+
const PLATFORM_CONFIG = {
|
|
20
|
+
claude: {
|
|
21
|
+
userAgentsDir: path.join(os.homedir(), '.claude', 'agents'),
|
|
22
|
+
projectAgentsDir: (projectPath) => path.join(projectPath, '.claude', 'agents'),
|
|
23
|
+
repoType: 'agents'
|
|
24
|
+
},
|
|
25
|
+
opencode: {
|
|
26
|
+
userAgentsDir: path.join(OPENCODE_CONFIG_DIR, 'agents'),
|
|
27
|
+
legacyUserAgentsDir: path.join(OPENCODE_CONFIG_DIR, 'agent'),
|
|
28
|
+
projectAgentsDir: (projectPath) => {
|
|
29
|
+
const modern = path.join(projectPath, '.opencode', 'agents');
|
|
30
|
+
const legacy = path.join(projectPath, '.opencode', 'agent');
|
|
31
|
+
if (fs.existsSync(legacy) && !fs.existsSync(modern)) {
|
|
32
|
+
return legacy;
|
|
33
|
+
}
|
|
34
|
+
return modern;
|
|
35
|
+
},
|
|
36
|
+
repoType: 'opencode-agents'
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
function normalizePlatform(platform) {
|
|
41
|
+
return SUPPORTED_PLATFORMS.includes(platform) ? platform : 'claude';
|
|
42
|
+
}
|
|
22
43
|
|
|
23
44
|
/**
|
|
24
45
|
* 确保目录存在
|
|
@@ -74,11 +95,11 @@ function parseFrontmatter(content) {
|
|
|
74
95
|
/**
|
|
75
96
|
* 生成 frontmatter 字符串
|
|
76
97
|
*/
|
|
77
|
-
function generateFrontmatter(data) {
|
|
98
|
+
function generateFrontmatter(data, platform = 'claude') {
|
|
78
99
|
const lines = ['---'];
|
|
79
100
|
|
|
80
|
-
//
|
|
81
|
-
if (data.name) {
|
|
101
|
+
// Claude 下写入 name,OpenCode 以文件名作为 agent id
|
|
102
|
+
if (platform !== 'opencode' && data.name) {
|
|
82
103
|
lines.push(`name: ${data.name}`);
|
|
83
104
|
}
|
|
84
105
|
if (data.description) {
|
|
@@ -164,10 +185,10 @@ function scanAgentsDir(dir, basePath, scope) {
|
|
|
164
185
|
* Agents 仓库扫描器
|
|
165
186
|
*/
|
|
166
187
|
class AgentsRepoScanner extends RepoScannerBase {
|
|
167
|
-
constructor() {
|
|
188
|
+
constructor(platform, installDir) {
|
|
168
189
|
super({
|
|
169
|
-
type: 'agents',
|
|
170
|
-
installDir
|
|
190
|
+
type: PLATFORM_CONFIG[platform]?.repoType || 'agents',
|
|
191
|
+
installDir,
|
|
171
192
|
markerFile: null, // 直接扫描 .md 文件
|
|
172
193
|
fileExtension: '.md',
|
|
173
194
|
defaultRepos: DEFAULT_REPOS
|
|
@@ -248,12 +269,28 @@ class AgentsRepoScanner extends RepoScannerBase {
|
|
|
248
269
|
* Agents 服务类
|
|
249
270
|
*/
|
|
250
271
|
class AgentsService {
|
|
251
|
-
constructor() {
|
|
252
|
-
this.
|
|
253
|
-
|
|
272
|
+
constructor(platform = 'claude') {
|
|
273
|
+
this.platform = normalizePlatform(platform);
|
|
274
|
+
const config = PLATFORM_CONFIG[this.platform];
|
|
275
|
+
|
|
276
|
+
this.userAgentsDir = config.userAgentsDir;
|
|
277
|
+
if (this.platform === 'opencode') {
|
|
278
|
+
const legacyUserDir = config.legacyUserAgentsDir;
|
|
279
|
+
if (legacyUserDir && fs.existsSync(legacyUserDir) && !fs.existsSync(this.userAgentsDir)) {
|
|
280
|
+
this.userAgentsDir = legacyUserDir;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
this.projectAgentsDir = config.projectAgentsDir;
|
|
285
|
+
this.repoScanner = new AgentsRepoScanner(this.platform, this.userAgentsDir);
|
|
254
286
|
ensureDir(this.userAgentsDir);
|
|
255
287
|
}
|
|
256
288
|
|
|
289
|
+
getProjectAgentsDir(projectPath) {
|
|
290
|
+
if (!projectPath) return null;
|
|
291
|
+
return this.projectAgentsDir(projectPath);
|
|
292
|
+
}
|
|
293
|
+
|
|
257
294
|
/**
|
|
258
295
|
* 获取所有代理列表
|
|
259
296
|
* @param {string} projectPath - 项目路径(可选,用于获取项目级代理)
|
|
@@ -267,7 +304,7 @@ class AgentsService {
|
|
|
267
304
|
|
|
268
305
|
// 获取项目级代理(如果提供了项目路径)
|
|
269
306
|
if (projectPath) {
|
|
270
|
-
const projectAgentsDir =
|
|
307
|
+
const projectAgentsDir = this.getProjectAgentsDir(projectPath);
|
|
271
308
|
const projectAgents = scanAgentsDir(projectAgentsDir, projectAgentsDir, 'project');
|
|
272
309
|
agents.push(...projectAgents);
|
|
273
310
|
}
|
|
@@ -331,7 +368,7 @@ class AgentsService {
|
|
|
331
368
|
getAgent(fileName, scope, projectPath = null) {
|
|
332
369
|
const baseDir = scope === 'user'
|
|
333
370
|
? this.userAgentsDir
|
|
334
|
-
:
|
|
371
|
+
: this.getProjectAgentsDir(projectPath);
|
|
335
372
|
|
|
336
373
|
const filePath = path.join(baseDir, `${fileName}.md`);
|
|
337
374
|
|
|
@@ -382,7 +419,7 @@ class AgentsService {
|
|
|
382
419
|
|
|
383
420
|
const baseDir = scope === 'user'
|
|
384
421
|
? this.userAgentsDir
|
|
385
|
-
:
|
|
422
|
+
: this.getProjectAgentsDir(projectPath);
|
|
386
423
|
|
|
387
424
|
ensureDir(baseDir);
|
|
388
425
|
|
|
@@ -400,7 +437,7 @@ class AgentsService {
|
|
|
400
437
|
if (permissionMode) frontmatterData.permissionMode = permissionMode;
|
|
401
438
|
if (skills) frontmatterData.skills = skills;
|
|
402
439
|
|
|
403
|
-
const content = generateFrontmatter(frontmatterData) + '\n\n' + (systemPrompt || '');
|
|
440
|
+
const content = generateFrontmatter(frontmatterData, this.platform) + '\n\n' + (systemPrompt || '');
|
|
404
441
|
|
|
405
442
|
fs.writeFileSync(filePath, content, 'utf-8');
|
|
406
443
|
|
|
@@ -413,7 +450,7 @@ class AgentsService {
|
|
|
413
450
|
updateAgent({ fileName, scope, projectPath, name, description, tools, model, permissionMode, skills, systemPrompt }) {
|
|
414
451
|
const baseDir = scope === 'user'
|
|
415
452
|
? this.userAgentsDir
|
|
416
|
-
:
|
|
453
|
+
: this.getProjectAgentsDir(projectPath);
|
|
417
454
|
|
|
418
455
|
const filePath = path.join(baseDir, `${fileName}.md`);
|
|
419
456
|
|
|
@@ -431,7 +468,7 @@ class AgentsService {
|
|
|
431
468
|
if (permissionMode) frontmatterData.permissionMode = permissionMode;
|
|
432
469
|
if (skills) frontmatterData.skills = skills;
|
|
433
470
|
|
|
434
|
-
const content = generateFrontmatter(frontmatterData) + '\n\n' + (systemPrompt || '');
|
|
471
|
+
const content = generateFrontmatter(frontmatterData, this.platform) + '\n\n' + (systemPrompt || '');
|
|
435
472
|
|
|
436
473
|
fs.writeFileSync(filePath, content, 'utf-8');
|
|
437
474
|
|
|
@@ -444,7 +481,7 @@ class AgentsService {
|
|
|
444
481
|
deleteAgent(fileName, scope, projectPath = null) {
|
|
445
482
|
const baseDir = scope === 'user'
|
|
446
483
|
? this.userAgentsDir
|
|
447
|
-
:
|
|
484
|
+
: this.getProjectAgentsDir(projectPath);
|
|
448
485
|
|
|
449
486
|
const filePath = path.join(baseDir, `${fileName}.md`);
|
|
450
487
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const { getAllChannels } = require('./channels');
|
|
2
2
|
const { getChannels: getCodexChannels } = require('./codex-channels');
|
|
3
3
|
const { getChannels: getGeminiChannels } = require('./gemini-channels');
|
|
4
|
+
const { getChannels: getOpenCodeChannels } = require('./opencode-channels');
|
|
4
5
|
const { isChannelAvailable, getChannelHealthStatus, setOnChannelFrozen } = require('./channel-health');
|
|
5
6
|
|
|
6
7
|
const channelProviders = {
|
|
@@ -12,6 +13,10 @@ const channelProviders = {
|
|
|
12
13
|
gemini: () => {
|
|
13
14
|
const data = getGeminiChannels();
|
|
14
15
|
return Array.isArray(data?.channels) ? data.channels : [];
|
|
16
|
+
},
|
|
17
|
+
opencode: () => {
|
|
18
|
+
const data = getOpenCodeChannels();
|
|
19
|
+
return Array.isArray(data?.channels) ? data.channels : [];
|
|
15
20
|
}
|
|
16
21
|
};
|
|
17
22
|
|
|
@@ -27,7 +32,8 @@ function createState() {
|
|
|
27
32
|
const schedulerStates = {
|
|
28
33
|
claude: createState(),
|
|
29
34
|
codex: createState(),
|
|
30
|
-
gemini: createState()
|
|
35
|
+
gemini: createState(),
|
|
36
|
+
opencode: createState()
|
|
31
37
|
};
|
|
32
38
|
|
|
33
39
|
function getState(source = 'claude') {
|
|
@@ -69,10 +75,8 @@ function refreshChannels(source = 'claude') {
|
|
|
69
75
|
state.channels = raw
|
|
70
76
|
.filter(ch => ch.enabled !== false)
|
|
71
77
|
.map(ch => ({
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
baseUrl: ch.baseUrl,
|
|
75
|
-
apiKey: ch.apiKey,
|
|
78
|
+
// 保留渠道完整字段,避免 proxy 等运行时配置在调度层丢失
|
|
79
|
+
...ch,
|
|
76
80
|
weight: Math.max(1, Number(ch.weight) || 1),
|
|
77
81
|
maxConcurrency: ch.maxConcurrency ?? null,
|
|
78
82
|
modelConfig: ch.modelConfig || null,
|
|
@@ -4,7 +4,7 @@ const os = require('os');
|
|
|
4
4
|
const { isProxyConfig } = require('./settings-manager');
|
|
5
5
|
|
|
6
6
|
function getChannelsFilePath() {
|
|
7
|
-
const dir = path.join(os.homedir(), '.
|
|
7
|
+
const dir = path.join(os.homedir(), '.cc-tool');
|
|
8
8
|
if (!fs.existsSync(dir)) {
|
|
9
9
|
fs.mkdirSync(dir, { recursive: true });
|
|
10
10
|
}
|
|
@@ -12,7 +12,7 @@ function getChannelsFilePath() {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
function getActiveChannelIdPath() {
|
|
15
|
-
const dir = path.join(os.homedir(), '.
|
|
15
|
+
const dir = path.join(os.homedir(), '.cc-tool');
|
|
16
16
|
if (!fs.existsSync(dir)) {
|
|
17
17
|
fs.mkdirSync(dir, { recursive: true });
|
|
18
18
|
}
|
|
@@ -57,6 +57,38 @@ function normalizeNumber(value, defaultValue, max = null) {
|
|
|
57
57
|
return num;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
function normalizeGatewaySourceType(value, fallback = 'claude') {
|
|
61
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
62
|
+
if (normalized === 'claude') return 'claude';
|
|
63
|
+
if (normalized === 'codex') return 'codex';
|
|
64
|
+
if (normalized === 'gemini') return 'gemini';
|
|
65
|
+
return fallback;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function extractApiKeyFromHelper(apiKeyHelper) {
|
|
69
|
+
if (typeof apiKeyHelper !== 'string' || !apiKeyHelper.trim()) {
|
|
70
|
+
return '';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const helper = apiKeyHelper.trim();
|
|
74
|
+
let match = helper.match(/^echo\s+["']([^"']+)["']$/);
|
|
75
|
+
if (match && match[1]) {
|
|
76
|
+
return match[1];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
match = helper.match(/^printf\s+["'][^"']*["']\s+["']([^"']+)["']$/);
|
|
80
|
+
if (match && match[1]) {
|
|
81
|
+
return match[1];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return '';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function buildApiKeyHelperCommand() {
|
|
88
|
+
// 避免把明文 API Key 写入可执行命令,降低注入风险
|
|
89
|
+
return 'printf "%s" "${ANTHROPIC_AUTH_TOKEN:-${ANTHROPIC_API_KEY:-}}"';
|
|
90
|
+
}
|
|
91
|
+
|
|
60
92
|
function applyChannelDefaults(channel) {
|
|
61
93
|
const normalized = { ...channel };
|
|
62
94
|
if (normalized.enabled === undefined) {
|
|
@@ -65,11 +97,6 @@ function applyChannelDefaults(channel) {
|
|
|
65
97
|
normalized.enabled = !!normalized.enabled;
|
|
66
98
|
}
|
|
67
99
|
|
|
68
|
-
// OAuth 字段默认值(向后兼容)
|
|
69
|
-
if (!normalized.authType) {
|
|
70
|
-
normalized.authType = 'apiKey';
|
|
71
|
-
}
|
|
72
|
-
|
|
73
100
|
normalized.weight = normalizeNumber(normalized.weight, 1, 100);
|
|
74
101
|
|
|
75
102
|
if (normalized.maxConcurrency === undefined ||
|
|
@@ -80,6 +107,8 @@ function applyChannelDefaults(channel) {
|
|
|
80
107
|
normalized.maxConcurrency = normalizeNumber(normalized.maxConcurrency, 1, 100);
|
|
81
108
|
}
|
|
82
109
|
|
|
110
|
+
normalized.gatewaySourceType = normalizeGatewaySourceType(normalized.gatewaySourceType, 'claude');
|
|
111
|
+
|
|
83
112
|
return normalized;
|
|
84
113
|
}
|
|
85
114
|
|
|
@@ -144,10 +173,7 @@ function getCurrentSettings() {
|
|
|
144
173
|
'';
|
|
145
174
|
|
|
146
175
|
if (!apiKey && settings.apiKeyHelper) {
|
|
147
|
-
|
|
148
|
-
if (match && match[1]) {
|
|
149
|
-
apiKey = match[1];
|
|
150
|
-
}
|
|
176
|
+
apiKey = extractApiKeyFromHelper(settings.apiKeyHelper);
|
|
151
177
|
}
|
|
152
178
|
|
|
153
179
|
if (!baseUrl && !apiKey) {
|
|
@@ -178,6 +204,23 @@ function getAllChannels() {
|
|
|
178
204
|
return data.channels;
|
|
179
205
|
}
|
|
180
206
|
|
|
207
|
+
function getCurrentChannel() {
|
|
208
|
+
const channels = getAllChannels();
|
|
209
|
+
if (!Array.isArray(channels) || channels.length === 0) {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const activeChannelId = loadActiveChannelId();
|
|
214
|
+
if (activeChannelId) {
|
|
215
|
+
const matched = channels.find(ch => ch.id === activeChannelId);
|
|
216
|
+
if (matched) {
|
|
217
|
+
return matched;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return channels.find(ch => ch.enabled !== false) || channels[0];
|
|
222
|
+
}
|
|
223
|
+
|
|
181
224
|
function createChannel(name, baseUrl, apiKey, websiteUrl, extraConfig = {}) {
|
|
182
225
|
const data = loadChannels();
|
|
183
226
|
const newChannel = applyChannelDefaults({
|
|
@@ -195,10 +238,7 @@ function createChannel(name, baseUrl, apiKey, websiteUrl, extraConfig = {}) {
|
|
|
195
238
|
modelRedirects: extraConfig.modelRedirects || [],
|
|
196
239
|
proxyUrl: extraConfig.proxyUrl || '',
|
|
197
240
|
speedTestModel: extraConfig.speedTestModel || null,
|
|
198
|
-
|
|
199
|
-
authType: extraConfig.authType || 'apiKey',
|
|
200
|
-
oauthProvider: extraConfig.oauthProvider || null,
|
|
201
|
-
oauthTokenId: extraConfig.oauthTokenId || null
|
|
241
|
+
gatewaySourceType: normalizeGatewaySourceType(extraConfig.gatewaySourceType, 'claude')
|
|
202
242
|
});
|
|
203
243
|
|
|
204
244
|
data.channels.push(newChannel);
|
|
@@ -218,7 +258,7 @@ function updateChannel(id, updates) {
|
|
|
218
258
|
const oldChannel = { ...data.channels[index] };
|
|
219
259
|
|
|
220
260
|
const merged = { ...data.channels[index], ...updates };
|
|
221
|
-
|
|
261
|
+
const nextChannel = applyChannelDefaults({
|
|
222
262
|
...merged,
|
|
223
263
|
weight: merged.weight,
|
|
224
264
|
maxConcurrency: merged.maxConcurrency,
|
|
@@ -228,8 +268,10 @@ function updateChannel(id, updates) {
|
|
|
228
268
|
modelRedirects: merged.modelRedirects || [],
|
|
229
269
|
proxyUrl: merged.proxyUrl,
|
|
230
270
|
speedTestModel: merged.speedTestModel,
|
|
271
|
+
gatewaySourceType: normalizeGatewaySourceType(merged.gatewaySourceType, 'claude'),
|
|
231
272
|
updatedAt: Date.now()
|
|
232
273
|
});
|
|
274
|
+
data.channels[index] = nextChannel;
|
|
233
275
|
|
|
234
276
|
// Get proxy status
|
|
235
277
|
const { getProxyStatus } = require('../proxy-server');
|
|
@@ -273,7 +315,7 @@ function updateChannel(id, updates) {
|
|
|
273
315
|
return data.channels[index];
|
|
274
316
|
}
|
|
275
317
|
|
|
276
|
-
function deleteChannel(id) {
|
|
318
|
+
async function deleteChannel(id) {
|
|
277
319
|
const data = loadChannels();
|
|
278
320
|
const index = data.channels.findIndex(ch => ch.id === id);
|
|
279
321
|
|
|
@@ -283,6 +325,7 @@ function deleteChannel(id) {
|
|
|
283
325
|
|
|
284
326
|
data.channels.splice(index, 1);
|
|
285
327
|
saveChannels(data);
|
|
328
|
+
|
|
286
329
|
return { success: true };
|
|
287
330
|
}
|
|
288
331
|
|
|
@@ -356,7 +399,7 @@ function updateClaudeSettingsWithModelConfig(channel) {
|
|
|
356
399
|
delete settings.env.NO_PROXY;
|
|
357
400
|
}
|
|
358
401
|
|
|
359
|
-
settings.apiKeyHelper =
|
|
402
|
+
settings.apiKeyHelper = buildApiKeyHelperCommand();
|
|
360
403
|
|
|
361
404
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
|
|
362
405
|
}
|
|
@@ -385,34 +428,18 @@ function updateClaudeSettings(baseUrl, apiKey) {
|
|
|
385
428
|
settings.env.ANTHROPIC_API_KEY = apiKey;
|
|
386
429
|
}
|
|
387
430
|
|
|
388
|
-
settings.apiKeyHelper =
|
|
431
|
+
settings.apiKeyHelper = buildApiKeyHelperCommand();
|
|
389
432
|
|
|
390
433
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
|
|
391
434
|
}
|
|
392
435
|
|
|
393
|
-
/**
|
|
394
|
-
* 获取渠道的有效 API Key
|
|
395
|
-
* 如果渠道使用 OAuth 认证,返回有效的 OAuth 令牌;否则返回静态 API Key
|
|
396
|
-
*
|
|
397
|
-
* @param {Object} channel - 渠道对象
|
|
398
|
-
* @returns {string|null} 有效的 API Key,OAuth 令牌无效/过期时返回 null
|
|
399
|
-
*/
|
|
400
436
|
function getEffectiveApiKey(channel) {
|
|
401
|
-
|
|
402
|
-
const { getToken, isTokenExpired } = require('./oauth-token-storage');
|
|
403
|
-
const token = getToken(channel.oauthTokenId);
|
|
404
|
-
if (token && !isTokenExpired(token)) {
|
|
405
|
-
return token.accessToken;
|
|
406
|
-
}
|
|
407
|
-
// OAuth 令牌无效或已过期,返回 null(调用方应处理刷新或报错)
|
|
408
|
-
console.warn(`[Channels] OAuth token expired or not found for channel ${channel.name}`);
|
|
409
|
-
return null;
|
|
410
|
-
}
|
|
411
|
-
return channel.apiKey;
|
|
437
|
+
return channel.apiKey || null;
|
|
412
438
|
}
|
|
413
439
|
|
|
414
440
|
module.exports = {
|
|
415
441
|
getAllChannels,
|
|
442
|
+
getCurrentChannel,
|
|
416
443
|
getCurrentSettings,
|
|
417
444
|
createChannel,
|
|
418
445
|
updateChannel,
|