@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.
- package/CHANGELOG.md +15 -18
- package/README.md +8 -8
- package/dist/web/assets/ConfigTemplates-Bidwfdf2.css +1 -0
- package/dist/web/assets/ConfigTemplates-ZrK_s7ma.js +1 -0
- package/dist/web/assets/Home-B8YfhZ3c.js +1 -0
- package/dist/web/assets/Home-Di2qsylF.css +1 -0
- package/dist/web/assets/PluginManager-BD7QUZbU.js +1 -0
- package/dist/web/assets/PluginManager-ROyoZ-6m.css +1 -0
- package/dist/web/assets/ProjectList-C1fQb9OW.css +1 -0
- package/dist/web/assets/ProjectList-DRb1DuHV.js +1 -0
- package/dist/web/assets/SessionList-BGJWyneI.css +1 -0
- package/dist/web/assets/SessionList-lZ0LKzfT.js +1 -0
- package/dist/web/assets/SkillManager-C1xG5B4Q.js +1 -0
- package/dist/web/assets/SkillManager-D7pd-d_P.css +1 -0
- package/dist/web/assets/Terminal-DGNJeVtc.css +1 -0
- package/dist/web/assets/Terminal-DksBo_lM.js +1 -0
- package/dist/web/assets/WorkspaceManager-Burx7XOo.js +1 -0
- package/dist/web/assets/WorkspaceManager-CrwgQgmP.css +1 -0
- package/dist/web/assets/icons-kcfLIMBB.js +1 -0
- package/dist/web/assets/index-Ufv5rCa5.css +1 -0
- package/dist/web/assets/index-lAkrRC3h.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 +39 -2
- package/src/config/loader.js +74 -8
- 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 +419 -0
- package/src/server/api/opencode-projects.js +99 -0
- package/src/server/api/opencode-proxy.js +198 -0
- package/src/server/api/opencode-sessions.js +403 -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/skills.js +69 -18
- package/src/server/api/workspaces.js +78 -6
- package/src/server/codex-proxy-server.js +30 -18
- package/src/server/dev-server.js +1 -1
- package/src/server/gemini-proxy-server.js +15 -3
- package/src/server/index.js +165 -58
- package/src/server/opencode-proxy-server.js +4375 -0
- package/src/server/proxy-server.js +27 -18
- 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-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 +26 -12
- package/src/server/services/env-manager.js +126 -18
- 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 +206 -0
- package/src/server/services/opencode-gateway-converter.js +639 -0
- package/src/server/services/opencode-sessions.js +663 -0
- package/src/server/services/opencode-settings-manager.js +342 -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/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 +132 -3
- 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
package/src/server/api/agents.js
CHANGED
|
@@ -8,7 +8,24 @@ const express = require('express');
|
|
|
8
8
|
const { AgentsService } = require('../services/agents-service');
|
|
9
9
|
|
|
10
10
|
const router = express.Router();
|
|
11
|
-
const
|
|
11
|
+
const SUPPORTED_PLATFORMS = ['claude', 'opencode'];
|
|
12
|
+
const agentServices = new Map();
|
|
13
|
+
|
|
14
|
+
function resolvePlatform(rawPlatform) {
|
|
15
|
+
return SUPPORTED_PLATFORMS.includes(rawPlatform) ? rawPlatform : 'claude';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getPlatform(req) {
|
|
19
|
+
return resolvePlatform(req.query?.platform || req.body?.platform);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getAgentsService(req) {
|
|
23
|
+
const platform = getPlatform(req);
|
|
24
|
+
if (!agentServices.has(platform)) {
|
|
25
|
+
agentServices.set(platform, new AgentsService(platform));
|
|
26
|
+
}
|
|
27
|
+
return { platform, service: agentServices.get(platform) };
|
|
28
|
+
}
|
|
12
29
|
|
|
13
30
|
/**
|
|
14
31
|
* 获取代理列表
|
|
@@ -17,11 +34,13 @@ const agentsService = new AgentsService();
|
|
|
17
34
|
*/
|
|
18
35
|
router.get('/', (req, res) => {
|
|
19
36
|
try {
|
|
37
|
+
const { platform, service } = getAgentsService(req);
|
|
20
38
|
const { projectPath } = req.query;
|
|
21
|
-
const result =
|
|
39
|
+
const result = service.listAgents(projectPath || null);
|
|
22
40
|
|
|
23
41
|
res.json({
|
|
24
42
|
success: true,
|
|
43
|
+
platform,
|
|
25
44
|
...result
|
|
26
45
|
});
|
|
27
46
|
} catch (err) {
|
|
@@ -39,11 +58,13 @@ router.get('/', (req, res) => {
|
|
|
39
58
|
*/
|
|
40
59
|
router.get('/stats', (req, res) => {
|
|
41
60
|
try {
|
|
61
|
+
const { platform, service } = getAgentsService(req);
|
|
42
62
|
const { projectPath } = req.query;
|
|
43
|
-
const stats =
|
|
63
|
+
const stats = service.getStats(projectPath || null);
|
|
44
64
|
|
|
45
65
|
res.json({
|
|
46
66
|
success: true,
|
|
67
|
+
platform,
|
|
47
68
|
...stats
|
|
48
69
|
});
|
|
49
70
|
} catch (err) {
|
|
@@ -61,6 +82,7 @@ router.get('/stats', (req, res) => {
|
|
|
61
82
|
*/
|
|
62
83
|
router.get('/:scope/:fileName', (req, res) => {
|
|
63
84
|
try {
|
|
85
|
+
const { platform, service } = getAgentsService(req);
|
|
64
86
|
const { scope, fileName } = req.params;
|
|
65
87
|
const { projectPath } = req.query;
|
|
66
88
|
|
|
@@ -78,7 +100,7 @@ router.get('/:scope/:fileName', (req, res) => {
|
|
|
78
100
|
});
|
|
79
101
|
}
|
|
80
102
|
|
|
81
|
-
const agent =
|
|
103
|
+
const agent = service.getAgent(fileName, scope, projectPath || null);
|
|
82
104
|
|
|
83
105
|
if (!agent) {
|
|
84
106
|
return res.status(404).json({
|
|
@@ -89,6 +111,7 @@ router.get('/:scope/:fileName', (req, res) => {
|
|
|
89
111
|
|
|
90
112
|
res.json({
|
|
91
113
|
success: true,
|
|
114
|
+
platform,
|
|
92
115
|
agent
|
|
93
116
|
});
|
|
94
117
|
} catch (err) {
|
|
@@ -107,6 +130,7 @@ router.get('/:scope/:fileName', (req, res) => {
|
|
|
107
130
|
*/
|
|
108
131
|
router.post('/', (req, res) => {
|
|
109
132
|
try {
|
|
133
|
+
const { platform, service } = getAgentsService(req);
|
|
110
134
|
const { fileName, scope, projectPath, name, description, tools, model, permissionMode, skills, systemPrompt } = req.body;
|
|
111
135
|
|
|
112
136
|
if (!fileName) {
|
|
@@ -130,7 +154,7 @@ router.post('/', (req, res) => {
|
|
|
130
154
|
});
|
|
131
155
|
}
|
|
132
156
|
|
|
133
|
-
const agent =
|
|
157
|
+
const agent = service.createAgent({
|
|
134
158
|
fileName,
|
|
135
159
|
scope,
|
|
136
160
|
projectPath: projectPath || null,
|
|
@@ -145,6 +169,7 @@ router.post('/', (req, res) => {
|
|
|
145
169
|
|
|
146
170
|
res.json({
|
|
147
171
|
success: true,
|
|
172
|
+
platform,
|
|
148
173
|
agent,
|
|
149
174
|
message: '代理创建成功'
|
|
150
175
|
});
|
|
@@ -163,6 +188,7 @@ router.post('/', (req, res) => {
|
|
|
163
188
|
*/
|
|
164
189
|
router.put('/:scope/:fileName', (req, res) => {
|
|
165
190
|
try {
|
|
191
|
+
const { platform, service } = getAgentsService(req);
|
|
166
192
|
const { scope, fileName } = req.params;
|
|
167
193
|
const { projectPath, name, description, tools, model, permissionMode, skills, systemPrompt } = req.body;
|
|
168
194
|
|
|
@@ -180,7 +206,7 @@ router.put('/:scope/:fileName', (req, res) => {
|
|
|
180
206
|
});
|
|
181
207
|
}
|
|
182
208
|
|
|
183
|
-
const agent =
|
|
209
|
+
const agent = service.updateAgent({
|
|
184
210
|
fileName,
|
|
185
211
|
scope,
|
|
186
212
|
projectPath: projectPath || null,
|
|
@@ -195,6 +221,7 @@ router.put('/:scope/:fileName', (req, res) => {
|
|
|
195
221
|
|
|
196
222
|
res.json({
|
|
197
223
|
success: true,
|
|
224
|
+
platform,
|
|
198
225
|
agent,
|
|
199
226
|
message: '代理更新成功'
|
|
200
227
|
});
|
|
@@ -213,6 +240,7 @@ router.put('/:scope/:fileName', (req, res) => {
|
|
|
213
240
|
*/
|
|
214
241
|
router.delete('/:scope/:fileName', (req, res) => {
|
|
215
242
|
try {
|
|
243
|
+
const { platform, service } = getAgentsService(req);
|
|
216
244
|
const { scope, fileName } = req.params;
|
|
217
245
|
const { projectPath } = req.query;
|
|
218
246
|
|
|
@@ -230,9 +258,10 @@ router.delete('/:scope/:fileName', (req, res) => {
|
|
|
230
258
|
});
|
|
231
259
|
}
|
|
232
260
|
|
|
233
|
-
const result =
|
|
261
|
+
const result = service.deleteAgent(fileName, scope, projectPath || null);
|
|
234
262
|
|
|
235
263
|
res.json({
|
|
264
|
+
platform,
|
|
236
265
|
success: result.success,
|
|
237
266
|
message: result.message
|
|
238
267
|
});
|
|
@@ -254,12 +283,14 @@ router.delete('/:scope/:fileName', (req, res) => {
|
|
|
254
283
|
*/
|
|
255
284
|
router.get('/all', async (req, res) => {
|
|
256
285
|
try {
|
|
286
|
+
const { platform, service } = getAgentsService(req);
|
|
257
287
|
const { projectPath, refresh } = req.query;
|
|
258
288
|
const forceRefresh = refresh === '1';
|
|
259
|
-
const result = await
|
|
289
|
+
const result = await service.listAllAgents(projectPath || null, forceRefresh);
|
|
260
290
|
|
|
261
291
|
res.json({
|
|
262
292
|
success: true,
|
|
293
|
+
platform,
|
|
263
294
|
...result
|
|
264
295
|
});
|
|
265
296
|
} catch (err) {
|
|
@@ -277,9 +308,11 @@ router.get('/all', async (req, res) => {
|
|
|
277
308
|
*/
|
|
278
309
|
router.get('/repos', (req, res) => {
|
|
279
310
|
try {
|
|
280
|
-
const
|
|
311
|
+
const { platform, service } = getAgentsService(req);
|
|
312
|
+
const repos = service.getRepos();
|
|
281
313
|
res.json({
|
|
282
314
|
success: true,
|
|
315
|
+
platform,
|
|
283
316
|
repos
|
|
284
317
|
});
|
|
285
318
|
} catch (err) {
|
|
@@ -298,6 +331,7 @@ router.get('/repos', (req, res) => {
|
|
|
298
331
|
*/
|
|
299
332
|
router.post('/repos', (req, res) => {
|
|
300
333
|
try {
|
|
334
|
+
const { platform, service } = getAgentsService(req);
|
|
301
335
|
const { owner, name, branch = 'main', directory = '', enabled = true } = req.body;
|
|
302
336
|
|
|
303
337
|
if (!owner || !name) {
|
|
@@ -307,10 +341,11 @@ router.post('/repos', (req, res) => {
|
|
|
307
341
|
});
|
|
308
342
|
}
|
|
309
343
|
|
|
310
|
-
const repos =
|
|
344
|
+
const repos = service.addRepo({ owner, name, branch, directory, enabled });
|
|
311
345
|
|
|
312
346
|
res.json({
|
|
313
347
|
success: true,
|
|
348
|
+
platform,
|
|
314
349
|
repos
|
|
315
350
|
});
|
|
316
351
|
} catch (err) {
|
|
@@ -329,12 +364,14 @@ router.post('/repos', (req, res) => {
|
|
|
329
364
|
*/
|
|
330
365
|
router.delete('/repos/:owner/:name', (req, res) => {
|
|
331
366
|
try {
|
|
367
|
+
const { platform, service } = getAgentsService(req);
|
|
332
368
|
const { owner, name } = req.params;
|
|
333
369
|
const { directory = '' } = req.query;
|
|
334
|
-
const repos =
|
|
370
|
+
const repos = service.removeRepo(owner, name, directory);
|
|
335
371
|
|
|
336
372
|
res.json({
|
|
337
373
|
success: true,
|
|
374
|
+
platform,
|
|
338
375
|
repos
|
|
339
376
|
});
|
|
340
377
|
} catch (err) {
|
|
@@ -353,13 +390,15 @@ router.delete('/repos/:owner/:name', (req, res) => {
|
|
|
353
390
|
*/
|
|
354
391
|
router.put('/repos/:owner/:name/toggle', (req, res) => {
|
|
355
392
|
try {
|
|
393
|
+
const { platform, service } = getAgentsService(req);
|
|
356
394
|
const { owner, name } = req.params;
|
|
357
395
|
const { enabled, directory = '' } = req.body;
|
|
358
396
|
|
|
359
|
-
const repos =
|
|
397
|
+
const repos = service.toggleRepo(owner, name, directory, enabled);
|
|
360
398
|
|
|
361
399
|
res.json({
|
|
362
400
|
success: true,
|
|
401
|
+
platform,
|
|
363
402
|
repos
|
|
364
403
|
});
|
|
365
404
|
} catch (err) {
|
|
@@ -378,6 +417,7 @@ router.put('/repos/:owner/:name/toggle', (req, res) => {
|
|
|
378
417
|
*/
|
|
379
418
|
router.post('/install', async (req, res) => {
|
|
380
419
|
try {
|
|
420
|
+
const { platform, service } = getAgentsService(req);
|
|
381
421
|
const agent = req.body;
|
|
382
422
|
|
|
383
423
|
if (!agent || !agent.repoOwner || !agent.repoName) {
|
|
@@ -387,10 +427,11 @@ router.post('/install', async (req, res) => {
|
|
|
387
427
|
});
|
|
388
428
|
}
|
|
389
429
|
|
|
390
|
-
const result = await
|
|
430
|
+
const result = await service.installFromRemote(agent);
|
|
391
431
|
|
|
392
432
|
res.json({
|
|
393
433
|
success: true,
|
|
434
|
+
platform,
|
|
394
435
|
...result
|
|
395
436
|
});
|
|
396
437
|
} catch (err) {
|
|
@@ -409,6 +450,7 @@ router.post('/install', async (req, res) => {
|
|
|
409
450
|
*/
|
|
410
451
|
router.post('/uninstall', (req, res) => {
|
|
411
452
|
try {
|
|
453
|
+
const { platform, service } = getAgentsService(req);
|
|
412
454
|
const { fileName } = req.body;
|
|
413
455
|
|
|
414
456
|
if (!fileName) {
|
|
@@ -418,10 +460,11 @@ router.post('/uninstall', (req, res) => {
|
|
|
418
460
|
});
|
|
419
461
|
}
|
|
420
462
|
|
|
421
|
-
const result =
|
|
463
|
+
const result = service.uninstallAgent(fileName);
|
|
422
464
|
|
|
423
465
|
res.json({
|
|
424
466
|
success: true,
|
|
467
|
+
platform,
|
|
425
468
|
...result
|
|
426
469
|
});
|
|
427
470
|
} catch (err) {
|
|
@@ -12,10 +12,19 @@ const {
|
|
|
12
12
|
} = require('../services/channels');
|
|
13
13
|
const { getSchedulerState } = require('../services/channel-scheduler');
|
|
14
14
|
const { getChannelHealthStatus, getAllChannelHealthStatus, resetChannelHealth } = require('../services/channel-health');
|
|
15
|
-
const {
|
|
16
|
-
|
|
15
|
+
const {
|
|
16
|
+
testChannelSpeed,
|
|
17
|
+
getLatencyLevel,
|
|
18
|
+
sanitizeBatchConcurrency,
|
|
19
|
+
runWithConcurrencyLimit
|
|
20
|
+
} = require('../services/speed-test');
|
|
21
|
+
const {
|
|
22
|
+
probeModelAvailability,
|
|
23
|
+
fetchModelsFromProvider
|
|
24
|
+
} = require('../services/model-detector');
|
|
17
25
|
const { broadcastLog, broadcastProxyState, broadcastSchedulerState } = require('../websocket-server');
|
|
18
26
|
const { clearRedirectCache } = require('../proxy-server');
|
|
27
|
+
const CLAUDE_GATEWAY_SOURCE_TYPE = 'claude';
|
|
19
28
|
|
|
20
29
|
// GET /api/channels - Get all channels with health status
|
|
21
30
|
router.get('/', (req, res) => {
|
|
@@ -66,16 +75,40 @@ router.get('/current', (req, res) => {
|
|
|
66
75
|
// POST /api/channels - Create new channel
|
|
67
76
|
router.post('/', (req, res) => {
|
|
68
77
|
try {
|
|
69
|
-
const {
|
|
78
|
+
const {
|
|
79
|
+
name,
|
|
80
|
+
baseUrl,
|
|
81
|
+
apiKey,
|
|
82
|
+
websiteUrl,
|
|
83
|
+
enabled,
|
|
84
|
+
weight,
|
|
85
|
+
maxConcurrency,
|
|
86
|
+
presetId,
|
|
87
|
+
modelConfig,
|
|
88
|
+
modelRedirects,
|
|
89
|
+
proxyUrl,
|
|
90
|
+
speedTestModel,
|
|
91
|
+
gatewaySourceType
|
|
92
|
+
} = req.body;
|
|
93
|
+
|
|
94
|
+
if (!name || !baseUrl) {
|
|
95
|
+
return res.status(400).json({ error: 'Missing required fields: name, baseUrl' });
|
|
96
|
+
}
|
|
70
97
|
|
|
71
|
-
if (!
|
|
72
|
-
return res.status(400).json({ error: 'Missing required fields' });
|
|
98
|
+
if (!apiKey) {
|
|
99
|
+
return res.status(400).json({ error: 'Missing required fields: apiKey' });
|
|
73
100
|
}
|
|
74
101
|
|
|
75
102
|
const channel = createChannel(name, baseUrl, apiKey, websiteUrl, {
|
|
76
103
|
enabled,
|
|
77
104
|
weight,
|
|
78
|
-
maxConcurrency
|
|
105
|
+
maxConcurrency,
|
|
106
|
+
presetId,
|
|
107
|
+
modelConfig,
|
|
108
|
+
modelRedirects: modelRedirects || [],
|
|
109
|
+
proxyUrl: proxyUrl || '',
|
|
110
|
+
speedTestModel: speedTestModel || null,
|
|
111
|
+
gatewaySourceType
|
|
79
112
|
});
|
|
80
113
|
res.json({ channel });
|
|
81
114
|
broadcastSchedulerState('claude', getSchedulerState('claude'));
|
|
@@ -114,10 +147,10 @@ router.put('/:id', (req, res) => {
|
|
|
114
147
|
});
|
|
115
148
|
|
|
116
149
|
// DELETE /api/channels/:id - Delete channel
|
|
117
|
-
router.delete('/:id', (req, res) => {
|
|
150
|
+
router.delete('/:id', async (req, res) => {
|
|
118
151
|
try {
|
|
119
152
|
const { id } = req.params;
|
|
120
|
-
const result = deleteChannel(id);
|
|
153
|
+
const result = await deleteChannel(id);
|
|
121
154
|
res.json(result);
|
|
122
155
|
broadcastSchedulerState('claude', getSchedulerState('claude'));
|
|
123
156
|
} catch (error) {
|
|
@@ -213,9 +246,10 @@ router.post('/:id/speed-test', async (req, res) => {
|
|
|
213
246
|
return res.status(404).json({ error: '渠道不存在' });
|
|
214
247
|
}
|
|
215
248
|
|
|
216
|
-
|
|
217
|
-
const result = await testChannelSpeed(channel, timeout,
|
|
249
|
+
const speedTestType = CLAUDE_GATEWAY_SOURCE_TYPE;
|
|
250
|
+
const result = await testChannelSpeed(channel, timeout, speedTestType);
|
|
218
251
|
result.level = getLatencyLevel(result.latency);
|
|
252
|
+
result.gatewaySourceType = speedTestType;
|
|
219
253
|
|
|
220
254
|
res.json(result);
|
|
221
255
|
} catch (error) {
|
|
@@ -228,12 +262,6 @@ router.post('/:id/speed-test', async (req, res) => {
|
|
|
228
262
|
router.get('/:id/models', async (req, res) => {
|
|
229
263
|
try {
|
|
230
264
|
const { id } = req.params;
|
|
231
|
-
const VALID_CHANNEL_TYPES = ['claude', 'codex', 'gemini', 'openai_compatible'];
|
|
232
|
-
const { type = 'claude' } = req.query;
|
|
233
|
-
|
|
234
|
-
if (!VALID_CHANNEL_TYPES.includes(type)) {
|
|
235
|
-
return res.status(400).json({ error: 'Invalid channel type', channelId: id });
|
|
236
|
-
}
|
|
237
265
|
|
|
238
266
|
const channels = getAllChannels();
|
|
239
267
|
const channel = channels.find(ch => ch.id === id);
|
|
@@ -242,23 +270,53 @@ router.get('/:id/models', async (req, res) => {
|
|
|
242
270
|
return res.status(404).json({ error: '渠道不存在' });
|
|
243
271
|
}
|
|
244
272
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
273
|
+
const gatewaySourceType = CLAUDE_GATEWAY_SOURCE_TYPE;
|
|
274
|
+
const listResult = await fetchModelsFromProvider(channel, 'openai_compatible');
|
|
275
|
+
const listedModels = Array.isArray(listResult.models) ? listResult.models : [];
|
|
276
|
+
let result;
|
|
277
|
+
|
|
278
|
+
if (listedModels.length > 0) {
|
|
279
|
+
result = {
|
|
280
|
+
models: listedModels,
|
|
281
|
+
supported: true,
|
|
282
|
+
cached: !!listResult.cached,
|
|
283
|
+
fallbackUsed: false,
|
|
284
|
+
lastChecked: listResult.lastChecked || new Date().toISOString(),
|
|
285
|
+
error: null,
|
|
286
|
+
errorHint: null
|
|
287
|
+
};
|
|
288
|
+
} else {
|
|
289
|
+
const usingConfiguredProbe = !!listResult.disabledByConfig;
|
|
290
|
+
const probe = await probeModelAvailability(channel, gatewaySourceType, {
|
|
291
|
+
stopOnFirstAvailable: false
|
|
292
|
+
});
|
|
293
|
+
const probedModels = Array.isArray(probe.availableModels) ? probe.availableModels : [];
|
|
294
|
+
|
|
295
|
+
result = {
|
|
296
|
+
models: probedModels,
|
|
297
|
+
supported: probedModels.length > 0,
|
|
298
|
+
cached: !!probe.cached || !!listResult.cached,
|
|
299
|
+
fallbackUsed: probedModels.length > 0,
|
|
300
|
+
lastChecked: probe.lastChecked || listResult.lastChecked || new Date().toISOString(),
|
|
301
|
+
error: probedModels.length > 0 ? null : (listResult.error || '无法获取可用模型'),
|
|
302
|
+
errorHint: probedModels.length > 0
|
|
303
|
+
? (usingConfiguredProbe ? '已按设置跳过 /v1/models,使用默认模型探测结果' : '模型列表接口不可用,已使用模型探测结果')
|
|
304
|
+
: (listResult.errorHint || (usingConfiguredProbe
|
|
305
|
+
? '已按设置跳过 /v1/models,且默认模型探测无可用结果'
|
|
306
|
+
: '模型列表接口不可用且模型探测无可用结果'))
|
|
307
|
+
};
|
|
251
308
|
}
|
|
252
309
|
|
|
253
|
-
const result = await fetchModelsFromProvider(channel, channelType);
|
|
254
|
-
|
|
255
310
|
res.json({
|
|
256
311
|
channelId: id,
|
|
312
|
+
gatewaySourceType,
|
|
257
313
|
models: result.models,
|
|
258
314
|
supported: result.supported,
|
|
315
|
+
fallbackUsed: result.fallbackUsed,
|
|
259
316
|
cached: result.cached,
|
|
260
317
|
fetchedAt: result.lastChecked || new Date().toISOString(),
|
|
261
|
-
error: result.error
|
|
318
|
+
error: result.error,
|
|
319
|
+
errorHint: result.errorHint
|
|
262
320
|
});
|
|
263
321
|
} catch (error) {
|
|
264
322
|
console.error('Error fetching channel models:', error);
|
|
@@ -272,18 +330,36 @@ router.get('/:id/models', async (req, res) => {
|
|
|
272
330
|
// POST /api/channels/speed-test-all - Test all channels speed
|
|
273
331
|
router.post('/speed-test-all', async (req, res) => {
|
|
274
332
|
try {
|
|
275
|
-
const { timeout = 10000 } = req.body;
|
|
333
|
+
const { timeout = 10000, concurrency } = req.body || {};
|
|
276
334
|
const channels = getAllChannels();
|
|
335
|
+
const safeConcurrency = sanitizeBatchConcurrency(concurrency);
|
|
277
336
|
|
|
278
337
|
if (channels.length === 0) {
|
|
279
338
|
return res.json({ results: [], message: '没有可测试的渠道' });
|
|
280
339
|
}
|
|
281
340
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
341
|
+
const results = await runWithConcurrencyLimit(
|
|
342
|
+
channels,
|
|
343
|
+
safeConcurrency,
|
|
344
|
+
async channel => {
|
|
345
|
+
const speedTestType = CLAUDE_GATEWAY_SOURCE_TYPE;
|
|
346
|
+
const result = await testChannelSpeed(channel, timeout, speedTestType);
|
|
347
|
+
result.level = getLatencyLevel(result.latency);
|
|
348
|
+
result.gatewaySourceType = speedTestType;
|
|
349
|
+
return result;
|
|
350
|
+
}
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
// 与其他渠道保持一致:成功在前,成功结果按延迟升序
|
|
354
|
+
results.sort((a, b) => {
|
|
355
|
+
if (a.success && !b.success) return -1;
|
|
356
|
+
if (!a.success && b.success) return 1;
|
|
357
|
+
if (a.success && b.success) {
|
|
358
|
+
const aLatency = (a.latency === null || a.latency === undefined) ? Infinity : a.latency;
|
|
359
|
+
const bLatency = (b.latency === null || b.latency === undefined) ? Infinity : b.latency;
|
|
360
|
+
return aLatency - bLatency;
|
|
361
|
+
}
|
|
362
|
+
return 0;
|
|
287
363
|
});
|
|
288
364
|
|
|
289
365
|
res.json({
|
|
@@ -292,7 +368,8 @@ router.post('/speed-test-all', async (req, res) => {
|
|
|
292
368
|
total: results.length,
|
|
293
369
|
success: results.filter(r => r.success).length,
|
|
294
370
|
failed: results.filter(r => !r.success).length,
|
|
295
|
-
avgLatency: calculateAvgLatency(results)
|
|
371
|
+
avgLatency: calculateAvgLatency(results),
|
|
372
|
+
concurrency: safeConcurrency
|
|
296
373
|
}
|
|
297
374
|
});
|
|
298
375
|
} catch (error) {
|
|
@@ -303,7 +380,9 @@ router.post('/speed-test-all', async (req, res) => {
|
|
|
303
380
|
|
|
304
381
|
// 计算平均延迟
|
|
305
382
|
function calculateAvgLatency(results) {
|
|
306
|
-
const successResults = results.filter(
|
|
383
|
+
const successResults = results.filter(
|
|
384
|
+
r => r.success && r.latency !== null && r.latency !== undefined
|
|
385
|
+
);
|
|
307
386
|
if (successResults.length === 0) return null;
|
|
308
387
|
const sum = successResults.reduce((acc, r) => acc + r.latency, 0);
|
|
309
388
|
return Math.round(sum / successResults.length);
|