coding-tool-x 3.2.0
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 +599 -0
- package/LICENSE +21 -0
- package/README.md +439 -0
- package/bin/ctx.js +8 -0
- package/dist/web/assets/Analytics-DN_YsnkW.js +39 -0
- package/dist/web/assets/Analytics-DuYvId7u.css +1 -0
- package/dist/web/assets/ConfigTemplates-Bidwfdf2.css +1 -0
- package/dist/web/assets/ConfigTemplates-DpXIMy0p.js +1 -0
- package/dist/web/assets/Home-38JTUlYt.js +1 -0
- package/dist/web/assets/Home-CjupSEWE.css +1 -0
- package/dist/web/assets/PluginManager-CX2tgq2H.js +1 -0
- package/dist/web/assets/PluginManager-ROyoZ-6m.css +1 -0
- package/dist/web/assets/ProjectList-C1lDcsn6.js +1 -0
- package/dist/web/assets/ProjectList-oJIyIRkP.css +1 -0
- package/dist/web/assets/SessionList-C55tjV7i.css +1 -0
- package/dist/web/assets/SessionList-CZ7T6rVx.js +1 -0
- package/dist/web/assets/SkillManager-D7pd-d_P.css +1 -0
- package/dist/web/assets/SkillManager-DLN9f79y.js +1 -0
- package/dist/web/assets/WorkspaceManager-CrwgQgmP.css +1 -0
- package/dist/web/assets/WorkspaceManager-DxlHZkpZ.js +1 -0
- package/dist/web/assets/icons-DRrXwWZi.js +1 -0
- package/dist/web/assets/index-CetESrXw.css +1 -0
- package/dist/web/assets/index-Cfvn-2Gb.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-DlpKk-8M.js +1 -0
- package/dist/web/assets/vendors-DMjSfzlv.js +7 -0
- package/dist/web/assets/vue-vendor-DET08QYg.js +45 -0
- package/dist/web/favicon.ico +0 -0
- package/dist/web/index.html +20 -0
- package/dist/web/logo.png +0 -0
- package/docs/bannel.png +0 -0
- package/docs/home.png +0 -0
- package/docs/logo.png +0 -0
- package/docs/model-redirection.md +251 -0
- package/docs/multi-channel-load-balancing.md +249 -0
- package/package.json +80 -0
- package/src/commands/channels.js +551 -0
- package/src/commands/cli-type.js +101 -0
- package/src/commands/daemon.js +365 -0
- package/src/commands/doctor.js +333 -0
- package/src/commands/export-config.js +205 -0
- package/src/commands/list.js +222 -0
- package/src/commands/logs.js +261 -0
- package/src/commands/plugin.js +585 -0
- package/src/commands/port-config.js +135 -0
- package/src/commands/proxy-control.js +264 -0
- package/src/commands/proxy.js +152 -0
- package/src/commands/resume.js +137 -0
- package/src/commands/search.js +190 -0
- package/src/commands/security.js +37 -0
- package/src/commands/stats.js +398 -0
- package/src/commands/switch.js +48 -0
- package/src/commands/toggle-proxy.js +247 -0
- package/src/commands/ui.js +99 -0
- package/src/commands/update.js +97 -0
- package/src/commands/workspace.js +454 -0
- package/src/config/default.js +69 -0
- package/src/config/loader.js +149 -0
- package/src/config/model-metadata.js +167 -0
- package/src/config/model-metadata.json +125 -0
- package/src/config/model-pricing.js +35 -0
- package/src/config/paths.js +190 -0
- package/src/index.js +680 -0
- package/src/plugins/constants.js +15 -0
- package/src/plugins/event-bus.js +54 -0
- package/src/plugins/manifest-validator.js +129 -0
- package/src/plugins/plugin-api.js +128 -0
- package/src/plugins/plugin-installer.js +601 -0
- package/src/plugins/plugin-loader.js +229 -0
- package/src/plugins/plugin-manager.js +170 -0
- package/src/plugins/registry.js +152 -0
- package/src/plugins/schema/plugin-manifest.json +115 -0
- package/src/reset-config.js +94 -0
- package/src/server/api/agents.js +826 -0
- package/src/server/api/aliases.js +36 -0
- package/src/server/api/channels.js +368 -0
- package/src/server/api/claude-hooks.js +480 -0
- package/src/server/api/codex-channels.js +417 -0
- package/src/server/api/codex-projects.js +104 -0
- package/src/server/api/codex-proxy.js +195 -0
- package/src/server/api/codex-sessions.js +483 -0
- package/src/server/api/codex-statistics.js +57 -0
- package/src/server/api/commands.js +482 -0
- package/src/server/api/config-export.js +212 -0
- package/src/server/api/config-registry.js +357 -0
- package/src/server/api/config-sync.js +155 -0
- package/src/server/api/config-templates.js +248 -0
- package/src/server/api/config.js +521 -0
- package/src/server/api/convert.js +260 -0
- package/src/server/api/dashboard.js +142 -0
- package/src/server/api/env.js +144 -0
- package/src/server/api/favorites.js +77 -0
- package/src/server/api/gemini-channels.js +366 -0
- package/src/server/api/gemini-projects.js +91 -0
- package/src/server/api/gemini-proxy.js +173 -0
- package/src/server/api/gemini-sessions.js +376 -0
- package/src/server/api/gemini-statistics.js +57 -0
- package/src/server/api/health-check.js +31 -0
- package/src/server/api/mcp.js +399 -0
- 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 +207 -0
- package/src/server/api/opencode-sessions.js +327 -0
- package/src/server/api/opencode-statistics.js +57 -0
- package/src/server/api/plugins.js +463 -0
- package/src/server/api/pm2-autostart.js +269 -0
- package/src/server/api/projects.js +124 -0
- package/src/server/api/prompts.js +279 -0
- package/src/server/api/proxy.js +306 -0
- package/src/server/api/security.js +53 -0
- package/src/server/api/sessions.js +514 -0
- package/src/server/api/settings.js +142 -0
- package/src/server/api/skills.js +570 -0
- package/src/server/api/statistics.js +238 -0
- package/src/server/api/ui-config.js +64 -0
- package/src/server/api/workspaces.js +456 -0
- package/src/server/codex-proxy-server.js +681 -0
- package/src/server/dev-server.js +26 -0
- package/src/server/gemini-proxy-server.js +610 -0
- package/src/server/index.js +422 -0
- package/src/server/opencode-proxy-server.js +4771 -0
- package/src/server/proxy-server.js +669 -0
- package/src/server/services/agents-service.js +1137 -0
- package/src/server/services/alias.js +71 -0
- package/src/server/services/channel-health.js +234 -0
- package/src/server/services/channel-scheduler.js +240 -0
- package/src/server/services/channels.js +447 -0
- package/src/server/services/codex-channels.js +705 -0
- package/src/server/services/codex-config.js +90 -0
- package/src/server/services/codex-parser.js +322 -0
- package/src/server/services/codex-sessions.js +936 -0
- package/src/server/services/codex-settings-manager.js +619 -0
- package/src/server/services/codex-speed-test-template.json +24 -0
- package/src/server/services/codex-statistics-service.js +161 -0
- package/src/server/services/commands-service.js +574 -0
- package/src/server/services/config-export-service.js +1165 -0
- package/src/server/services/config-registry-service.js +828 -0
- package/src/server/services/config-sync-manager.js +941 -0
- package/src/server/services/config-sync-service.js +504 -0
- package/src/server/services/config-templates-service.js +913 -0
- package/src/server/services/enhanced-cache.js +196 -0
- package/src/server/services/env-checker.js +409 -0
- package/src/server/services/env-manager.js +436 -0
- package/src/server/services/favorites.js +165 -0
- package/src/server/services/format-converter.js +620 -0
- package/src/server/services/gemini-channels.js +459 -0
- package/src/server/services/gemini-config.js +73 -0
- package/src/server/services/gemini-sessions.js +689 -0
- package/src/server/services/gemini-settings-manager.js +263 -0
- package/src/server/services/gemini-statistics-service.js +157 -0
- package/src/server/services/health-check.js +85 -0
- package/src/server/services/mcp-client.js +790 -0
- package/src/server/services/mcp-service.js +1732 -0
- package/src/server/services/model-detector.js +1245 -0
- package/src/server/services/network-access.js +80 -0
- package/src/server/services/opencode-channels.js +366 -0
- package/src/server/services/opencode-gateway-adapters.js +1168 -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 +161 -0
- package/src/server/services/plugins-service.js +1268 -0
- package/src/server/services/prompts-service.js +534 -0
- package/src/server/services/proxy-runtime.js +79 -0
- package/src/server/services/repo-scanner-base.js +708 -0
- package/src/server/services/request-logger.js +130 -0
- package/src/server/services/response-decoder.js +21 -0
- package/src/server/services/security-config.js +131 -0
- package/src/server/services/session-cache.js +127 -0
- package/src/server/services/session-converter.js +577 -0
- package/src/server/services/sessions.js +900 -0
- package/src/server/services/settings-manager.js +163 -0
- package/src/server/services/skill-service.js +1482 -0
- package/src/server/services/speed-test.js +1146 -0
- package/src/server/services/statistics-service.js +1043 -0
- package/src/server/services/ui-config.js +132 -0
- package/src/server/services/workspace-service.js +830 -0
- package/src/server/utils/pricing.js +73 -0
- package/src/server/websocket-server.js +513 -0
- package/src/ui/menu.js +139 -0
- package/src/ui/prompts.js +100 -0
- package/src/utils/format.js +43 -0
- package/src/utils/port-helper.js +108 -0
- package/src/utils/session.js +240 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const router = express.Router();
|
|
3
|
+
const { setAlias, deleteAlias } = require('../services/alias');
|
|
4
|
+
|
|
5
|
+
module.exports = () => {
|
|
6
|
+
// POST /api/aliases - Set alias for a session
|
|
7
|
+
router.post('/', (req, res) => {
|
|
8
|
+
try {
|
|
9
|
+
const { sessionId, alias } = req.body;
|
|
10
|
+
|
|
11
|
+
if (!sessionId || !alias) {
|
|
12
|
+
return res.status(400).json({ error: 'sessionId and alias are required' });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const aliases = setAlias(sessionId, alias);
|
|
16
|
+
res.json({ success: true, aliases });
|
|
17
|
+
} catch (error) {
|
|
18
|
+
console.error('Error setting alias:', error);
|
|
19
|
+
res.status(500).json({ error: error.message });
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// DELETE /api/aliases/:sessionId - Delete alias
|
|
24
|
+
router.delete('/:sessionId', (req, res) => {
|
|
25
|
+
try {
|
|
26
|
+
const { sessionId } = req.params;
|
|
27
|
+
const aliases = deleteAlias(sessionId);
|
|
28
|
+
res.json({ success: true, aliases });
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.error('Error deleting alias:', error);
|
|
31
|
+
res.status(500).json({ error: error.message });
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
return router;
|
|
36
|
+
};
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const router = express.Router();
|
|
3
|
+
const {
|
|
4
|
+
getAllChannels,
|
|
5
|
+
applyChannelToSettings,
|
|
6
|
+
createChannel,
|
|
7
|
+
updateChannel,
|
|
8
|
+
deleteChannel,
|
|
9
|
+
getCurrentSettings,
|
|
10
|
+
getBestChannelForRestore,
|
|
11
|
+
updateClaudeSettingsWithModelConfig
|
|
12
|
+
} = require('../services/channels');
|
|
13
|
+
const { getSchedulerState } = require('../services/channel-scheduler');
|
|
14
|
+
const { getChannelHealthStatus, getAllChannelHealthStatus, resetChannelHealth } = require('../services/channel-health');
|
|
15
|
+
const {
|
|
16
|
+
testChannelSpeed,
|
|
17
|
+
getLatencyLevel,
|
|
18
|
+
sanitizeBatchConcurrency,
|
|
19
|
+
runWithConcurrencyLimit
|
|
20
|
+
} = require('../services/speed-test');
|
|
21
|
+
const { getDefaultSpeedTestModelByToolType } = require('../../config/model-metadata');
|
|
22
|
+
const { broadcastLog, broadcastProxyState, broadcastSchedulerState } = require('../websocket-server');
|
|
23
|
+
const { clearRedirectCache } = require('../proxy-server');
|
|
24
|
+
const CLAUDE_GATEWAY_SOURCE_TYPE = 'claude';
|
|
25
|
+
|
|
26
|
+
function getDefaultClaudeModel() {
|
|
27
|
+
return getDefaultSpeedTestModelByToolType('claude');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// GET /api/channels - Get all channels with health status
|
|
31
|
+
router.get('/', (req, res) => {
|
|
32
|
+
try {
|
|
33
|
+
const channels = getAllChannels();
|
|
34
|
+
// 为每个渠道附加健康状态
|
|
35
|
+
const channelsWithHealth = channels.map(ch => ({
|
|
36
|
+
...ch,
|
|
37
|
+
health: getChannelHealthStatus(ch.id)
|
|
38
|
+
}));
|
|
39
|
+
res.json({ channels: channelsWithHealth });
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error('Error fetching channels:', error);
|
|
42
|
+
res.status(500).json({ error: error.message });
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
router.get('/pool/status', (req, res) => {
|
|
47
|
+
try {
|
|
48
|
+
const source = req.query.source || 'claude';
|
|
49
|
+
const scheduler = getSchedulerState(source);
|
|
50
|
+
res.json({ source, scheduler });
|
|
51
|
+
} catch (error) {
|
|
52
|
+
res.status(500).json({ error: error.message });
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// GET /api/channels/current - Get current settings
|
|
57
|
+
router.get('/current', (req, res) => {
|
|
58
|
+
try {
|
|
59
|
+
const settings = getCurrentSettings();
|
|
60
|
+
const channels = getAllChannels();
|
|
61
|
+
let currentChannel = null;
|
|
62
|
+
|
|
63
|
+
if (settings) {
|
|
64
|
+
currentChannel = channels.find(ch =>
|
|
65
|
+
ch.baseUrl === settings.baseUrl && ch.apiKey === settings.apiKey
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
res.json({ channel: currentChannel, settings });
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error('Error fetching current settings:', error);
|
|
72
|
+
res.status(500).json({ error: error.message });
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// POST /api/channels - Create new channel
|
|
77
|
+
router.post('/', (req, res) => {
|
|
78
|
+
try {
|
|
79
|
+
const {
|
|
80
|
+
name,
|
|
81
|
+
baseUrl,
|
|
82
|
+
apiKey,
|
|
83
|
+
websiteUrl,
|
|
84
|
+
enabled,
|
|
85
|
+
weight,
|
|
86
|
+
maxConcurrency,
|
|
87
|
+
presetId,
|
|
88
|
+
modelConfig,
|
|
89
|
+
modelRedirects,
|
|
90
|
+
proxyUrl,
|
|
91
|
+
speedTestModel,
|
|
92
|
+
gatewaySourceType
|
|
93
|
+
} = req.body;
|
|
94
|
+
|
|
95
|
+
if (!name || !baseUrl) {
|
|
96
|
+
return res.status(400).json({ error: 'Missing required fields: name, baseUrl' });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!apiKey) {
|
|
100
|
+
return res.status(400).json({ error: 'Missing required fields: apiKey' });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const channel = createChannel(name, baseUrl, apiKey, websiteUrl, {
|
|
104
|
+
enabled,
|
|
105
|
+
weight,
|
|
106
|
+
maxConcurrency,
|
|
107
|
+
presetId,
|
|
108
|
+
modelConfig,
|
|
109
|
+
modelRedirects: modelRedirects || [],
|
|
110
|
+
proxyUrl: proxyUrl || '',
|
|
111
|
+
speedTestModel: speedTestModel || null,
|
|
112
|
+
gatewaySourceType
|
|
113
|
+
});
|
|
114
|
+
res.json({ channel });
|
|
115
|
+
broadcastSchedulerState('claude', getSchedulerState('claude'));
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.error('Error creating channel:', error);
|
|
118
|
+
res.status(500).json({ error: error.message });
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// GET /api/channels/best-for-restore - Get best channel for restore (must be before /:id)
|
|
123
|
+
router.get('/best-for-restore', (req, res) => {
|
|
124
|
+
try {
|
|
125
|
+
const channel = getBestChannelForRestore();
|
|
126
|
+
res.json({ channel });
|
|
127
|
+
} catch (error) {
|
|
128
|
+
console.error('Error getting best channel for restore:', error);
|
|
129
|
+
res.status(500).json({ error: error.message });
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// PUT /api/channels/:id - Update channel
|
|
134
|
+
router.put('/:id', (req, res) => {
|
|
135
|
+
try {
|
|
136
|
+
const { id } = req.params;
|
|
137
|
+
const updates = req.body;
|
|
138
|
+
|
|
139
|
+
const channel = updateChannel(id, updates);
|
|
140
|
+
// 清除该渠道的模型重定向日志缓存,使下次请求时重新打印
|
|
141
|
+
clearRedirectCache(id);
|
|
142
|
+
res.json({ channel });
|
|
143
|
+
broadcastSchedulerState('claude', getSchedulerState('claude'));
|
|
144
|
+
} catch (error) {
|
|
145
|
+
console.error('Error updating channel:', error);
|
|
146
|
+
res.status(500).json({ error: error.message });
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// DELETE /api/channels/:id - Delete channel
|
|
151
|
+
router.delete('/:id', async (req, res) => {
|
|
152
|
+
try {
|
|
153
|
+
const { id } = req.params;
|
|
154
|
+
const result = await deleteChannel(id);
|
|
155
|
+
res.json(result);
|
|
156
|
+
broadcastSchedulerState('claude', getSchedulerState('claude'));
|
|
157
|
+
} catch (error) {
|
|
158
|
+
console.error('Error deleting channel:', error);
|
|
159
|
+
res.status(500).json({ error: error.message });
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
router.post('/:id/apply-to-settings', async (req, res) => {
|
|
164
|
+
try {
|
|
165
|
+
const { id } = req.params;
|
|
166
|
+
const channel = applyChannelToSettings(id);
|
|
167
|
+
|
|
168
|
+
// Check if proxy is running
|
|
169
|
+
const { getProxyStatus } = require('../proxy-server');
|
|
170
|
+
const proxyStatus = getProxyStatus();
|
|
171
|
+
|
|
172
|
+
broadcastLog({
|
|
173
|
+
type: 'action',
|
|
174
|
+
action: 'apply_settings',
|
|
175
|
+
message: `已将 (${channel.name}) 渠道写入配置文件中`,
|
|
176
|
+
channelName: channel.name,
|
|
177
|
+
timestamp: Date.now(),
|
|
178
|
+
source: 'claude'
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Stop proxy if running
|
|
182
|
+
if (proxyStatus && proxyStatus.running) {
|
|
183
|
+
console.log(`Proxy is running, stopping to apply channel settings: ${channel.name}`);
|
|
184
|
+
|
|
185
|
+
// Stop proxy and restore backup
|
|
186
|
+
const { stopProxyServer } = require('../proxy-server');
|
|
187
|
+
await stopProxyServer({ clearStartTime: false });
|
|
188
|
+
|
|
189
|
+
// Re-apply channel settings after proxy stop to prevent race condition
|
|
190
|
+
// (stopProxyServer restores backup, then we overwrite it with current channel)
|
|
191
|
+
updateClaudeSettingsWithModelConfig(channel);
|
|
192
|
+
|
|
193
|
+
console.log(`✅ 已停���动态切换,默认使用当前渠道`);
|
|
194
|
+
broadcastLog({
|
|
195
|
+
type: 'action',
|
|
196
|
+
action: 'stop_proxy',
|
|
197
|
+
message: `已停止动态切换,默认使用当前渠道`,
|
|
198
|
+
timestamp: Date.now(),
|
|
199
|
+
source: 'claude'
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// 广播代理状态更新,通知前端代理已停止
|
|
203
|
+
const { broadcastProxyState } = require('../websocket-server');
|
|
204
|
+
broadcastProxyState('claude', {
|
|
205
|
+
running: false,
|
|
206
|
+
port: null,
|
|
207
|
+
runtime: null,
|
|
208
|
+
startTime: null
|
|
209
|
+
}, null, getAllChannels());
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
res.json({
|
|
213
|
+
message: `已将 (${channel.name}) 渠道写入配置文件中`,
|
|
214
|
+
channel
|
|
215
|
+
});
|
|
216
|
+
} catch (error) {
|
|
217
|
+
console.error('Error applying channel to settings:', error);
|
|
218
|
+
res.status(500).json({ error: error.message });
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// POST /api/channels/:id/reset-health - Reset channel health status
|
|
223
|
+
router.post('/:id/reset-health', (req, res) => {
|
|
224
|
+
try {
|
|
225
|
+
const { id } = req.params;
|
|
226
|
+
resetChannelHealth(id, 'claude');
|
|
227
|
+
res.json({
|
|
228
|
+
success: true,
|
|
229
|
+
message: '渠道健康状态已重置',
|
|
230
|
+
health: getChannelHealthStatus(id)
|
|
231
|
+
});
|
|
232
|
+
} catch (error) {
|
|
233
|
+
console.error('Error resetting channel health:', error);
|
|
234
|
+
res.status(500).json({ error: error.message });
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// POST /api/channels/:id/speed-test - Test single channel speed
|
|
239
|
+
router.post('/:id/speed-test', async (req, res) => {
|
|
240
|
+
try {
|
|
241
|
+
const { id } = req.params;
|
|
242
|
+
const { timeout = 10000 } = req.body;
|
|
243
|
+
const channels = getAllChannels();
|
|
244
|
+
const channel = channels.find(ch => ch.id === id);
|
|
245
|
+
|
|
246
|
+
if (!channel) {
|
|
247
|
+
return res.status(404).json({ error: '渠道不存在' });
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const speedTestType = CLAUDE_GATEWAY_SOURCE_TYPE;
|
|
251
|
+
const result = await testChannelSpeed(channel, timeout, speedTestType);
|
|
252
|
+
result.level = getLatencyLevel(result.latency);
|
|
253
|
+
result.gatewaySourceType = speedTestType;
|
|
254
|
+
|
|
255
|
+
res.json(result);
|
|
256
|
+
} catch (error) {
|
|
257
|
+
console.error('Error testing channel speed:', error);
|
|
258
|
+
res.status(500).json({ error: error.message });
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// GET /api/channels/:id/models - Get available models for a channel
|
|
263
|
+
router.get('/:id/models', async (req, res) => {
|
|
264
|
+
try {
|
|
265
|
+
const { id } = req.params;
|
|
266
|
+
|
|
267
|
+
const channels = getAllChannels();
|
|
268
|
+
const channel = channels.find(ch => ch.id === id);
|
|
269
|
+
|
|
270
|
+
if (!channel) {
|
|
271
|
+
return res.status(404).json({ error: '渠道不存在' });
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const gatewaySourceType = CLAUDE_GATEWAY_SOURCE_TYPE;
|
|
275
|
+
const models = [getDefaultClaudeModel()];
|
|
276
|
+
const now = new Date().toISOString();
|
|
277
|
+
const result = {
|
|
278
|
+
models,
|
|
279
|
+
supported: models.length > 0,
|
|
280
|
+
cached: false,
|
|
281
|
+
fallbackUsed: false,
|
|
282
|
+
lastChecked: now,
|
|
283
|
+
error: models.length > 0 ? null : '未配置默认模型列表',
|
|
284
|
+
errorHint: models.length > 0 ? null : '请在设置中配置 Claude 默认模型'
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
res.json({
|
|
288
|
+
channelId: id,
|
|
289
|
+
gatewaySourceType,
|
|
290
|
+
models: result.models,
|
|
291
|
+
supported: result.supported,
|
|
292
|
+
fallbackUsed: result.fallbackUsed,
|
|
293
|
+
cached: result.cached,
|
|
294
|
+
fetchedAt: result.lastChecked || new Date().toISOString(),
|
|
295
|
+
error: result.error,
|
|
296
|
+
errorHint: result.errorHint
|
|
297
|
+
});
|
|
298
|
+
} catch (error) {
|
|
299
|
+
console.error('Error fetching channel models:', error);
|
|
300
|
+
res.status(500).json({
|
|
301
|
+
error: '获取模型列表失败',
|
|
302
|
+
channelId: req.params.id
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// POST /api/channels/speed-test-all - Test all channels speed
|
|
308
|
+
router.post('/speed-test-all', async (req, res) => {
|
|
309
|
+
try {
|
|
310
|
+
const { timeout = 10000, concurrency } = req.body || {};
|
|
311
|
+
const channels = getAllChannels();
|
|
312
|
+
const safeConcurrency = sanitizeBatchConcurrency(concurrency);
|
|
313
|
+
|
|
314
|
+
if (channels.length === 0) {
|
|
315
|
+
return res.json({ results: [], message: '没有可测试的渠道' });
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const results = await runWithConcurrencyLimit(
|
|
319
|
+
channels,
|
|
320
|
+
safeConcurrency,
|
|
321
|
+
async channel => {
|
|
322
|
+
const speedTestType = CLAUDE_GATEWAY_SOURCE_TYPE;
|
|
323
|
+
const result = await testChannelSpeed(channel, timeout, speedTestType);
|
|
324
|
+
result.level = getLatencyLevel(result.latency);
|
|
325
|
+
result.gatewaySourceType = speedTestType;
|
|
326
|
+
return result;
|
|
327
|
+
}
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
// 与其他渠道保持一致:成功在前,成功结果按延迟升序
|
|
331
|
+
results.sort((a, b) => {
|
|
332
|
+
if (a.success && !b.success) return -1;
|
|
333
|
+
if (!a.success && b.success) return 1;
|
|
334
|
+
if (a.success && b.success) {
|
|
335
|
+
const aLatency = (a.latency === null || a.latency === undefined) ? Infinity : a.latency;
|
|
336
|
+
const bLatency = (b.latency === null || b.latency === undefined) ? Infinity : b.latency;
|
|
337
|
+
return aLatency - bLatency;
|
|
338
|
+
}
|
|
339
|
+
return 0;
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
res.json({
|
|
343
|
+
results,
|
|
344
|
+
summary: {
|
|
345
|
+
total: results.length,
|
|
346
|
+
success: results.filter(r => r.success).length,
|
|
347
|
+
failed: results.filter(r => !r.success).length,
|
|
348
|
+
avgLatency: calculateAvgLatency(results),
|
|
349
|
+
concurrency: safeConcurrency
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
} catch (error) {
|
|
353
|
+
console.error('Error testing all channels speed:', error);
|
|
354
|
+
res.status(500).json({ error: error.message });
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
// 计算平均延迟
|
|
359
|
+
function calculateAvgLatency(results) {
|
|
360
|
+
const successResults = results.filter(
|
|
361
|
+
r => r.success && r.latency !== null && r.latency !== undefined
|
|
362
|
+
);
|
|
363
|
+
if (successResults.length === 0) return null;
|
|
364
|
+
const sum = successResults.reduce((acc, r) => acc + r.latency, 0);
|
|
365
|
+
return Math.round(sum / successResults.length);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
module.exports = router;
|