coding-tool-x 3.9.5-261014 → 3.9.6-261016
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/dist/web/assets/{AgentsDrawer-K97azdxi.js → AgentsDrawer-D2oYKcOj.js} +1 -1
- package/dist/web/assets/{Analytics-CNPDGLNe.js → Analytics-Cy3QSSrn.js} +1 -1
- package/dist/web/assets/{AssetPathField-BzwSZryS.js → AssetPathField-BGuw5ZR7.js} +1 -1
- package/dist/web/assets/{ChatHistoryDrawer-BVbfqK-u.js → ChatHistoryDrawer-BZ8FY_-9.js} +1 -1
- package/dist/web/assets/{CollapseItem-DgBQWlsJ.js → CollapseItem-DOoxtmtj.js} +1 -1
- package/dist/web/assets/{CommandsDrawer-885BtWjs.js → CommandsDrawer-YCgZAeNx.js} +1 -1
- package/dist/web/assets/{ConfigExportDrawer-jn00iZWm.js → ConfigExportDrawer-BlAHQidK.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-DoUG3mOo.js → ConfigTemplates-C6qSF4Ht.js} +1 -1
- package/dist/web/assets/{ConfigTemplatesDrawer-BQ8h43E6.js → ConfigTemplatesDrawer-lRT_6-jD.js} +1 -1
- package/dist/web/assets/{ConfigTemplatesPanel-VCghYqMx.js → ConfigTemplatesPanel-DrS57Y8V.js} +1 -1
- package/dist/web/assets/{FavoritesDrawer-CKjlAe4O.js → FavoritesDrawer-CuOcHtqM.js} +1 -1
- package/dist/web/assets/{GatewayConvertDrawer-Cin2yotJ.js → GatewayConvertDrawer-b0pIvH02.js} +1 -1
- package/dist/web/assets/{Home-ui0qANow.js → Home-DvfQcP4b.js} +1 -1
- package/dist/web/assets/{MarkdownEditor-LiZaVnJu.js → MarkdownEditor-BLS6HmBb.js} +1 -1
- package/dist/web/assets/{McpDrawer-ClaSJOLK.js → McpDrawer-BHt7zFf0.js} +1 -1
- package/dist/web/assets/{McpFormDrawer-w1EJUIsa.js → McpFormDrawer-DpTMHc-e.js} +1 -1
- package/dist/web/assets/{PluginManager-C_Yr9wyj.js → PluginManager-CYAuy5dm.js} +1 -1
- package/dist/web/assets/{PluginsDrawer-CUnoai6T.js → PluginsDrawer-CoyprWNl.js} +1 -1
- package/dist/web/assets/{PluginsPanel-CNShDbDF.js → PluginsPanel-C3AzXlkH.js} +1 -1
- package/dist/web/assets/{ProjectList-CZ2ZwKRn.js → ProjectList-pJo0xp0Q.js} +1 -1
- package/dist/web/assets/{PromptsDrawer-BhB_q-KZ.js → PromptsDrawer-CM-irN84.js} +1 -1
- package/dist/web/assets/{RecentSessionsDrawer-BZ8YKqwq.js → RecentSessionsDrawer-l5nO_5bS.js} +1 -1
- package/dist/web/assets/{SessionCard-CEFt-CNZ.js → SessionCard-BqTFOt05.js} +1 -1
- package/dist/web/assets/{SessionList-CTO4NiMY.js → SessionList-Bs_YFKW5.js} +1 -1
- package/dist/web/assets/{SettingsDrawer-BiOXu9lZ.js → SettingsDrawer-COszrj_Y.js} +1 -1
- package/dist/web/assets/{SkillManager-Bf87paMe.js → SkillManager-DgLhL2Qv.js} +1 -1
- package/dist/web/assets/{SkillsDrawer-BwkyaeRk.js → SkillsDrawer-CT69YSEE.js} +1 -1
- package/dist/web/assets/{SkillsPanel-obdDEThm.js → SkillsPanel-Dm8FkYD6.js} +1 -1
- package/dist/web/assets/{Upload-DdjJgjTW.js → Upload-BHTymsxe.js} +1 -1
- package/dist/web/assets/{WorkspaceDrawer-UWKpEUXI.js → WorkspaceDrawer-DLXh_c7u.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-rKzSKzKC.js → WorkspaceManager-E_IoRTlC.js} +1 -1
- package/dist/web/assets/{WorkspacePanel-BCMcITVD.js → WorkspacePanel-DLlTIU8Y.js} +1 -1
- package/dist/web/assets/index-DP0Ggf-8.js +2 -0
- package/dist/web/assets/{index-DvoMGWQv.css → index-DjIuAtaS.css} +1 -1
- package/dist/web/assets/{platforms-JV36QBCw.js → platforms-DNVEAtqx.js} +1 -1
- package/dist/web/assets/platforms-p9AiybLU.js +1 -0
- package/dist/web/assets/{sessions-wvj5oaYr.js → sessions-DJiJJgvg.js} +1 -1
- package/dist/web/assets/sessions-DSLpMXCq.js +1 -0
- package/dist/web/assets/{useResponsiveDrawer-Cn6gH3VG.js → useResponsiveDrawer-D9ti7g-U.js} +1 -1
- package/dist/web/index.html +4 -4
- package/package.json +1 -1
- package/src/commands/channels.js +88 -95
- package/src/commands/toggle-proxy.js +26 -19
- package/src/platforms/drivers/claude/channels.js +11 -1
- package/src/platforms/drivers/claude/proxy.js +1 -2
- package/src/platforms/drivers/codex/channels.js +14 -2
- package/src/platforms/drivers/codex/proxy.js +1 -2
- package/src/platforms/drivers/dsh/api-operations.js +1 -1
- package/src/platforms/drivers/dsh/channels.js +3 -7
- package/src/platforms/drivers/dsh/proxy.js +0 -4
- package/src/platforms/drivers/gemini/channels.js +10 -12
- package/src/platforms/drivers/gemini/proxy.js +1 -2
- package/src/platforms/drivers/omp/channels.js +53 -13
- package/src/platforms/drivers/omp/gateway-routing.js +69 -33
- package/src/platforms/drivers/omp/gateway.js +102 -40
- package/src/platforms/drivers/omp/proxy.js +1 -2
- package/src/platforms/drivers/opencode/channels.js +53 -24
- package/src/platforms/drivers/opencode/proxy.js +1 -2
- package/src/platforms/manifest-schema.js +53 -1
- package/src/platforms/manifests/claude.json +11 -0
- package/src/platforms/manifests/codex.json +11 -16
- package/src/platforms/manifests/dsh.json +13 -0
- package/src/platforms/manifests/gemini.json +17 -16
- package/src/platforms/manifests/omp.json +6 -16
- package/src/platforms/manifests/opencode.json +21 -16
- package/src/server/api/platforms.js +29 -0
- package/src/server/services/memory-pressure.js +101 -0
- package/src/shared/channel-driver.js +24 -14
- package/dist/web/assets/index-aW6itxZg.js +0 -2
- package/dist/web/assets/platforms-DILqyP72.js +0 -1
- package/dist/web/assets/sessions-WXODMvLY.js +0 -1
package/src/commands/channels.js
CHANGED
|
@@ -39,11 +39,14 @@ function getChannelServices(cliType, runtime = getPlatformRuntime()) {
|
|
|
39
39
|
|
|
40
40
|
return {
|
|
41
41
|
...metadata,
|
|
42
|
-
getAllChannels: () => {
|
|
43
|
-
const value = unwrapChannels('list');
|
|
42
|
+
getAllChannels: async () => {
|
|
43
|
+
const value = await unwrapChannels('list');
|
|
44
44
|
return Array.isArray(value) ? value : (value?.channels || []);
|
|
45
45
|
},
|
|
46
|
-
createChannel: input =>
|
|
46
|
+
createChannel: input => {
|
|
47
|
+
const create = channelsDriver?.createFromCli || channelsDriver?.create;
|
|
48
|
+
return typeof create === 'function' ? unwrapDriverResult(create(input)) : null;
|
|
49
|
+
},
|
|
47
50
|
updateChannel: (id, patch) => unwrapChannels('update', id, patch),
|
|
48
51
|
activateChannel: id => unwrapChannels('applyNativeConfig', id),
|
|
49
52
|
getBestChannelForRestore: () => unwrapChannels('getBestChannelForRestore'),
|
|
@@ -78,6 +81,7 @@ async function handleChannelManagement() {
|
|
|
78
81
|
return;
|
|
79
82
|
}
|
|
80
83
|
|
|
84
|
+
const channels = await services.getAllChannels();
|
|
81
85
|
if (channels.length === 0) {
|
|
82
86
|
console.log(chalk.yellow('还没有添加任何渠道'));
|
|
83
87
|
if (services.createUnavailableMessage) {
|
|
@@ -102,14 +106,21 @@ async function handleChannelManagement() {
|
|
|
102
106
|
return;
|
|
103
107
|
}
|
|
104
108
|
|
|
105
|
-
const supportsMultiToggle =
|
|
109
|
+
const supportsMultiToggle = services.supportsCliToggle === true
|
|
110
|
+
&& typeof services.updateChannel === 'function';
|
|
106
111
|
|
|
107
112
|
if (supportsMultiToggle) {
|
|
108
113
|
await handleChannelToggle(channels, services, cliType);
|
|
109
114
|
return;
|
|
110
115
|
}
|
|
111
116
|
|
|
112
|
-
|
|
117
|
+
if (typeof services.activateChannel === 'function') {
|
|
118
|
+
await handleLegacySwitch(channels, services);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
console.log(chalk.yellow('当前平台未声明 CLI 渠道切换能力。'));
|
|
123
|
+
await inquirer.prompt([{ type: 'input', name: 'continue', message: '按回车返回...' }]);
|
|
113
124
|
}
|
|
114
125
|
|
|
115
126
|
/**
|
|
@@ -125,7 +136,7 @@ async function handleAddChannel() {
|
|
|
125
136
|
const cliType = normalizePlatformKey(config.currentCliType || 'claude');
|
|
126
137
|
const services = getChannelServices(cliType);
|
|
127
138
|
|
|
128
|
-
if (!services || services.supportsCliCreate
|
|
139
|
+
if (!services || services.supportsCliCreate !== true || typeof services.createChannel !== 'function') {
|
|
129
140
|
console.log(chalk.red(
|
|
130
141
|
`${services?.createUnavailableMessage || `当前 CLI 类型 (${cliType}) 暂不支持添加渠道`}\n`
|
|
131
142
|
));
|
|
@@ -133,75 +144,31 @@ async function handleAddChannel() {
|
|
|
133
144
|
return;
|
|
134
145
|
}
|
|
135
146
|
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
145
|
-
return true;
|
|
146
|
-
},
|
|
147
|
-
},
|
|
148
|
-
{
|
|
149
|
-
type: 'input',
|
|
150
|
-
name: 'baseUrl',
|
|
151
|
-
message: 'Base URL:',
|
|
152
|
-
validate: (input) => {
|
|
153
|
-
if (!input.trim()) {
|
|
154
|
-
return 'Base URL 不能为空';
|
|
155
|
-
}
|
|
156
|
-
// 简单的 URL 验证
|
|
157
|
-
if (!input.startsWith('http://') && !input.startsWith('https://')) {
|
|
158
|
-
return 'Base URL 必须以 http:// 或 https:// 开头';
|
|
159
|
-
}
|
|
160
|
-
return true;
|
|
161
|
-
},
|
|
162
|
-
},
|
|
163
|
-
{
|
|
164
|
-
type: 'input',
|
|
165
|
-
name: 'apiKey',
|
|
166
|
-
message: 'API Key:',
|
|
167
|
-
validate: (input) => {
|
|
168
|
-
if (!input.trim()) {
|
|
169
|
-
return 'API Key 不能为空';
|
|
170
|
-
}
|
|
171
|
-
return true;
|
|
172
|
-
},
|
|
173
|
-
},
|
|
174
|
-
{
|
|
175
|
-
type: 'input',
|
|
176
|
-
name: 'websiteUrl',
|
|
177
|
-
message: '网站地址(可选,直接回车跳过):',
|
|
178
|
-
},
|
|
179
|
-
]);
|
|
147
|
+
const configuredQuestions = Array.isArray(services.createQuestions)
|
|
148
|
+
? services.createQuestions
|
|
149
|
+
: [];
|
|
150
|
+
if (!configuredQuestions.length) {
|
|
151
|
+
console.log(chalk.red('当前平台未配置 CLI 渠道创建表单。'));
|
|
152
|
+
await inquirer.prompt([{ type: 'input', name: 'continue', message: '按回车返回...' }]);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
180
155
|
|
|
181
|
-
const
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
baseUrl: answers.baseUrl.trim(),
|
|
193
|
-
apiKey: answers.apiKey.trim(),
|
|
194
|
-
websiteUrl,
|
|
195
|
-
providerKey: answers.name.trim().toLowerCase().replace(/\s+/g, '-'),
|
|
196
|
-
extra: {
|
|
197
|
-
...services.createDefaults,
|
|
198
|
-
...extraAnswers,
|
|
199
|
-
websiteUrl
|
|
156
|
+
const answers = await inquirer.prompt(buildChannelCreateQuestions(configuredQuestions));
|
|
157
|
+
const createInput = { ...(services.createDefaults || {}) };
|
|
158
|
+
for (const question of configuredQuestions) {
|
|
159
|
+
const value = answers[question.name];
|
|
160
|
+
if (typeof value === 'string') {
|
|
161
|
+
const normalized = value.trim();
|
|
162
|
+
if (normalized || Object.prototype.hasOwnProperty.call(services.createDefaults || {}, question.name)) {
|
|
163
|
+
createInput[question.name] = normalized;
|
|
164
|
+
}
|
|
165
|
+
} else if (value !== undefined) {
|
|
166
|
+
createInput[question.name] = value;
|
|
200
167
|
}
|
|
201
|
-
}
|
|
168
|
+
}
|
|
202
169
|
|
|
203
170
|
try {
|
|
204
|
-
const channel = services.createChannel(createInput);
|
|
171
|
+
const channel = await services.createChannel(createInput);
|
|
205
172
|
|
|
206
173
|
console.log(chalk.green(`\n[OK] 渠道添加成功: ${channel.name}\n`));
|
|
207
174
|
console.log(chalk.gray('提示: 使用"渠道管理"功能来启用此渠道\n'));
|
|
@@ -233,32 +200,38 @@ async function handleChannelStatus() {
|
|
|
233
200
|
console.log(chalk.bold.cyan('║ 调度状态查看 ║'));
|
|
234
201
|
console.log(chalk.bold.cyan('╚=======================================╝\n'));
|
|
235
202
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
203
|
+
const runtime = getPlatformRuntime();
|
|
204
|
+
for (const source of buildSchedulerSources()) {
|
|
205
|
+
console.log(chalk.bold(`\n[${source.label}]`));
|
|
206
|
+
const driver = runtime?.getDriver?.(source.key, 'channels');
|
|
207
|
+
if (typeof driver?.poolStatus !== 'function') {
|
|
208
|
+
console.log(chalk.yellow('该平台未提供调度状态。'));
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
239
211
|
|
|
240
|
-
|
|
241
|
-
const
|
|
242
|
-
|
|
212
|
+
try {
|
|
213
|
+
const result = await unwrapDriverResult(driver.poolStatus());
|
|
214
|
+
const state = result?.scheduler || result || {};
|
|
243
215
|
console.log(chalk.gray(`排队中: ${state.pending || 0}`));
|
|
244
216
|
|
|
245
217
|
if (!state.channels || state.channels.length === 0) {
|
|
246
218
|
console.log(chalk.yellow('暂无调度数据或未启用渠道。'));
|
|
247
|
-
|
|
248
|
-
state.channels.forEach((channel, index) => {
|
|
249
|
-
const concurrency = channel.maxConcurrency ?? '∞';
|
|
250
|
-
const healthText = channel.health?.statusText || '健康';
|
|
251
|
-
const healthColor = channel.health?.statusColor || '#18a058';
|
|
252
|
-
console.log(
|
|
253
|
-
`${chalk.cyan(String(index + 1).padStart(2, '0'))}. ${chalk.bold(channel.name)} ` +
|
|
254
|
-
chalk.gray(`并发 ${channel.inflight}/${concurrency} | 权重 ${channel.weight || 1}`)
|
|
255
|
-
);
|
|
256
|
-
console.log(` 健康状态: ${chalk.hex(healthColor)(healthText)}`);
|
|
257
|
-
});
|
|
219
|
+
continue;
|
|
258
220
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
221
|
+
|
|
222
|
+
state.channels.forEach((channel, index) => {
|
|
223
|
+
const concurrency = channel.maxConcurrency ?? '∞';
|
|
224
|
+
const healthText = channel.health?.statusText || '健康';
|
|
225
|
+
const healthColor = channel.health?.statusColor || '#18a058';
|
|
226
|
+
console.log(
|
|
227
|
+
`${chalk.cyan(String(index + 1).padStart(2, '0'))}. ${chalk.bold(channel.name)} ` +
|
|
228
|
+
chalk.gray(`并发 ${channel.inflight}/${concurrency} | 权重 ${channel.weight || 1}`)
|
|
229
|
+
);
|
|
230
|
+
console.log(` 健康状态: ${chalk.hex(healthColor)(healthText)}`);
|
|
231
|
+
});
|
|
232
|
+
} catch (error) {
|
|
233
|
+
console.log(chalk.red('无法读取调度状态: ' + error.message));
|
|
234
|
+
}
|
|
262
235
|
}
|
|
263
236
|
|
|
264
237
|
await inquirer.prompt([{ type: 'input', name: 'continue', message: '按回车返回...' }]);
|
|
@@ -270,10 +243,30 @@ module.exports = {
|
|
|
270
243
|
handleChannelStatus,
|
|
271
244
|
_test: {
|
|
272
245
|
buildSchedulerSources,
|
|
273
|
-
getChannelServices
|
|
246
|
+
getChannelServices,
|
|
247
|
+
buildChannelCreateQuestions
|
|
274
248
|
}
|
|
275
249
|
};
|
|
276
250
|
|
|
251
|
+
function buildChannelCreateQuestions(configuredQuestions = []) {
|
|
252
|
+
return configuredQuestions.map(({ validation, validationMessage, ...question }) => {
|
|
253
|
+
if (!validation) return question;
|
|
254
|
+
return {
|
|
255
|
+
...question,
|
|
256
|
+
validate: value => {
|
|
257
|
+
const normalized = String(value ?? '').trim();
|
|
258
|
+
if (validation === 'required' && !normalized) {
|
|
259
|
+
return validationMessage || '此项不能为空';
|
|
260
|
+
}
|
|
261
|
+
if (validation === 'http_url' && !/^https?:\/\//i.test(normalized)) {
|
|
262
|
+
return validationMessage || '请输入以 http:// 或 https:// 开头的地址';
|
|
263
|
+
}
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
277
270
|
/**
|
|
278
271
|
* 统一的多渠道选择处理
|
|
279
272
|
*/
|
|
@@ -337,10 +330,10 @@ async function handleChannelToggle(channels, services, cliType) {
|
|
|
337
330
|
}
|
|
338
331
|
|
|
339
332
|
if (hasChanged) {
|
|
340
|
-
broadcastSchedulerSnapshot(
|
|
333
|
+
broadcastSchedulerSnapshot(cliType);
|
|
341
334
|
}
|
|
342
335
|
|
|
343
|
-
await handleAdvancedConfig(services,
|
|
336
|
+
await handleAdvancedConfig(services, cliType);
|
|
344
337
|
|
|
345
338
|
console.log(
|
|
346
339
|
chalk.cyan(
|
|
@@ -368,7 +361,7 @@ async function handleAdvancedConfig(services, cliType) {
|
|
|
368
361
|
if (!adjust) return;
|
|
369
362
|
|
|
370
363
|
while (true) {
|
|
371
|
-
const latestChannels = services.getAllChannels();
|
|
364
|
+
const latestChannels = await services.getAllChannels();
|
|
372
365
|
if (!latestChannels.length) {
|
|
373
366
|
console.log(chalk.red('暂无渠道可供调整'));
|
|
374
367
|
return;
|
|
@@ -188,13 +188,17 @@ async function handleStartProxy(cliType, services) {
|
|
|
188
188
|
console.log(chalk.bold.cyan('\n╔=======================================╗'));
|
|
189
189
|
const toolName = getPlatformLabel(cliType);
|
|
190
190
|
const defaultPort = services.defaultPort;
|
|
191
|
+
const labels = services.proxyLabels || {};
|
|
192
|
+
const serviceLabel = labels.serviceLabel || `${toolName} 代理服务`;
|
|
193
|
+
const proxyLabel = labels.proxyLabel || '动态切换';
|
|
194
|
+
const addressLabel = labels.addressLabel || (services.managedProviderConfig ? '网关地址' : '代理地址');
|
|
191
195
|
|
|
192
196
|
console.log(chalk.cyan('动态切换功能说明:'));
|
|
193
197
|
if (services.managedProviderConfig) {
|
|
194
|
-
console.log(chalk.gray(
|
|
195
|
-
console.log(chalk.gray(`• 可以通过 Web UI 或"渠道管理"功能调整启用的 ${toolName}
|
|
196
|
-
console.log(chalk.gray('•
|
|
197
|
-
console.log(chalk.gray(`•
|
|
198
|
+
console.log(chalk.gray(`• 开启后会启动${serviceLabel},并将平台原生 provider 指向本地网关`));
|
|
199
|
+
console.log(chalk.gray(`• 可以通过 Web UI 或"渠道管理"功能调整启用的 ${toolName} 渠道`));
|
|
200
|
+
console.log(chalk.gray('• 网关按平台路由设置分发请求,模型 fallback 仍由平台处理'));
|
|
201
|
+
console.log(chalk.gray(`• ${addressLabel}: http://127.0.0.1:${defaultPort}\n`));
|
|
198
202
|
} else {
|
|
199
203
|
console.log(chalk.gray('• 开启后会在本地启动一个代理服务'));
|
|
200
204
|
console.log(chalk.gray(`• 可以在不重启 ${toolName} 的情况下动态管理渠道`));
|
|
@@ -204,8 +208,8 @@ async function handleStartProxy(cliType, services) {
|
|
|
204
208
|
|
|
205
209
|
console.log(chalk.yellow('[WARN] 重要提示:'));
|
|
206
210
|
if (services.managedProviderConfig) {
|
|
207
|
-
console.log(chalk.yellow('•
|
|
208
|
-
console.log(chalk.yellow('•
|
|
211
|
+
console.log(chalk.yellow('• 启用后会写入指向本地网关的受管 provider 配置'));
|
|
212
|
+
console.log(chalk.yellow('• 关闭时会由平台 Driver 按原生配置策略完成交接\n'));
|
|
209
213
|
} else {
|
|
210
214
|
console.log(chalk.yellow('• 开启期间请勿关闭 CLI 终端窗口'));
|
|
211
215
|
console.log(chalk.yellow('• 如果异常关闭导致代理失效,请运行: ctx reset'));
|
|
@@ -228,18 +232,18 @@ async function handleStartProxy(cliType, services) {
|
|
|
228
232
|
|
|
229
233
|
try {
|
|
230
234
|
console.log(chalk.cyan(services.managedProviderConfig
|
|
231
|
-
?
|
|
235
|
+
? `\n[START] 正在启用${serviceLabel}...\n`
|
|
232
236
|
: '\n[START] 正在启动代理服务...\n'));
|
|
233
237
|
|
|
234
238
|
// 启动代理服务器
|
|
235
239
|
const proxyResult = await services.startProxyServer();
|
|
236
240
|
|
|
237
241
|
if (!proxyResult.success) {
|
|
238
|
-
throw new Error(services.managedProviderConfig ?
|
|
242
|
+
throw new Error(services.managedProviderConfig ? `${serviceLabel}启用失败` : '代理服务器启动失败');
|
|
239
243
|
}
|
|
240
244
|
|
|
241
245
|
if (services.managedProviderConfig) {
|
|
242
|
-
console.log(chalk.green(
|
|
246
|
+
console.log(chalk.green(`[OK] ${labels.startedMessage || `${serviceLabel}已启用`}`));
|
|
243
247
|
(proxyResult.warnings || []).forEach((warning) => {
|
|
244
248
|
console.log(chalk.yellow(`[WARN] ${warning}`));
|
|
245
249
|
});
|
|
@@ -259,9 +263,9 @@ async function handleStartProxy(cliType, services) {
|
|
|
259
263
|
}
|
|
260
264
|
}
|
|
261
265
|
|
|
262
|
-
console.log(chalk.cyan(
|
|
266
|
+
console.log(chalk.cyan(`\n[TIP] ${labels.startedMessage || `${proxyLabel}已启用`}!`));
|
|
263
267
|
if (services.managedProviderConfig) {
|
|
264
|
-
console.log(chalk.gray(
|
|
268
|
+
console.log(chalk.gray(` 现在可以通过"渠道管理"功能调整已启用的 ${toolName} 渠道\n`));
|
|
265
269
|
} else {
|
|
266
270
|
console.log(chalk.gray(` 现在可以通过"渠道管理"功能快速调整,无需重启 ${toolName}\n`));
|
|
267
271
|
}
|
|
@@ -297,11 +301,14 @@ async function handleStopProxy(cliType, services) {
|
|
|
297
301
|
|
|
298
302
|
const toolName = getPlatformLabel(cliType);
|
|
299
303
|
const proxyStatus = services.getProxyStatus();
|
|
304
|
+
const labels = services.proxyLabels || {};
|
|
305
|
+
const serviceLabel = labels.serviceLabel || `${toolName} 代理服务`;
|
|
306
|
+
const proxyLabel = labels.proxyLabel || '动态切换';
|
|
300
307
|
|
|
301
308
|
console.log(chalk.cyan('当前状态:'));
|
|
302
309
|
if (services.managedProviderConfig) {
|
|
303
|
-
console.log(chalk.gray(`•
|
|
304
|
-
console.log(chalk.gray(`• 配置模式: ${proxyStatus.mode || '
|
|
310
|
+
console.log(chalk.gray(`• ${proxyLabel}: ${chalk.green(labels.runningText || '已启用')}`));
|
|
311
|
+
console.log(chalk.gray(`• 配置模式: ${proxyStatus.mode || '平台受管 provider 配置'}`));
|
|
305
312
|
} else {
|
|
306
313
|
console.log(chalk.gray(`• 代理服务: ${chalk.green('运行中')}`));
|
|
307
314
|
console.log(chalk.gray(`• 代理端口: ${proxyStatus.port}`));
|
|
@@ -310,8 +317,8 @@ async function handleStopProxy(cliType, services) {
|
|
|
310
317
|
|
|
311
318
|
console.log(chalk.yellow('关闭后:'));
|
|
312
319
|
if (services.managedProviderConfig) {
|
|
313
|
-
console.log(chalk.gray(
|
|
314
|
-
console.log(chalk.gray(`• 之后调整 ${toolName}
|
|
320
|
+
console.log(chalk.gray(`• ${serviceLabel}会按平台 Driver 策略交接原生 provider 配置`));
|
|
321
|
+
console.log(chalk.gray(`• 之后调整 ${toolName} 渠道或模型将按平台配置策略生效\n`));
|
|
315
322
|
} else {
|
|
316
323
|
console.log(chalk.gray('• 代理服务将被停止'));
|
|
317
324
|
console.log(chalk.gray('• 配置将恢复为当前激活渠道的单渠道模式'));
|
|
@@ -334,13 +341,13 @@ async function handleStopProxy(cliType, services) {
|
|
|
334
341
|
|
|
335
342
|
try {
|
|
336
343
|
console.log(chalk.cyan(services.managedProviderConfig
|
|
337
|
-
?
|
|
344
|
+
? `\n[STOP] 正在停止${serviceLabel}...\n`
|
|
338
345
|
: '\n[STOP] 正在停止代理服务...\n'));
|
|
339
346
|
|
|
340
347
|
// 停止代理服务器
|
|
341
348
|
await services.stopProxyServer();
|
|
342
349
|
console.log(chalk.green(services.managedProviderConfig
|
|
343
|
-
?
|
|
350
|
+
? (labels.stoppedMessage || `${serviceLabel}已停止`)
|
|
344
351
|
: '[OK] 代理服务已停止'));
|
|
345
352
|
|
|
346
353
|
// 恢复配置文件
|
|
@@ -364,9 +371,9 @@ async function handleStopProxy(cliType, services) {
|
|
|
364
371
|
console.log(chalk.green('[OK] 已清理代理接管状态'));
|
|
365
372
|
}
|
|
366
373
|
|
|
367
|
-
console.log(chalk.cyan(
|
|
374
|
+
console.log(chalk.cyan(`\n[TIP] ${labels.stoppedMessage || `${proxyLabel}已关闭`}`));
|
|
368
375
|
if (services.managedProviderConfig) {
|
|
369
|
-
console.log(chalk.gray(
|
|
376
|
+
console.log(chalk.gray(` ${toolName} 渠道配置已交由平台 Driver 处理\n`));
|
|
370
377
|
} else {
|
|
371
378
|
console.log(chalk.gray(` 现在调整渠道需要重启 ${toolName} 才能生效\n`));
|
|
372
379
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const { createChannelDriver } = require('../../../shared/channel-driver');
|
|
4
4
|
|
|
5
5
|
function createDriver(context = {}) {
|
|
6
|
+
const { resetChannelHealth } = require('../../../server/services/channel-health');
|
|
6
7
|
const driver = createChannelDriver({
|
|
7
8
|
...context,
|
|
8
9
|
platform: 'claude',
|
|
@@ -10,16 +11,25 @@ function createDriver(context = {}) {
|
|
|
10
11
|
localServicePath: '../platforms/drivers/claude/channels-implementation',
|
|
11
12
|
listMethod: 'getAllChannels',
|
|
12
13
|
syncMethod: 'syncCurrentClaudeChannel',
|
|
14
|
+
modelListType: context.manifest?.channelModelType || 'claude',
|
|
13
15
|
createArgs: (input, rest) => typeof input === 'object'
|
|
14
16
|
? (() => {
|
|
15
17
|
const { name, baseUrl, apiKey, websiteUrl, extra, ...channelExtra } = input;
|
|
16
18
|
return [name, baseUrl, apiKey, websiteUrl, { ...channelExtra, ...extra }];
|
|
17
19
|
})()
|
|
18
20
|
: [input, ...rest],
|
|
19
|
-
cliMetadata: { supportsCliCreate: true, supportsCliToggle: true, defaultPort: 20088 },
|
|
20
21
|
dashboardChannelShape: 'array'
|
|
21
22
|
});
|
|
22
23
|
const callLegacy = (name, args) => driver._service()[name](...args);
|
|
24
|
+
const saveOrder = driver.saveOrder;
|
|
25
|
+
driver.saveOrder = input => saveOrder(input?.order || input);
|
|
26
|
+
driver.getCurrentChannel = () => callLegacy('getCurrentChannel', []);
|
|
27
|
+
driver.resetHealth = input => {
|
|
28
|
+
const channelId = typeof input === 'string' ? input : input?.channelId || input?.body?.channelId;
|
|
29
|
+
if (!channelId) throw new Error('Channel id is required');
|
|
30
|
+
resetChannelHealth(channelId, 'claude');
|
|
31
|
+
return { channelId, reset: true };
|
|
32
|
+
};
|
|
23
33
|
const auth = require('../../channel-auth-service');
|
|
24
34
|
driver.getAuth = context => auth.getChannelAuth('claude', { channelId: context?.params?.channelId || context?.query?.channelId || '' });
|
|
25
35
|
driver.syncLocalAuth = context => auth.syncLocalChannelAuth('claude', { channelId: context?.body?.channelId || context?.params?.channelId || context?.query?.channelId || '' });
|
|
@@ -8,8 +8,7 @@ function createDriver(context = {}) {
|
|
|
8
8
|
platform: 'claude',
|
|
9
9
|
servicePath: './claude/proxy-implementation',
|
|
10
10
|
localServicePath: '../platforms/drivers/claude/proxy-implementation',
|
|
11
|
-
exports: { status: 'getProxyStatus', start: 'startProxyServer', stop: 'stopProxyServer' }
|
|
12
|
-
cliMetadata: { defaultPort: 20088 }
|
|
11
|
+
exports: { status: 'getProxyStatus', start: 'startProxyServer', stop: 'stopProxyServer' }
|
|
13
12
|
});
|
|
14
13
|
}
|
|
15
14
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const { createChannelDriver } = require('../../../shared/channel-driver');
|
|
4
4
|
|
|
5
5
|
function createDriver(context = {}) {
|
|
6
|
+
const { resetChannelHealth } = require('../../../server/services/channel-health');
|
|
6
7
|
const driver = createChannelDriver({
|
|
7
8
|
...context,
|
|
8
9
|
platform: 'codex',
|
|
@@ -15,12 +16,23 @@ function createDriver(context = {}) {
|
|
|
15
16
|
return [name, providerKey, baseUrl, apiKey, wireApi, { ...channelExtra, ...extra }];
|
|
16
17
|
})()
|
|
17
18
|
: [input, ...rest],
|
|
18
|
-
|
|
19
|
-
modelListType: 'openai_compatible',
|
|
19
|
+
modelListType: context.manifest?.channelModelType || 'openai_compatible',
|
|
20
20
|
formatCliChannelDetails: channel => channel.providerKey
|
|
21
21
|
? [`provider ${channel.providerKey}`]
|
|
22
22
|
: []
|
|
23
23
|
});
|
|
24
|
+
const saveOrder = driver.saveOrder;
|
|
25
|
+
driver.saveOrder = input => saveOrder(input?.order || input);
|
|
26
|
+
driver.resetHealth = input => {
|
|
27
|
+
const channelId = typeof input === 'string' ? input : input?.channelId || input?.body?.channelId;
|
|
28
|
+
if (!channelId) throw new Error('Channel id is required');
|
|
29
|
+
resetChannelHealth(channelId, 'codex');
|
|
30
|
+
return { channelId, reset: true };
|
|
31
|
+
};
|
|
32
|
+
driver.createFromCli = input => driver.create({
|
|
33
|
+
...input,
|
|
34
|
+
providerKey: String(input?.providerKey || input?.name || '').trim().toLowerCase().replace(/\s+/g, '-')
|
|
35
|
+
});
|
|
24
36
|
const auth = require('../../channel-auth-service');
|
|
25
37
|
driver.getAuth = context => auth.getChannelAuth('codex', { channelId: context?.params?.channelId || context?.query?.channelId || '' });
|
|
26
38
|
driver.syncLocalAuth = context => auth.syncLocalChannelAuth('codex', { channelId: context?.body?.channelId || context?.params?.channelId || context?.query?.channelId || '' });
|
|
@@ -8,8 +8,7 @@ function createDriver(context = {}) {
|
|
|
8
8
|
platform: 'codex',
|
|
9
9
|
servicePath: './codex/proxy-implementation',
|
|
10
10
|
localServicePath: '../platforms/drivers/codex/proxy-implementation',
|
|
11
|
-
exports: { status: 'getCodexProxyStatus', start: 'startCodexProxyServer', stop: 'stopCodexProxyServer' }
|
|
12
|
-
cliMetadata: { defaultPort: 20089 }
|
|
11
|
+
exports: { status: 'getCodexProxyStatus', start: 'startCodexProxyServer', stop: 'stopCodexProxyServer' }
|
|
13
12
|
});
|
|
14
13
|
}
|
|
15
14
|
|
|
@@ -222,7 +222,7 @@ function createDriver(context = {}) {
|
|
|
222
222
|
|
|
223
223
|
// Keep the aggregate DSH API driver compatible with the manifest consistency
|
|
224
224
|
// contract. Actual descriptor requests are still dispatched by capability.
|
|
225
|
-
for (const operation of ['list', 'current', 'enabled', 'create', 'update', 'remove', 'applyToSettings', 'sync', 'order', 'models', 'probeModels', 'speedTest']) {
|
|
225
|
+
for (const operation of ['list', 'current', 'enabled', 'poolStatus', 'create', 'update', 'remove', 'applyToSettings', 'sync', 'order', 'models', 'probeModels', 'speedTest']) {
|
|
226
226
|
driver[operation] = (...args) => channels[operation](...args);
|
|
227
227
|
}
|
|
228
228
|
for (const operation of ['status', 'start', 'stop']) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const implementation = require('./channels-implementation');
|
|
4
|
+
const { getSchedulerState } = require('../../../server/services/channel-scheduler');
|
|
4
5
|
|
|
5
6
|
function resultFor(request, status, data, error) {
|
|
6
7
|
const route = request?.route || {};
|
|
@@ -69,17 +70,12 @@ function createDriver(context = {}) {
|
|
|
69
70
|
const driver = {
|
|
70
71
|
platform: 'dsh',
|
|
71
72
|
capability: 'channels',
|
|
72
|
-
getCliMetadata: () => ({
|
|
73
|
-
supportsCliCreate: false,
|
|
74
|
-
supportsCliToggle: true,
|
|
75
|
-
managedProviderConfig: true,
|
|
76
|
-
defaultPort: 20093,
|
|
77
|
-
createUnavailableMessage: '提示: DSH 渠道请通过 Web UI 或 API 添加。'
|
|
78
|
-
}),
|
|
73
|
+
getCliMetadata: () => ({ ...(context.manifest?.channelCli || {}) }),
|
|
79
74
|
list: request => withResult(request, 'list', () => implementation.getChannels()),
|
|
80
75
|
getChannels: () => implementation.getChannels().channels,
|
|
81
76
|
getEnabled: request => withResult(request, 'enabled', () => ({ channels: implementation.getEnabledChannels() })),
|
|
82
77
|
getEnabledChannels: () => implementation.getEnabledChannels(),
|
|
78
|
+
poolStatus: request => withResult(request, 'poolStatus', () => ({ scheduler: getSchedulerState('dsh') })),
|
|
83
79
|
current: request => withResult(request, 'current', () => implementation.getCurrentChannel()),
|
|
84
80
|
create: request => withResult(request, 'create', () => {
|
|
85
81
|
const channel = implementation.createChannel(bodyOf(request));
|
|
@@ -3,32 +3,30 @@
|
|
|
3
3
|
const { createChannelDriver } = require('../../../shared/channel-driver');
|
|
4
4
|
|
|
5
5
|
function createDriver(context = {}) {
|
|
6
|
+
const { resetChannelHealth } = require('../../../server/services/channel-health');
|
|
6
7
|
const driver = createChannelDriver({
|
|
7
8
|
...context,
|
|
8
9
|
platform: 'gemini',
|
|
9
10
|
servicePath: './gemini/channels-implementation',
|
|
10
11
|
localServicePath: '../platforms/drivers/gemini/channels-implementation',
|
|
11
12
|
syncMethod: 'syncCurrentGeminiChannel',
|
|
13
|
+
modelListType: context.manifest?.channelModelType || 'gemini',
|
|
12
14
|
createArgs: (input, rest) => typeof input === 'object'
|
|
13
15
|
? (() => {
|
|
14
16
|
const { name, baseUrl, apiKey, model, extra, ...channelExtra } = input;
|
|
15
17
|
return [name, baseUrl, apiKey, model, { ...channelExtra, ...extra }];
|
|
16
18
|
})()
|
|
17
19
|
: [input, ...rest],
|
|
18
|
-
cliMetadata: {
|
|
19
|
-
supportsCliCreate: true,
|
|
20
|
-
supportsCliToggle: true,
|
|
21
|
-
defaultPort: 20090,
|
|
22
|
-
createDefaults: { model: 'gemini-2.5-pro' },
|
|
23
|
-
createQuestions: [{
|
|
24
|
-
type: 'input',
|
|
25
|
-
name: 'model',
|
|
26
|
-
message: '模型名称 (默认: gemini-2.5-pro):',
|
|
27
|
-
default: 'gemini-2.5-pro'
|
|
28
|
-
}]
|
|
29
|
-
},
|
|
30
20
|
formatCliChannelDetails: channel => channel.model ? [`model ${channel.model}`] : []
|
|
31
21
|
});
|
|
22
|
+
const saveOrder = driver.saveOrder;
|
|
23
|
+
driver.saveOrder = input => saveOrder(input?.order || input);
|
|
24
|
+
driver.resetHealth = input => {
|
|
25
|
+
const channelId = typeof input === 'string' ? input : input?.channelId || input?.body?.channelId;
|
|
26
|
+
if (!channelId) throw new Error('Channel id is required');
|
|
27
|
+
resetChannelHealth(channelId, 'gemini');
|
|
28
|
+
return { channelId, reset: true };
|
|
29
|
+
};
|
|
32
30
|
const auth = require('../../channel-auth-service');
|
|
33
31
|
driver.getAuth = context => auth.getChannelAuth('gemini', { channelId: context?.params?.channelId || context?.query?.channelId || '' });
|
|
34
32
|
driver.syncLocalAuth = context => auth.syncLocalChannelAuth('gemini', { channelId: context?.body?.channelId || context?.params?.channelId || context?.query?.channelId || '' });
|
|
@@ -8,8 +8,7 @@ function createDriver(context = {}) {
|
|
|
8
8
|
platform: 'gemini',
|
|
9
9
|
servicePath: './gemini/proxy-implementation',
|
|
10
10
|
localServicePath: '../platforms/drivers/gemini/proxy-implementation',
|
|
11
|
-
exports: { status: 'getGeminiProxyStatus', start: 'startGeminiProxyServer', stop: 'stopGeminiProxyServer' }
|
|
12
|
-
cliMetadata: { defaultPort: 20090 }
|
|
11
|
+
exports: { status: 'getGeminiProxyStatus', start: 'startGeminiProxyServer', stop: 'stopGeminiProxyServer' }
|
|
13
12
|
});
|
|
14
13
|
}
|
|
15
14
|
|