@adversity/coding-tool-x 3.0.0 → 3.0.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 +31 -0
- package/dist/web/assets/{index-AtwYwBZD.js → index-DfPKAt9R.js} +2 -2
- package/dist/web/assets/{index-BNHWEpD4.css → index-TjhcaFRe.css} +1 -1
- package/dist/web/assets/naive-ui-B1TP-0TP.js +1 -0
- package/dist/web/index.html +3 -3
- package/package.json +1 -1
- package/src/server/api/channels.js +52 -1
- package/src/server/api/config-export.js +7 -1
- package/src/server/api/proxy.js +63 -6
- package/src/server/services/channels.js +47 -3
- package/src/server/services/codex-channels.js +31 -0
- package/src/server/services/config-export-service.js +324 -1
- package/src/server/services/gemini-channels.js +95 -5
- package/src/server/services/model-detector.js +269 -0
- package/src/server/services/speed-test.js +72 -24
- package/dist/web/assets/naive-ui-BcSq2wzw.js +0 -1
package/dist/web/index.html
CHANGED
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
<link rel="icon" href="/favicon.ico">
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
7
|
<title>CC-TOOL - ClaudeCode增强工作助手</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-DfPKAt9R.js"></script>
|
|
9
9
|
<link rel="modulepreload" crossorigin href="/assets/vue-vendor-6JaYHOiI.js">
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/vendors-D2HHw_aW.js">
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/icons-BlzwYoRU.js">
|
|
12
|
-
<link rel="modulepreload" crossorigin href="/assets/naive-ui-
|
|
13
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
12
|
+
<link rel="modulepreload" crossorigin href="/assets/naive-ui-B1TP-0TP.js">
|
|
13
|
+
<link rel="stylesheet" crossorigin href="/assets/index-TjhcaFRe.css">
|
|
14
14
|
</head>
|
|
15
15
|
<body>
|
|
16
16
|
<div id="app"></div>
|
package/package.json
CHANGED
|
@@ -7,11 +7,13 @@ const {
|
|
|
7
7
|
updateChannel,
|
|
8
8
|
deleteChannel,
|
|
9
9
|
getCurrentSettings,
|
|
10
|
-
getBestChannelForRestore
|
|
10
|
+
getBestChannelForRestore,
|
|
11
|
+
updateClaudeSettingsWithModelConfig
|
|
11
12
|
} = require('../services/channels');
|
|
12
13
|
const { getSchedulerState } = require('../services/channel-scheduler');
|
|
13
14
|
const { getChannelHealthStatus, getAllChannelHealthStatus, resetChannelHealth } = require('../services/channel-health');
|
|
14
15
|
const { testChannelSpeed, testMultipleChannels, getLatencyLevel } = require('../services/speed-test');
|
|
16
|
+
const { fetchModelsFromProvider } = require('../services/model-detector');
|
|
15
17
|
const { broadcastLog, broadcastProxyState, broadcastSchedulerState } = require('../websocket-server');
|
|
16
18
|
|
|
17
19
|
// GET /api/channels - Get all channels with health status
|
|
@@ -147,6 +149,10 @@ router.post('/:id/apply-to-settings', async (req, res) => {
|
|
|
147
149
|
const { stopProxyServer } = require('../proxy-server');
|
|
148
150
|
await stopProxyServer({ clearStartTime: false });
|
|
149
151
|
|
|
152
|
+
// Re-apply channel settings after proxy stop to prevent race condition
|
|
153
|
+
// (stopProxyServer restores backup, then we overwrite it with current channel)
|
|
154
|
+
updateClaudeSettingsWithModelConfig(channel);
|
|
155
|
+
|
|
150
156
|
console.log(`✅ 已停���动态切换,默认使用当前渠道`);
|
|
151
157
|
broadcastLog({
|
|
152
158
|
type: 'action',
|
|
@@ -215,6 +221,51 @@ router.post('/:id/speed-test', async (req, res) => {
|
|
|
215
221
|
}
|
|
216
222
|
});
|
|
217
223
|
|
|
224
|
+
// GET /api/channels/:id/models - Get available models for a channel
|
|
225
|
+
router.get('/:id/models', async (req, res) => {
|
|
226
|
+
try {
|
|
227
|
+
const { id } = req.params;
|
|
228
|
+
const VALID_CHANNEL_TYPES = ['claude', 'codex', 'gemini', 'openai_compatible'];
|
|
229
|
+
const { type = 'claude' } = req.query;
|
|
230
|
+
|
|
231
|
+
if (!VALID_CHANNEL_TYPES.includes(type)) {
|
|
232
|
+
return res.status(400).json({ error: 'Invalid channel type', channelId: id });
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const channels = getAllChannels();
|
|
236
|
+
const channel = channels.find(ch => ch.id === id);
|
|
237
|
+
|
|
238
|
+
if (!channel) {
|
|
239
|
+
return res.status(404).json({ error: '渠道不存在' });
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// If no type specified or type is 'claude', auto-detect
|
|
243
|
+
let channelType = type;
|
|
244
|
+
if (!type || type === 'claude') {
|
|
245
|
+
const { detectChannelType } = require('../services/model-detector');
|
|
246
|
+
channelType = detectChannelType(channel);
|
|
247
|
+
console.log(`[API] Auto-detected channel type: ${channelType} for ${channel.name}`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const result = await fetchModelsFromProvider(channel, channelType);
|
|
251
|
+
|
|
252
|
+
res.json({
|
|
253
|
+
channelId: id,
|
|
254
|
+
models: result.models,
|
|
255
|
+
supported: result.supported,
|
|
256
|
+
cached: result.cached,
|
|
257
|
+
fetchedAt: result.lastChecked || new Date().toISOString(),
|
|
258
|
+
error: result.error
|
|
259
|
+
});
|
|
260
|
+
} catch (error) {
|
|
261
|
+
console.error('Error fetching channel models:', error);
|
|
262
|
+
res.status(500).json({
|
|
263
|
+
error: '获取模型列表失败',
|
|
264
|
+
channelId: req.params.id
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
|
|
218
269
|
// POST /api/channels/speed-test-all - Test all channels speed
|
|
219
270
|
router.post('/speed-test-all', async (req, res) => {
|
|
220
271
|
try {
|
|
@@ -25,7 +25,8 @@ function buildPreviewSummary(data) {
|
|
|
25
25
|
counts: {
|
|
26
26
|
permissionTemplates: (data.data.permissionTemplates || []).length,
|
|
27
27
|
configTemplates: (data.data.configTemplates || []).length,
|
|
28
|
-
channels: (data.data.channels || []).length
|
|
28
|
+
channels: (data.data.channels || []).length,
|
|
29
|
+
plugins: (data.data.plugins || []).length
|
|
29
30
|
},
|
|
30
31
|
items: {
|
|
31
32
|
permissionTemplates: (data.data.permissionTemplates || []).map(t => ({
|
|
@@ -42,6 +43,11 @@ function buildPreviewSummary(data) {
|
|
|
42
43
|
id: c.id,
|
|
43
44
|
name: c.name,
|
|
44
45
|
type: c.type
|
|
46
|
+
})),
|
|
47
|
+
plugins: (data.data.plugins || []).map(p => ({
|
|
48
|
+
name: p.name,
|
|
49
|
+
type: p.type,
|
|
50
|
+
version: p.version
|
|
45
51
|
}))
|
|
46
52
|
}
|
|
47
53
|
};
|
package/src/server/api/proxy.js
CHANGED
|
@@ -36,7 +36,23 @@ function saveActiveChannelId(channelId) {
|
|
|
36
36
|
fs.writeFileSync(filePath, JSON.stringify({ activeChannelId: channelId }, null, 2), 'utf8');
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
|
|
40
|
+
// 加载上次激活的渠道ID
|
|
41
|
+
function loadActiveChannelId() {
|
|
42
|
+
const filePath = path.join(os.homedir(), '.claude', 'cc-tool', 'active-channel.json');
|
|
43
|
+
try {
|
|
44
|
+
if (fs.existsSync(filePath)) {
|
|
45
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
46
|
+
const data = JSON.parse(content);
|
|
47
|
+
return data.activeChannelId || null;
|
|
48
|
+
}
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error('[Proxy] Error loading active channel ID:', error);
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 从 settings.json 找到当前激活的渠道(多级回退策略)
|
|
40
56
|
function findActiveChannelFromSettings() {
|
|
41
57
|
try {
|
|
42
58
|
const settings = readSettings();
|
|
@@ -49,25 +65,57 @@ function findActiveChannelFromSettings() {
|
|
|
49
65
|
|
|
50
66
|
// 如果 apiKey 仍为空,尝试从 apiKeyHelper 提取
|
|
51
67
|
if (!apiKey && settings?.apiKeyHelper) {
|
|
52
|
-
const match = settings.apiKeyHelper.match(/['"]([^'"]+)['"]/)
|
|
68
|
+
const match = settings.apiKeyHelper.match(/['\"]([^'\"]+)['\"]/)
|
|
53
69
|
if (match && match[1]) {
|
|
54
70
|
apiKey = match[1];
|
|
55
71
|
}
|
|
56
72
|
}
|
|
57
73
|
|
|
58
74
|
if (!baseUrl || !apiKey || baseUrl.includes('127.0.0.1')) {
|
|
75
|
+
console.log('[Proxy] Invalid settings: empty baseUrl/apiKey or localhost detected');
|
|
59
76
|
return null;
|
|
60
77
|
}
|
|
61
78
|
|
|
62
|
-
// 找到匹配的渠道
|
|
63
79
|
const channels = getAllChannels();
|
|
64
|
-
|
|
80
|
+
|
|
81
|
+
// Level 1: Exact match (baseUrl + apiKey)
|
|
82
|
+
let matchingChannel = channels.find(ch =>
|
|
65
83
|
ch.baseUrl === baseUrl && ch.apiKey === apiKey
|
|
66
84
|
);
|
|
67
85
|
|
|
68
|
-
|
|
86
|
+
if (matchingChannel) {
|
|
87
|
+
console.log(`[Proxy] Level 1 - Exact match: ${matchingChannel.name}`);
|
|
88
|
+
return matchingChannel;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Level 2: Match by baseUrl only (when apiKey differs)
|
|
92
|
+
matchingChannel = channels.find(ch => ch.baseUrl === baseUrl);
|
|
93
|
+
if (matchingChannel) {
|
|
94
|
+
console.log(`[Proxy] Level 2 - Matched by baseUrl only: ${matchingChannel.name}`);
|
|
95
|
+
return matchingChannel;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Level 3: Use active-channel.json for last known active channel
|
|
99
|
+
const activeChannelId = loadActiveChannelId();
|
|
100
|
+
if (activeChannelId) {
|
|
101
|
+
matchingChannel = channels.find(ch => ch.id === activeChannelId);
|
|
102
|
+
if (matchingChannel) {
|
|
103
|
+
console.log(`[Proxy] Level 3 - Using last active channel: ${matchingChannel.name}`);
|
|
104
|
+
return matchingChannel;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Level 4: Return first enabled channel as last resort
|
|
109
|
+
matchingChannel = channels.find(ch => ch.enabled !== false);
|
|
110
|
+
if (matchingChannel) {
|
|
111
|
+
console.log(`[Proxy] Level 4 - Using first enabled channel: ${matchingChannel.name}`);
|
|
112
|
+
return matchingChannel;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
console.log('[Proxy] No matching channel found after all fallback levels');
|
|
116
|
+
return null;
|
|
69
117
|
} catch (err) {
|
|
70
|
-
console.error('Error finding active channel:', err);
|
|
118
|
+
console.error('[Proxy] Error finding active channel:', err);
|
|
71
119
|
return null;
|
|
72
120
|
}
|
|
73
121
|
}
|
|
@@ -105,6 +153,15 @@ router.post('/start', async (req, res) => {
|
|
|
105
153
|
});
|
|
106
154
|
}
|
|
107
155
|
|
|
156
|
+
// Fix 4: Validate enabled channels count before starting proxy
|
|
157
|
+
const allChannels = getAllChannels();
|
|
158
|
+
const enabledCount = allChannels.filter(ch => ch.enabled !== false).length;
|
|
159
|
+
if (enabledCount === 0) {
|
|
160
|
+
return res.status(400).json({
|
|
161
|
+
error: '没有启用的渠道。请至少启用一个渠道后再启动动态切换。'
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
108
165
|
// 2. 从 settings.json 找到当前使用的渠道
|
|
109
166
|
const currentChannel = findActiveChannelFromSettings();
|
|
110
167
|
if (!currentChannel) {
|
|
@@ -187,7 +187,8 @@ function createChannel(name, baseUrl, apiKey, websiteUrl, extraConfig = {}) {
|
|
|
187
187
|
maxConcurrency: extraConfig.maxConcurrency,
|
|
188
188
|
presetId: extraConfig.presetId || 'official',
|
|
189
189
|
modelConfig: extraConfig.modelConfig || null,
|
|
190
|
-
proxyUrl: extraConfig.proxyUrl || ''
|
|
190
|
+
proxyUrl: extraConfig.proxyUrl || '',
|
|
191
|
+
speedTestModel: extraConfig.speedTestModel || null
|
|
191
192
|
});
|
|
192
193
|
|
|
193
194
|
data.channels.push(newChannel);
|
|
@@ -203,6 +204,9 @@ function updateChannel(id, updates) {
|
|
|
203
204
|
throw new Error('Channel not found');
|
|
204
205
|
}
|
|
205
206
|
|
|
207
|
+
// Store old channel data before updates
|
|
208
|
+
const oldChannel = { ...data.channels[index] };
|
|
209
|
+
|
|
206
210
|
const merged = { ...data.channels[index], ...updates };
|
|
207
211
|
data.channels[index] = applyChannelDefaults({
|
|
208
212
|
...merged,
|
|
@@ -211,10 +215,49 @@ function updateChannel(id, updates) {
|
|
|
211
215
|
enabled: merged.enabled,
|
|
212
216
|
presetId: merged.presetId,
|
|
213
217
|
modelConfig: merged.modelConfig,
|
|
214
|
-
proxyUrl: merged.proxyUrl
|
|
218
|
+
proxyUrl: merged.proxyUrl,
|
|
219
|
+
speedTestModel: merged.speedTestModel
|
|
215
220
|
});
|
|
216
221
|
|
|
222
|
+
// Get proxy status
|
|
223
|
+
const { getProxyStatus } = require('../proxy-server');
|
|
224
|
+
const proxyStatus = getProxyStatus();
|
|
225
|
+
const isProxyRunning = proxyStatus.running;
|
|
226
|
+
|
|
227
|
+
// Fix 1: Detect enabled toggle (false → true) when proxy is OFF
|
|
228
|
+
if (!isProxyRunning && !oldChannel.enabled && data.channels[index].enabled) {
|
|
229
|
+
console.log(`[Settings-sync] Proxy is OFF and channel "${data.channels[index].name}" was enabled, syncing settings.json...`);
|
|
230
|
+
updateClaudeSettingsWithModelConfig(data.channels[index]);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Fix 2: Single-channel enforcement when proxy is OFF
|
|
234
|
+
if (!isProxyRunning && data.channels[index].enabled && !oldChannel.enabled) {
|
|
235
|
+
// Disable all other channels
|
|
236
|
+
data.channels.forEach((ch, i) => {
|
|
237
|
+
if (i !== index && ch.enabled) {
|
|
238
|
+
ch.enabled = false;
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
console.log(`[Single-channel mode] Enabled "${data.channels[index].name}", disabled all others`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Fix 3: Prevent disabling last enabled channel when proxy is OFF
|
|
245
|
+
if (!isProxyRunning && !data.channels[index].enabled && oldChannel.enabled) {
|
|
246
|
+
const enabledCount = data.channels.filter(ch => ch.enabled).length;
|
|
247
|
+
if (enabledCount === 0) {
|
|
248
|
+
throw new Error('无法禁用最后一个启用的渠道。请先启用其他渠道或启动动态切换。');
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
217
252
|
saveChannels(data);
|
|
253
|
+
|
|
254
|
+
// Auto-sync settings.json if this is the currently active channel
|
|
255
|
+
const currentSettings = getCurrentSettings();
|
|
256
|
+
if (currentSettings && currentSettings.baseUrl === oldChannel.baseUrl) {
|
|
257
|
+
console.log(`[Auto-sync] Channel "${data.channels[index].name}" is active, syncing settings.json...`);
|
|
258
|
+
updateClaudeSettingsWithModelConfig(data.channels[index]);
|
|
259
|
+
}
|
|
260
|
+
|
|
218
261
|
return data.channels[index];
|
|
219
262
|
}
|
|
220
263
|
|
|
@@ -343,5 +386,6 @@ module.exports = {
|
|
|
343
386
|
deleteChannel,
|
|
344
387
|
applyChannelToSettings,
|
|
345
388
|
getBestChannelForRestore,
|
|
346
|
-
updateClaudeSettings
|
|
389
|
+
updateClaudeSettings,
|
|
390
|
+
updateClaudeSettingsWithModelConfig
|
|
347
391
|
};
|
|
@@ -226,6 +226,37 @@ function updateChannel(channelId, updates) {
|
|
|
226
226
|
};
|
|
227
227
|
|
|
228
228
|
data.channels[index] = newChannel;
|
|
229
|
+
|
|
230
|
+
// Get proxy status
|
|
231
|
+
const { getCodexProxyStatus } = require('../codex-proxy-server');
|
|
232
|
+
const proxyStatus = getCodexProxyStatus();
|
|
233
|
+
const isProxyRunning = proxyStatus.running;
|
|
234
|
+
|
|
235
|
+
// Fix 1: Detect enabled toggle (false → true) when proxy is OFF
|
|
236
|
+
if (!isProxyRunning && !oldChannel.enabled && data.channels[index].enabled) {
|
|
237
|
+
console.log(`[Codex Settings-sync] Proxy is OFF and channel "${data.channels[index].name}" was enabled, syncing config.toml...`);
|
|
238
|
+
applyChannelToSettings(channelId);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Fix 2: Single-channel enforcement when proxy is OFF
|
|
242
|
+
if (!isProxyRunning && data.channels[index].enabled && !oldChannel.enabled) {
|
|
243
|
+
// Disable all other channels
|
|
244
|
+
data.channels.forEach((ch, i) => {
|
|
245
|
+
if (i !== index && ch.enabled) {
|
|
246
|
+
ch.enabled = false;
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
console.log(`[Codex Single-channel mode] Enabled "${data.channels[index].name}", disabled all others`);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Fix 3: Prevent disabling last enabled channel when proxy is OFF
|
|
253
|
+
if (!isProxyRunning && !data.channels[index].enabled && oldChannel.enabled) {
|
|
254
|
+
const enabledCount = data.channels.filter(ch => ch.enabled).length;
|
|
255
|
+
if (enabledCount === 0) {
|
|
256
|
+
throw new Error('无法禁用最后一个启用的渠道。请先启用其他渠道或启动动态切换。');
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
229
260
|
saveChannels(data);
|
|
230
261
|
|
|
231
262
|
// 处理环境变量更新
|