@adversity/coding-tool-x 3.0.6 → 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 (133) hide show
  1. package/CHANGELOG.md +38 -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 +92 -13
  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/ui.js +8 -1
  45. package/src/commands/update.js +97 -0
  46. package/src/commands/workspace.js +1 -1
  47. package/src/config/default.js +39 -2
  48. package/src/config/loader.js +74 -8
  49. package/src/config/paths.js +105 -33
  50. package/src/index.js +67 -4
  51. package/src/plugins/constants.js +3 -2
  52. package/src/plugins/plugin-api.js +1 -1
  53. package/src/reset-config.js +4 -2
  54. package/src/server/api/agents.js +57 -14
  55. package/src/server/api/channels.js +112 -33
  56. package/src/server/api/codex-channels.js +111 -18
  57. package/src/server/api/codex-proxy.js +14 -8
  58. package/src/server/api/commands.js +71 -18
  59. package/src/server/api/config-export.js +0 -6
  60. package/src/server/api/config-registry.js +11 -3
  61. package/src/server/api/config.js +376 -5
  62. package/src/server/api/convert.js +133 -0
  63. package/src/server/api/dashboard.js +22 -6
  64. package/src/server/api/gemini-channels.js +107 -18
  65. package/src/server/api/gemini-proxy.js +14 -8
  66. package/src/server/api/gemini-sessions.js +1 -1
  67. package/src/server/api/health-check.js +4 -3
  68. package/src/server/api/mcp.js +3 -3
  69. package/src/server/api/opencode-channels.js +419 -0
  70. package/src/server/api/opencode-projects.js +99 -0
  71. package/src/server/api/opencode-proxy.js +198 -0
  72. package/src/server/api/opencode-sessions.js +403 -0
  73. package/src/server/api/opencode-statistics.js +57 -0
  74. package/src/server/api/plugins.js +66 -19
  75. package/src/server/api/prompts.js +2 -2
  76. package/src/server/api/proxy.js +7 -4
  77. package/src/server/api/sessions.js +3 -0
  78. package/src/server/api/skills.js +69 -18
  79. package/src/server/api/workspaces.js +78 -6
  80. package/src/server/codex-proxy-server.js +32 -19
  81. package/src/server/dev-server.js +1 -1
  82. package/src/server/gemini-proxy-server.js +17 -3
  83. package/src/server/index.js +164 -48
  84. package/src/server/opencode-proxy-server.js +4375 -0
  85. package/src/server/proxy-server.js +30 -19
  86. package/src/server/services/agents-service.js +61 -24
  87. package/src/server/services/channel-scheduler.js +9 -5
  88. package/src/server/services/channels.js +70 -12
  89. package/src/server/services/codex-channels.js +61 -23
  90. package/src/server/services/codex-settings-manager.js +271 -49
  91. package/src/server/services/codex-statistics-service.js +2 -2
  92. package/src/server/services/commands-service.js +84 -25
  93. package/src/server/services/config-export-service.js +7 -45
  94. package/src/server/services/config-registry-service.js +63 -17
  95. package/src/server/services/config-sync-manager.js +160 -7
  96. package/src/server/services/config-templates-service.js +204 -51
  97. package/src/server/services/env-checker.js +26 -12
  98. package/src/server/services/env-manager.js +126 -18
  99. package/src/server/services/favorites.js +5 -3
  100. package/src/server/services/gemini-channels.js +37 -15
  101. package/src/server/services/gemini-statistics-service.js +2 -2
  102. package/src/server/services/mcp-service.js +350 -9
  103. package/src/server/services/model-detector.js +707 -221
  104. package/src/server/services/network-access.js +80 -0
  105. package/src/server/services/opencode-channels.js +206 -0
  106. package/src/server/services/opencode-gateway-converter.js +639 -0
  107. package/src/server/services/opencode-sessions.js +663 -0
  108. package/src/server/services/opencode-settings-manager.js +342 -0
  109. package/src/server/services/opencode-statistics-service.js +255 -0
  110. package/src/server/services/plugins-service.js +479 -22
  111. package/src/server/services/prompts-service.js +53 -11
  112. package/src/server/services/proxy-runtime.js +1 -1
  113. package/src/server/services/repo-scanner-base.js +1 -1
  114. package/src/server/services/security-config.js +1 -1
  115. package/src/server/services/session-cache.js +1 -1
  116. package/src/server/services/skill-service.js +300 -46
  117. package/src/server/services/speed-test.js +464 -186
  118. package/src/server/services/statistics-service.js +2 -2
  119. package/src/server/services/terminal-commands.js +10 -3
  120. package/src/server/services/terminal-config.js +1 -1
  121. package/src/server/services/ui-config.js +1 -1
  122. package/src/server/services/workspace-service.js +57 -100
  123. package/src/server/websocket-server.js +132 -3
  124. package/src/ui/menu.js +49 -40
  125. package/src/utils/port-helper.js +22 -8
  126. package/src/utils/session.js +5 -4
  127. package/dist/web/assets/icons-BxudHPiX.js +0 -1
  128. package/dist/web/assets/index-D2VfwJBa.js +0 -14
  129. package/dist/web/assets/index-oXBzu0bd.css +0 -41
  130. package/dist/web/assets/naive-ui-DT-Uur8K.js +0 -1
  131. package/dist/web/assets/vue-vendor-6JaYHOiI.js +0 -44
  132. package/src/server/api/permissions.js +0 -385
  133. package/src/server/services/permission-templates-service.js +0 -308
