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.
Files changed (71) hide show
  1. package/dist/web/assets/{AgentsDrawer-K97azdxi.js → AgentsDrawer-D2oYKcOj.js} +1 -1
  2. package/dist/web/assets/{Analytics-CNPDGLNe.js → Analytics-Cy3QSSrn.js} +1 -1
  3. package/dist/web/assets/{AssetPathField-BzwSZryS.js → AssetPathField-BGuw5ZR7.js} +1 -1
  4. package/dist/web/assets/{ChatHistoryDrawer-BVbfqK-u.js → ChatHistoryDrawer-BZ8FY_-9.js} +1 -1
  5. package/dist/web/assets/{CollapseItem-DgBQWlsJ.js → CollapseItem-DOoxtmtj.js} +1 -1
  6. package/dist/web/assets/{CommandsDrawer-885BtWjs.js → CommandsDrawer-YCgZAeNx.js} +1 -1
  7. package/dist/web/assets/{ConfigExportDrawer-jn00iZWm.js → ConfigExportDrawer-BlAHQidK.js} +1 -1
  8. package/dist/web/assets/{ConfigTemplates-DoUG3mOo.js → ConfigTemplates-C6qSF4Ht.js} +1 -1
  9. package/dist/web/assets/{ConfigTemplatesDrawer-BQ8h43E6.js → ConfigTemplatesDrawer-lRT_6-jD.js} +1 -1
  10. package/dist/web/assets/{ConfigTemplatesPanel-VCghYqMx.js → ConfigTemplatesPanel-DrS57Y8V.js} +1 -1
  11. package/dist/web/assets/{FavoritesDrawer-CKjlAe4O.js → FavoritesDrawer-CuOcHtqM.js} +1 -1
  12. package/dist/web/assets/{GatewayConvertDrawer-Cin2yotJ.js → GatewayConvertDrawer-b0pIvH02.js} +1 -1
  13. package/dist/web/assets/{Home-ui0qANow.js → Home-DvfQcP4b.js} +1 -1
  14. package/dist/web/assets/{MarkdownEditor-LiZaVnJu.js → MarkdownEditor-BLS6HmBb.js} +1 -1
  15. package/dist/web/assets/{McpDrawer-ClaSJOLK.js → McpDrawer-BHt7zFf0.js} +1 -1
  16. package/dist/web/assets/{McpFormDrawer-w1EJUIsa.js → McpFormDrawer-DpTMHc-e.js} +1 -1
  17. package/dist/web/assets/{PluginManager-C_Yr9wyj.js → PluginManager-CYAuy5dm.js} +1 -1
  18. package/dist/web/assets/{PluginsDrawer-CUnoai6T.js → PluginsDrawer-CoyprWNl.js} +1 -1
  19. package/dist/web/assets/{PluginsPanel-CNShDbDF.js → PluginsPanel-C3AzXlkH.js} +1 -1
  20. package/dist/web/assets/{ProjectList-CZ2ZwKRn.js → ProjectList-pJo0xp0Q.js} +1 -1
  21. package/dist/web/assets/{PromptsDrawer-BhB_q-KZ.js → PromptsDrawer-CM-irN84.js} +1 -1
  22. package/dist/web/assets/{RecentSessionsDrawer-BZ8YKqwq.js → RecentSessionsDrawer-l5nO_5bS.js} +1 -1
  23. package/dist/web/assets/{SessionCard-CEFt-CNZ.js → SessionCard-BqTFOt05.js} +1 -1
  24. package/dist/web/assets/{SessionList-CTO4NiMY.js → SessionList-Bs_YFKW5.js} +1 -1
  25. package/dist/web/assets/{SettingsDrawer-BiOXu9lZ.js → SettingsDrawer-COszrj_Y.js} +1 -1
  26. package/dist/web/assets/{SkillManager-Bf87paMe.js → SkillManager-DgLhL2Qv.js} +1 -1
  27. package/dist/web/assets/{SkillsDrawer-BwkyaeRk.js → SkillsDrawer-CT69YSEE.js} +1 -1
  28. package/dist/web/assets/{SkillsPanel-obdDEThm.js → SkillsPanel-Dm8FkYD6.js} +1 -1
  29. package/dist/web/assets/{Upload-DdjJgjTW.js → Upload-BHTymsxe.js} +1 -1
  30. package/dist/web/assets/{WorkspaceDrawer-UWKpEUXI.js → WorkspaceDrawer-DLXh_c7u.js} +1 -1
  31. package/dist/web/assets/{WorkspaceManager-rKzSKzKC.js → WorkspaceManager-E_IoRTlC.js} +1 -1
  32. package/dist/web/assets/{WorkspacePanel-BCMcITVD.js → WorkspacePanel-DLlTIU8Y.js} +1 -1
  33. package/dist/web/assets/index-DP0Ggf-8.js +2 -0
  34. package/dist/web/assets/{index-DvoMGWQv.css → index-DjIuAtaS.css} +1 -1
  35. package/dist/web/assets/{platforms-JV36QBCw.js → platforms-DNVEAtqx.js} +1 -1
  36. package/dist/web/assets/platforms-p9AiybLU.js +1 -0
  37. package/dist/web/assets/{sessions-wvj5oaYr.js → sessions-DJiJJgvg.js} +1 -1
  38. package/dist/web/assets/sessions-DSLpMXCq.js +1 -0
  39. package/dist/web/assets/{useResponsiveDrawer-Cn6gH3VG.js → useResponsiveDrawer-D9ti7g-U.js} +1 -1
  40. package/dist/web/index.html +4 -4
  41. package/package.json +1 -1
  42. package/src/commands/channels.js +88 -95
  43. package/src/commands/toggle-proxy.js +26 -19
  44. package/src/platforms/drivers/claude/channels.js +11 -1
  45. package/src/platforms/drivers/claude/proxy.js +1 -2
  46. package/src/platforms/drivers/codex/channels.js +14 -2
  47. package/src/platforms/drivers/codex/proxy.js +1 -2
  48. package/src/platforms/drivers/dsh/api-operations.js +1 -1
  49. package/src/platforms/drivers/dsh/channels.js +3 -7
  50. package/src/platforms/drivers/dsh/proxy.js +0 -4
  51. package/src/platforms/drivers/gemini/channels.js +10 -12
  52. package/src/platforms/drivers/gemini/proxy.js +1 -2
  53. package/src/platforms/drivers/omp/channels.js +53 -13
  54. package/src/platforms/drivers/omp/gateway-routing.js +69 -33
  55. package/src/platforms/drivers/omp/gateway.js +102 -40
  56. package/src/platforms/drivers/omp/proxy.js +1 -2
  57. package/src/platforms/drivers/opencode/channels.js +53 -24
  58. package/src/platforms/drivers/opencode/proxy.js +1 -2
  59. package/src/platforms/manifest-schema.js +53 -1
  60. package/src/platforms/manifests/claude.json +11 -0
  61. package/src/platforms/manifests/codex.json +11 -16
  62. package/src/platforms/manifests/dsh.json +13 -0
  63. package/src/platforms/manifests/gemini.json +17 -16
  64. package/src/platforms/manifests/omp.json +6 -16
  65. package/src/platforms/manifests/opencode.json +21 -16
  66. package/src/server/api/platforms.js +29 -0
  67. package/src/server/services/memory-pressure.js +101 -0
  68. package/src/shared/channel-driver.js +24 -14
  69. package/dist/web/assets/index-aW6itxZg.js +0 -2
  70. package/dist/web/assets/platforms-DILqyP72.js +0 -1
  71. package/dist/web/assets/sessions-WXODMvLY.js +0 -1
