@adversity/coding-tool-x 3.1.0 → 3.1.1

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.
Files changed (137) hide show
  1. package/CHANGELOG.md +15 -18
  2. package/README.md +8 -8
  3. package/dist/web/assets/ConfigTemplates-Bidwfdf2.css +1 -0
  4. package/dist/web/assets/ConfigTemplates-ZrK_s7ma.js +1 -0
  5. package/dist/web/assets/Home-B8YfhZ3c.js +1 -0
  6. package/dist/web/assets/Home-Di2qsylF.css +1 -0
  7. package/dist/web/assets/PluginManager-BD7QUZbU.js +1 -0
  8. package/dist/web/assets/PluginManager-ROyoZ-6m.css +1 -0
  9. package/dist/web/assets/ProjectList-C1fQb9OW.css +1 -0
  10. package/dist/web/assets/ProjectList-DRb1DuHV.js +1 -0
  11. package/dist/web/assets/SessionList-BGJWyneI.css +1 -0
  12. package/dist/web/assets/SessionList-lZ0LKzfT.js +1 -0
  13. package/dist/web/assets/SkillManager-C1xG5B4Q.js +1 -0
  14. package/dist/web/assets/SkillManager-D7pd-d_P.css +1 -0
  15. package/dist/web/assets/Terminal-DGNJeVtc.css +1 -0
  16. package/dist/web/assets/Terminal-DksBo_lM.js +1 -0
  17. package/dist/web/assets/WorkspaceManager-Burx7XOo.js +1 -0
  18. package/dist/web/assets/WorkspaceManager-CrwgQgmP.css +1 -0
  19. package/dist/web/assets/icons-kcfLIMBB.js +1 -0
  20. package/dist/web/assets/index-Ufv5rCa5.css +1 -0
  21. package/dist/web/assets/index-lAkrRC3h.js +2 -0
  22. package/dist/web/assets/markdown-BfC0goYb.css +10 -0
  23. package/dist/web/assets/markdown-C9MYpaSi.js +1 -0
  24. package/dist/web/assets/naive-ui-CSrLusZZ.js +1 -0
  25. package/dist/web/assets/{vendors-D2HHw_aW.js → vendors-CO3Upi1d.js} +2 -2
  26. package/dist/web/assets/vue-vendor-DqyWIXEb.js +45 -0
  27. package/dist/web/assets/xterm-6GBZ9nXN.css +32 -0
  28. package/dist/web/assets/xterm-BJzAjXCH.js +13 -0
  29. package/dist/web/index.html +8 -6
  30. package/package.json +4 -2
  31. package/src/commands/channels.js +48 -1
  32. package/src/commands/cli-type.js +4 -2
  33. package/src/commands/daemon.js +81 -12
  34. package/src/commands/doctor.js +10 -9
  35. package/src/commands/list.js +1 -1
  36. package/src/commands/logs.js +6 -4
  37. package/src/commands/port-config.js +24 -4
  38. package/src/commands/proxy-control.js +12 -6
  39. package/src/commands/search.js +1 -1
  40. package/src/commands/security.js +3 -2
  41. package/src/commands/stats.js +226 -52
  42. package/src/commands/switch.js +1 -1
  43. package/src/commands/toggle-proxy.js +31 -6
  44. package/src/commands/update.js +97 -0
  45. package/src/commands/workspace.js +1 -1
  46. package/src/config/default.js +39 -2
  47. package/src/config/loader.js +74 -8
  48. package/src/config/paths.js +105 -33
  49. package/src/index.js +64 -3
  50. package/src/plugins/constants.js +3 -2
  51. package/src/plugins/plugin-api.js +1 -1
  52. package/src/reset-config.js +4 -2
  53. package/src/server/api/agents.js +57 -14
  54. package/src/server/api/channels.js +112 -33
  55. package/src/server/api/codex-channels.js +111 -18
  56. package/src/server/api/codex-proxy.js +14 -8
  57. package/src/server/api/commands.js +71 -18
  58. package/src/server/api/config-export.js +0 -6
  59. package/src/server/api/config-registry.js +11 -3
  60. package/src/server/api/config.js +376 -5
  61. package/src/server/api/convert.js +133 -0
  62. package/src/server/api/dashboard.js +22 -6
  63. package/src/server/api/gemini-channels.js +107 -18
  64. package/src/server/api/gemini-proxy.js +14 -8
  65. package/src/server/api/gemini-sessions.js +1 -1
  66. package/src/server/api/health-check.js +4 -3
  67. package/src/server/api/mcp.js +3 -3
  68. package/src/server/api/opencode-channels.js +419 -0
  69. package/src/server/api/opencode-projects.js +99 -0
  70. package/src/server/api/opencode-proxy.js +198 -0
  71. package/src/server/api/opencode-sessions.js +403 -0
  72. package/src/server/api/opencode-statistics.js +57 -0
  73. package/src/server/api/plugins.js +66 -19
  74. package/src/server/api/prompts.js +2 -2
  75. package/src/server/api/proxy.js +7 -4
  76. package/src/server/api/sessions.js +3 -0
  77. package/src/server/api/skills.js +69 -18
  78. package/src/server/api/workspaces.js +78 -6
  79. package/src/server/codex-proxy-server.js +30 -18
  80. package/src/server/dev-server.js +1 -1
  81. package/src/server/gemini-proxy-server.js +15 -3
  82. package/src/server/index.js +165 -58
  83. package/src/server/opencode-proxy-server.js +4375 -0
  84. package/src/server/proxy-server.js +27 -18
  85. package/src/server/services/agents-service.js +61 -24
  86. package/src/server/services/channel-scheduler.js +9 -5
  87. package/src/server/services/channels.js +64 -37
  88. package/src/server/services/codex-channels.js +56 -43
  89. package/src/server/services/codex-settings-manager.js +271 -49
  90. package/src/server/services/codex-statistics-service.js +2 -2
  91. package/src/server/services/commands-service.js +84 -25
  92. package/src/server/services/config-export-service.js +7 -45
  93. package/src/server/services/config-registry-service.js +63 -17
  94. package/src/server/services/config-sync-manager.js +160 -7
  95. package/src/server/services/config-templates-service.js +204 -51
  96. package/src/server/services/env-checker.js +26 -12
  97. package/src/server/services/env-manager.js +126 -18
  98. package/src/server/services/favorites.js +5 -3
  99. package/src/server/services/gemini-channels.js +33 -44
  100. package/src/server/services/gemini-statistics-service.js +2 -2
  101. package/src/server/services/mcp-service.js +350 -9
  102. package/src/server/services/model-detector.js +707 -221
  103. package/src/server/services/network-access.js +80 -0
  104. package/src/server/services/opencode-channels.js +206 -0
  105. package/src/server/services/opencode-gateway-converter.js +639 -0
  106. package/src/server/services/opencode-sessions.js +663 -0
  107. package/src/server/services/opencode-settings-manager.js +342 -0
  108. package/src/server/services/opencode-statistics-service.js +255 -0
  109. package/src/server/services/plugins-service.js +479 -22
  110. package/src/server/services/prompts-service.js +53 -11
  111. package/src/server/services/proxy-runtime.js +1 -1
  112. package/src/server/services/repo-scanner-base.js +1 -1
  113. package/src/server/services/security-config.js +1 -1
  114. package/src/server/services/session-cache.js +1 -1
  115. package/src/server/services/skill-service.js +300 -46
  116. package/src/server/services/speed-test.js +464 -186
  117. package/src/server/services/statistics-service.js +2 -2
  118. package/src/server/services/terminal-commands.js +10 -3
  119. package/src/server/services/terminal-config.js +1 -1
  120. package/src/server/services/ui-config.js +1 -1
  121. package/src/server/services/workspace-service.js +57 -100
  122. package/src/server/websocket-server.js +132 -3
  123. package/src/ui/menu.js +49 -40
  124. package/src/utils/port-helper.js +22 -8
  125. package/src/utils/session.js +5 -4
  126. package/dist/web/assets/icons-CO_2OFES.js +0 -1
  127. package/dist/web/assets/index-DI8QOi-E.js +0 -14
  128. package/dist/web/assets/index-uLHGdeZh.css +0 -41
  129. package/dist/web/assets/naive-ui-B1re3c-e.js +0 -1
  130. package/dist/web/assets/vue-vendor-6JaYHOiI.js +0 -44
  131. package/src/server/api/oauth.js +0 -294
  132. package/src/server/api/permissions.js +0 -385
  133. package/src/server/config/oauth-providers.js +0 -68
  134. package/src/server/services/oauth-callback-server.js +0 -284
  135. package/src/server/services/oauth-service.js +0 -378
  136. package/src/server/services/oauth-token-storage.js +0 -135
  137. package/src/server/services/permission-templates-service.js +0 -308