@@ -12,9 +12,18 @@ const { getSchedulerState } = require('../services/channel-scheduler');
12
12
  const { getChannelHealthStatus, resetChannelHealth } = require('../services/channel-health');
13
13
  const { broadcastSchedulerState } = require('../websocket-server');
14
14
  const { isGeminiInstalled } = require('../services/gemini-config');
15
- const { testChannelSpeed, testMultipleChannels, getLatencyLevel } = require('../services/speed-test');
15
+ const {
16
+ testChannelSpeed,
17
+ getLatencyLevel,
18
+ sanitizeBatchConcurrency,
19
+ runWithConcurrencyLimit
20
+ } = require('../services/speed-test');
16
21
  const { clearGeminiRedirectCache } = require('../gemini-proxy-server');
17
- const { fetchModelsFromProvider } = require('../services/model-detector');
22
+ const {
23
+ probeModelAvailability,
24
+ fetchModelsFromProvider
25
+ } = require('../services/model-detector');
26
+ const GEMINI_GATEWAY_SOURCE_TYPE = 'gemini';
18
27
 
19
28
  module.exports = (config) => {
20
29
  /**
@@ -57,11 +66,46 @@ module.exports = (config) => {
57
66
  return res.status(404).json({ error: '渠道不存在' });
58
67
  }
59
68
 
60
- // Gemini 渠道尝试作为 OpenAI 兼容获取,失败则回退
61
- const result = await fetchModelsFromProvider(channel, 'openai_compatible');
69
+ const gatewaySourceType = GEMINI_GATEWAY_SOURCE_TYPE;
70
+ const listResult = await fetchModelsFromProvider(channel, 'openai_compatible');
71
+ const listedModels = Array.isArray(listResult.models) ? listResult.models : [];
72
+ let result;
73
+
74
+ if (listedModels.length > 0) {
75
+ result = {
76
+ models: listedModels,
77
+ supported: true,
78
+ cached: !!listResult.cached,
79
+ fallbackUsed: false,
80
+ lastChecked: listResult.lastChecked || new Date().toISOString(),
81
+ error: null,
82
+ errorHint: null
83
+ };
84
+ } else {
85
+ const usingConfiguredProbe = !!listResult.disabledByConfig;
86
+ const probe = await probeModelAvailability(channel, gatewaySourceType, {
87
+ stopOnFirstAvailable: false
88
+ });
89
+ const probedModels = Array.isArray(probe.availableModels) ? probe.availableModels : [];
90
+
91
+ result = {
92
+ models: probedModels,
93
+ supported: probedModels.length > 0,
94
+ cached: !!probe.cached || !!listResult.cached,
95
+ fallbackUsed: false,
96
+ lastChecked: probe.lastChecked || listResult.lastChecked || new Date().toISOString(),
97
+ error: probedModels.length > 0 ? null : (listResult.error || '无法获取可用模型'),
98
+ errorHint: probedModels.length > 0
99
+ ? (usingConfiguredProbe ? '已按设置跳过 /v1/models,使用默认模型探测结果' : '模型列表接口不可用,已自动切换为模型探测结果')
100
+ : (listResult.errorHint || (usingConfiguredProbe
101
+ ? '已按设置跳过 /v1/models,且默认模型探测无可用结果'
102
+ : '模型列表接口不可用且模型探测无可用结果'))
103
+ };
104
+ }
62
105
 
63
106
  res.json({
64
107
  channelId: id,
108
+ gatewaySourceType,
65
109
  models: result.models,
66
110
  supported: result.supported,
67
111
  cached: result.cached,
@@ -90,17 +134,38 @@ module.exports = (config) => {
90
134
  return res.status(404).json({ error: 'Gemini CLI not installed' });
91
135
  }
92
136
 
93
- const { name, baseUrl, apiKey, model, websiteUrl, enabled, weight, maxConcurrency } = req.body;
137
+ const {
138
+ name,
139
+ baseUrl,
140
+ apiKey,
141
+ model,
142
+ websiteUrl,
143
+ enabled,
144
+ weight,
145
+ maxConcurrency,
146
+ modelRedirects,
147
+ speedTestModel,
148
+ presetId,
149
+ gatewaySourceType
150
+ } = req.body;
151
+
152
+ if (!name || !baseUrl) {
153
+ return res.status(400).json({ error: 'Missing required fields: name, baseUrl' });
154
+ }
94
155
 
95
- if (!name || !baseUrl || !apiKey) {
96
- return res.status(400).json({ error: 'Missing required fields: name, baseUrl, apiKey' });
156
+ if (!apiKey) {
157
+ return res.status(400).json({ error: 'Missing required fields: apiKey' });
97
158
  }
98
159
 
99
160
  const channel = createChannel(name, baseUrl, apiKey, model || 'gemini-2.5-pro', {
100
161
  websiteUrl,
101
162
  enabled,
102
163
  weight,
103
- maxConcurrency
164
+ maxConcurrency,
165
+ modelRedirects: modelRedirects || [],
166
+ speedTestModel: speedTestModel || null,
167
+ presetId: presetId || null,
168
+ gatewaySourceType
104
169
  });
105
170
  res.json(channel);
106
171
  broadcastSchedulerState('gemini', getSchedulerState('gemini'));
@@ -138,14 +203,14 @@ module.exports = (config) => {
138
203
  * DELETE /api/gemini/channels/:channelId
139
204
  * 删除渠道
140
205
  */
141
- router.delete('/:channelId', (req, res) => {
206
+ router.delete('/:channelId', async (req, res) => {
142
207
  try {
143
208
  if (!isGeminiInstalled()) {
144
209
  return res.status(404).json({ error: 'Gemini CLI not installed' });
145
210
  }
146
211
 
147
212
  const { channelId } = req.params;
148
- const result = deleteChannel(channelId);
213
+ const result = await deleteChannel(channelId);
149
214
  res.json(result);
150
215
  broadcastSchedulerState('gemini', getSchedulerState('gemini'));
151
216
  } catch (err) {
@@ -215,8 +280,10 @@ module.exports = (config) => {
215
280
  return res.status(404).json({ error: '渠道不存在' });
216
281
  }
217
282
 
218
- const result = await testChannelSpeed(channel, timeout, 'gemini');
283
+ const speedTestType = GEMINI_GATEWAY_SOURCE_TYPE;
284
+ const result = await testChannelSpeed(channel, timeout, speedTestType);
219
285
  result.level = getLatencyLevel(result.latency);
286
+ result.gatewaySourceType = speedTestType;
220
287
 
221
288
  res.json(result);
222
289
  } catch (error) {
@@ -235,18 +302,37 @@ module.exports = (config) => {
235
302
  return res.json({ results: [], message: 'Gemini CLI not installed' });
236
303
  }
237
304
 
238
- const { timeout = 10000 } = req.body;
305
+ const { timeout = 10000, concurrency } = req.body || {};
239
306
  const data = getChannels();
240
307
  const channels = data.channels || [];
308
+ const safeConcurrency = sanitizeBatchConcurrency(concurrency);
241
309
 
242
310
  if (channels.length === 0) {
243
311
  return res.json({ results: [], message: '没有可测试的渠道' });
244
312
  }
245
313
 
246
- // Gemini 渠道使用 'gemini' 类型
247
- const results = await testMultipleChannels(channels, timeout, 'gemini');
248
- results.forEach(r => {
249
- r.level = getLatencyLevel(r.latency);
314
+ const results = await runWithConcurrencyLimit(
315
+ channels,
316
+ safeConcurrency,
317
+ async channel => {
318
+ const speedTestType = GEMINI_GATEWAY_SOURCE_TYPE;
319
+ const result = await testChannelSpeed(channel, timeout, speedTestType);
320
+ result.level = getLatencyLevel(result.latency);
321
+ result.gatewaySourceType = speedTestType;
322
+ return result;
323
+ }
324
+ );
325
+
326
+ // 成功在前,成功结果按延迟升序
327
+ results.sort((a, b) => {
328
+ if (a.success && !b.success) return -1;
329
+ if (!a.success && b.success) return 1;
330
+ if (a.success && b.success) {
331
+ const aLatency = (a.latency === null || a.latency === undefined) ? Infinity : a.latency;
332
+ const bLatency = (b.latency === null || b.latency === undefined) ? Infinity : b.latency;
333
+ return aLatency - bLatency;
334
+ }
335
+ return 0;
250
336
  });
251
337
 
252
338
  res.json({
@@ -255,7 +341,8 @@ module.exports = (config) => {
255
341
  total: results.length,
256
342
  success: results.filter(r => r.success).length,
257
343
  failed: results.filter(r => !r.success).length,
258
- avgLatency: calculateAvgLatency(results)
344
+ avgLatency: calculateAvgLatency(results),
345
+ concurrency: safeConcurrency
259
346
  }
260
347
  });
261
348
  } catch (error) {
@@ -294,7 +381,9 @@ module.exports = (config) => {
294
381
 
295
382
  // 计算平均延迟
296
383
  function calculateAvgLatency(results) {
297
- const successResults = results.filter(r => r.success && r.latency);
384
+ const successResults = results.filter(
385
+ r => r.success && r.latency !== null && r.latency !== undefined
386
+ );
298
387
  if (successResults.length === 0) return null;
299
388
  const sum = successResults.reduce((acc, r) => acc + r.latency, 0);
300
389
  return Math.round(sum / successResults.length);
@@ -14,9 +14,9 @@ const {
14
14
  hasBackup
15
15
  } = require('../services/gemini-settings-manager');
16
16
  const { getChannels, getEnabledChannels } = require('../services/gemini-channels');
17
+ const { PATHS, ensureStorageDirMigrated } = require('../../config/paths');
17
18
  const fs = require('fs');
18
19
  const path = require('path');
19
- const os = require('os');
20
20
 
21
21
  function sanitizeChannel(channel) {
22
22
  if (!channel) return null;
@@ -26,14 +26,24 @@ function sanitizeChannel(channel) {
26
26
 
27
27
  // 保存激活渠道ID
28
28
  function saveActiveChannelId(channelId) {
29
- const dir = path.join(os.homedir(), '.claude', 'cc-tool');
29
+ ensureStorageDirMigrated();
30
+ const filePath = PATHS.activeChannel.gemini;
31
+ const dir = path.dirname(filePath);
30
32
  if (!fs.existsSync(dir)) {
31
33
  fs.mkdirSync(dir, { recursive: true });
32
34
  }
33
- const filePath = path.join(dir, 'gemini-active-channel.json');
34
35
  fs.writeFileSync(filePath, JSON.stringify({ activeChannelId: channelId }, null, 2), 'utf8');
35
36
  }
36
37
 
38
+ function removeActiveChannelFile() {
39
+ ensureStorageDirMigrated();
40
+ const filePath = PATHS.activeChannel.gemini;
41
+ if (fs.existsSync(filePath)) {
42
+ fs.unlinkSync(filePath);
43
+ console.log('[Gemini Proxy] Removed gemini-active-channel.json');
44
+ }
45
+ }
46
+
37
47
  // 获取代理状态
38
48
  router.get('/status', (req, res) => {
39
49
  try {
@@ -127,11 +137,7 @@ router.post('/stop', async (req, res) => {
127
137
  console.log('[Gemini Proxy] Restored settings from backup');
128
138
 
129
139
  // 删除 gemini-active-channel.json
130
- const activeChannelPath = path.join(os.homedir(), '.claude', 'cc-tool', 'gemini-active-channel.json');
131
- if (fs.existsSync(activeChannelPath)) {
132
- fs.unlinkSync(activeChannelPath);
133
- console.log('[Gemini Proxy] Removed gemini-active-channel.json');
134
- }
140
+ removeActiveChannelFile();
135
141
 
136
142
  const response = {
137
143
  success: true,
@@ -359,7 +359,7 @@ module.exports = (config) => {
359
359
 
360
360
  try {
361
361
  // 获取终端启动命令
362
- const { command, terminalId, terminalName } = getTerminalLaunchCommand(projectPath, null, geminiCommand);
362
+ const { command, terminalId, terminalName } = getTerminalLaunchCommand(projectPath, null, 'gemini', geminiCommand);
363
363
 
364
364
  console.log(`[Gemini] Launching terminal: ${terminalName} (${terminalId})`);
365
365
  console.log(`[Gemini] Resuming session: ${sessionId} (index ${resumeIndex})`);
@@ -7,10 +7,11 @@ module.exports = (config) => {
7
7
  /**
8
8
  * GET /api/health-check - 健康检查所有项目
9
9
  */
10
- router.get('/', (req, res) => {
10
+ router.get('/', async (req, res) => {
11
11
  try {
12
- const projects = getProjects(config);
13
- const result = healthCheckAllProjects(projects);
12
+ const projects = await getProjects(config);
13
+ const projectList = Array.isArray(projects) ? projects : [];
14
+ const result = healthCheckAllProjects(projectList);
14
15
 
15
16
  res.json({
16
17
  success: true,
@@ -177,7 +177,7 @@ router.post('/import/:platform', async (req, res) => {
177
177
  try {
178
178
  const { platform } = req.params;
179
179
 
180
- if (!['claude', 'codex', 'gemini'].includes(platform)) {
180
+ if (!['claude', 'codex', 'gemini', 'opencode'].includes(platform)) {
181
181
  return res.status(400).json({
182
182
  success: false,
183
183
  error: `无效的平台: ${platform}`
@@ -283,7 +283,7 @@ router.get('/export', (req, res) => {
283
283
  try {
284
284
  const format = req.query.format || 'json';
285
285
 
286
- if (!['json', 'claude', 'codex'].includes(format)) {
286
+ if (!['json', 'claude', 'codex', 'opencode'].includes(format)) {
287
287
  return res.status(400).json({
288
288
  success: false,
289
289
  error: `无效的导出格式: ${format}`
@@ -312,7 +312,7 @@ router.get('/export/download', (req, res) => {
312
312
  try {
313
313
  const format = req.query.format || 'json';
314
314
 
315
- if (!['json', 'claude', 'codex'].includes(format)) {
315
+ if (!['json', 'claude', 'codex', 'opencode'].includes(format)) {
316
316
  return res.status(400).json({
317
317
  success: false,
318
318
  error: `无效的导出格式: ${format}`