@@ -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 => unwrapChannels('create', 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 = typeof services.updateChannel === 'function';
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
- await handleLegacySwitch(channels, services);
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 === false || typeof services.createChannel !== 'function') {
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 answers = await inquirer.prompt([
137
- {
138
- type: 'input',
139
- name: 'name',
140
- message: '渠道名称:',
141
- validate: (input) => {
142
- if (!input.trim()) {
143
- return '渠道名称不能为空';
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 extraQuestions = services.createQuestions || [];
182
- const extraAnswers = extraQuestions.length > 0
183
- ? await inquirer.prompt(extraQuestions)
184
- : {};
185
-
186
- const websiteUrl = answers.websiteUrl.trim() || undefined;
187
- const createInput = {
188
- ...services.createDefaults,
189
- ...answers,
190
- ...extraAnswers,
191
- name: answers.name.trim(),
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
- try {
237
- const { getSchedulerState } = require('../server/services/channel-scheduler');
238
- const sources = buildSchedulerSources();
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
- sources.forEach((source) => {
241
- const state = getSchedulerState(source.key);
242
- console.log(chalk.bold(`\n[${source.label}]`));
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
- } else {
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
- } catch (error) {
261
- console.log(chalk.red('无法读取调度状态: ' + error.message));
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(normalizedCliType);
333
+ broadcastSchedulerSnapshot(cliType);
341
334
  }
342
335
 
343
- await handleAdvancedConfig(services, normalizedCliType);
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('• 开启后会启动 OMP 专用本地网关,并把受管 provider 指向该网关'));
195
- console.log(chalk.gray(`• 可以通过 Web UI 或"渠道管理"功能调整启用的 ${toolName} 路由组`));
196
- console.log(chalk.gray('• 同路由组内由 coding-tool-x 动态选择渠道,跨模型 fallback 仍由 OMP 处理'));
197
- console.log(chalk.gray(`• OMP 网关地址: http://127.0.0.1:${defaultPort}\n`));
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('• 启用后会写入指向本地网关的 OMP models.yml provider 配置'));
208
- console.log(chalk.yellow('• 关闭后会保留当前渠道的直连 provider,不会清理 OMP 原生 auth.json\n'));
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
- ? '\n[START] 正在启用 OMP models.yml 受管 provider...\n'
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 ? 'OMP 受管 provider 启用失败' : '代理服务器启动失败');
242
+ throw new Error(services.managedProviderConfig ? `${serviceLabel}启用失败` : '代理服务器启动失败');
239
243
  }
240
244
 
241
245
  if (services.managedProviderConfig) {
242
- console.log(chalk.green('[OK] OMP models.yml 受管 provider 已启用'));
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(services.managedProviderConfig ? '\n[TIP] OMP 动态切换已启用!' : '\n[TIP] 动态切换已启用!'));
266
+ console.log(chalk.cyan(`\n[TIP] ${labels.startedMessage || `${proxyLabel}已启用`}!`));
263
267
  if (services.managedProviderConfig) {
264
- console.log(chalk.gray(' 现在可以通过"渠道管理"功能调整已启用的 OMP provider,并重新同步 models.yml\n'));
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(`• 动态切换: ${chalk.green('已启用')}`));
304
- console.log(chalk.gray(`• 配置模式: ${proxyStatus.mode || 'models-yml-provider-config'}`));
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('• OMP 将保留当前渠道的单一、直连 models.yml provider'));
314
- console.log(chalk.gray(`• 之后调整 ${toolName} provider 或模型会立即同步到 OMP\n`));
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
- ? '\n[STOP] 正在切换 OMP 到单渠道直连模式...\n'
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
- ? '[OK] OMP 已切换到单渠道直连模式'
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(services.managedProviderConfig ? '\n[TIP] OMP 动态切换已关闭' : '\n[TIP] 动态切换已关闭'));
374
+ console.log(chalk.cyan(`\n[TIP] ${labels.stoppedMessage || `${proxyLabel}已关闭`}`));
368
375
  if (services.managedProviderConfig) {
369
- console.log(chalk.gray(' 当前渠道保持直连;后续渠道和模型修改会立即同步到 OMP\n'));
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
- cliMetadata: { supportsCliCreate: true, supportsCliToggle: true, defaultPort: 20089, createDefaults: { wireApi: 'responses' } },
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));
@@ -12,10 +12,6 @@ function createDriver(context = {}) {
12
12
  status: 'getDshProxyStatus',
13
13
  start: 'startDshProxyServer',
14
14
  stop: 'stopDshProxyServer'
15
- },
16
- cliMetadata: {
17
- managedProviderConfig: true,
18
- defaultPort: 20093
19
15
  }
20
16
  });
21
17
  }
@@ -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