@@ -160,7 +160,7 @@ async function startProxyServer(options = {}) {
160
160
 
161
161
  try {
162
162
  const config = loadConfig();
163
- const port = config.ports?.proxy || 10088;
163
+ const port = config.ports?.proxy || 20088;
164
164
  currentPort = port;
165
165
 
166
166
  proxyApp = express();
@@ -186,7 +186,7 @@ async function startProxyServer(options = {}) {
186
186
  });
187
187
 
188
188
  proxyReq.removeHeader('x-api-key');
189
- const effectiveKey = getEffectiveApiKey(selectedChannel);
189
+ const effectiveKey = req.effectiveApiKey;
190
190
  proxyReq.setHeader('x-api-key', effectiveKey);
191
191
  proxyReq.removeHeader('authorization');
192
192
  proxyReq.setHeader('authorization', `Bearer ${effectiveKey}`);
@@ -221,6 +221,30 @@ async function startProxyServer(options = {}) {
221
221
 
222
222
  req.selectedChannel = channel;
223
223
  req.sessionId = sessionId || null;
224
+ let released = false;
225
+
226
+ const release = () => {
227
+ if (released) return;
228
+ released = true;
229
+ releaseChannel(channel.id, 'claude');
230
+ // 广播调度状态(请求结束)
231
+ broadcastSchedulerState('claude', getSchedulerState('claude'));
232
+ };
233
+
234
+ req.__releaseChannel = release;
235
+
236
+ res.on('close', release);
237
+ res.on('error', release);
238
+
239
+ const effectiveKey = getEffectiveApiKey(channel);
240
+ if (!effectiveKey) {
241
+ release();
242
+ return res.status(401).json({
243
+ error: 'API key not configured or expired. Please update your channel key.',
244
+ type: 'authentication_error'
245
+ });
246
+ }
247
+ req.effectiveApiKey = effectiveKey;
224
248
 
225
249
  // 应用模型重定向(当 proxy 开启时)
226
250
  if (req.body && req.body.model) {
@@ -242,21 +266,6 @@ async function startProxyServer(options = {}) {
242
266
  }
243
267
  }
244
268
 
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
269
  const proxyOptions = {
261
270
  target: channel.baseUrl,
262
271
  changeOrigin: true,
@@ -533,7 +542,7 @@ function getProxyStatus() {
533
542
  return {
534
543
  running: !!proxyServer,
535
544
  port: currentPort,
536
- defaultPort: config.ports?.proxy || 10088,
545
+ defaultPort: config.ports?.proxy || 20088,
537
546
  startTime,
538
547
  runtime
539
548
  };
@@ -1,11 +1,7 @@
1
1
  /**
2
2
  * Agents 服务
3
3
  *
4
- * 管理 Claude Code 自定义代理的 CRUD 操作
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: USER_AGENTS_DIR,
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.userAgentsDir = USER_AGENTS_DIR;
253
- this.repoScanner = new AgentsRepoScanner();
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 = path.join(projectPath, '.claude', 'agents');
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
- : path.join(projectPath, '.claude', 'agents');
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
- : path.join(projectPath, '.claude', 'agents');
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
- : path.join(projectPath, '.claude', 'agents');
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
- : path.join(projectPath, '.claude', 'agents');
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
- id: ch.id,
73
- name: ch.name,
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(), '.claude', 'cc-tool');
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(), '.claude', 'cc-tool');
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
- const match = settings.apiKeyHelper.match(/['"]([^'"]+)['"]/);
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
- // OAuth 支持
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
- data.channels[index] = applyChannelDefaults({
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 = `echo '${apiKey}'`;
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 = `echo '${apiKey}'`;
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
- if (channel.authType === 'oauth' && channel.oauthTokenId) {
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,