coding-tool-x 3.9.6-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 (70) hide show
  1. package/dist/web/assets/{AgentsDrawer-D-1A92Ig.js → AgentsDrawer-D2oYKcOj.js} +1 -1
  2. package/dist/web/assets/{Analytics-B2O5txNK.js → Analytics-Cy3QSSrn.js} +1 -1
  3. package/dist/web/assets/{AssetPathField-DuohRI-K.js → AssetPathField-BGuw5ZR7.js} +1 -1
  4. package/dist/web/assets/{ChatHistoryDrawer-CjyehFJ5.js → ChatHistoryDrawer-BZ8FY_-9.js} +1 -1
  5. package/dist/web/assets/{CollapseItem--khwfagQ.js → CollapseItem-DOoxtmtj.js} +1 -1
  6. package/dist/web/assets/{CommandsDrawer-gZsBcvyW.js → CommandsDrawer-YCgZAeNx.js} +1 -1
  7. package/dist/web/assets/{ConfigExportDrawer-CJnljMGE.js → ConfigExportDrawer-BlAHQidK.js} +1 -1
  8. package/dist/web/assets/{ConfigTemplates-wkwjB2C5.js → ConfigTemplates-C6qSF4Ht.js} +1 -1
  9. package/dist/web/assets/{ConfigTemplatesDrawer-hAGvUnSt.js → ConfigTemplatesDrawer-lRT_6-jD.js} +1 -1
  10. package/dist/web/assets/{ConfigTemplatesPanel-BfF8qmL_.js → ConfigTemplatesPanel-DrS57Y8V.js} +1 -1
  11. package/dist/web/assets/{FavoritesDrawer-YeAnYTuA.js → FavoritesDrawer-CuOcHtqM.js} +1 -1
  12. package/dist/web/assets/{GatewayConvertDrawer-D1znBVev.js → GatewayConvertDrawer-b0pIvH02.js} +1 -1
  13. package/dist/web/assets/{Home-CSsVyr4n.js → Home-DvfQcP4b.js} +1 -1
  14. package/dist/web/assets/{MarkdownEditor-CeLdWA-c.js → MarkdownEditor-BLS6HmBb.js} +1 -1
  15. package/dist/web/assets/{McpDrawer-z-veNi2j.js → McpDrawer-BHt7zFf0.js} +1 -1
  16. package/dist/web/assets/{McpFormDrawer-DgDBnMOk.js → McpFormDrawer-DpTMHc-e.js} +1 -1
  17. package/dist/web/assets/{PluginManager-DFYt_Xmd.js → PluginManager-CYAuy5dm.js} +1 -1
  18. package/dist/web/assets/{PluginsDrawer-Dqpb2UsR.js → PluginsDrawer-CoyprWNl.js} +1 -1
  19. package/dist/web/assets/{PluginsPanel-CvcT0Up3.js → PluginsPanel-C3AzXlkH.js} +1 -1
  20. package/dist/web/assets/{ProjectList-DjwDDvVo.js → ProjectList-pJo0xp0Q.js} +1 -1
  21. package/dist/web/assets/{PromptsDrawer-B2vI4UfM.js → PromptsDrawer-CM-irN84.js} +1 -1
  22. package/dist/web/assets/{RecentSessionsDrawer-DTbikshr.js → RecentSessionsDrawer-l5nO_5bS.js} +1 -1
  23. package/dist/web/assets/{SessionCard-AKJF0KLk.js → SessionCard-BqTFOt05.js} +1 -1
  24. package/dist/web/assets/{SessionList-C2kxSHyw.js → SessionList-Bs_YFKW5.js} +1 -1
  25. package/dist/web/assets/{SettingsDrawer-CYNU3Scm.js → SettingsDrawer-COszrj_Y.js} +1 -1
  26. package/dist/web/assets/{SkillManager-MMDMnZuA.js → SkillManager-DgLhL2Qv.js} +1 -1
  27. package/dist/web/assets/{SkillsDrawer-Bg6QLvgm.js → SkillsDrawer-CT69YSEE.js} +1 -1
  28. package/dist/web/assets/{SkillsPanel-CDUSYnfc.js → SkillsPanel-Dm8FkYD6.js} +1 -1
  29. package/dist/web/assets/{Upload-DMr2S_D5.js → Upload-BHTymsxe.js} +1 -1
  30. package/dist/web/assets/{WorkspaceDrawer-VrUPX-Pa.js → WorkspaceDrawer-DLXh_c7u.js} +1 -1
  31. package/dist/web/assets/{WorkspaceManager-q76RE4Ss.js → WorkspaceManager-E_IoRTlC.js} +1 -1
  32. package/dist/web/assets/{WorkspacePanel-CSDGZMrU.js → WorkspacePanel-DLlTIU8Y.js} +1 -1
  33. package/dist/web/assets/index-DP0Ggf-8.js +2 -0
  34. package/dist/web/assets/index-DjIuAtaS.css +1 -0
  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-DXmsFFe1.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 -40
  54. package/src/platforms/drivers/omp/gateway-routing.js +0 -54
  55. package/src/platforms/drivers/omp/proxy.js +1 -2
  56. package/src/platforms/drivers/opencode/channels.js +53 -24
  57. package/src/platforms/drivers/opencode/proxy.js +1 -2
  58. package/src/platforms/manifest-schema.js +53 -1
  59. package/src/platforms/manifests/claude.json +11 -0
  60. package/src/platforms/manifests/codex.json +11 -16
  61. package/src/platforms/manifests/dsh.json +13 -0
  62. package/src/platforms/manifests/gemini.json +17 -16
  63. package/src/platforms/manifests/omp.json +6 -16
  64. package/src/platforms/manifests/opencode.json +21 -16
  65. package/src/server/api/platforms.js +29 -0
  66. package/src/shared/channel-driver.js +12 -14
  67. package/dist/web/assets/index-BXFl1mzC.js +0 -2
  68. package/dist/web/assets/index-CcIxENSq.css +0 -1
  69. package/dist/web/assets/platforms-DILqyP72.js +0 -1
  70. package/dist/web/assets/sessions-WXODMvLY.js +0 -1
@@ -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
 
@@ -2,59 +2,72 @@
2
2
 
3
3
  const { createChannelDriver } = require('../../../shared/channel-driver');
4
4
  const { ok, failed } = require('../../../shared/driver-result');
5
- const { buildOmpRoutingSummary } = require('./gateway-routing');
5
+
6
+ function providerApiForChannel(channel = {}, input = {}) {
7
+ if (channel.providerApi) return channel.providerApi;
8
+ if (channel.gatewaySourceType === 'claude') return 'anthropic-messages';
9
+ if (channel.gatewaySourceType === 'gemini') return 'google-generative-ai';
10
+ if (channel.gatewaySourceType === 'codex') return 'responses';
11
+ return input.wireApi || 'openai-completions';
12
+ }
13
+
14
+ function createChannelArgs(input, rest) {
15
+ if (!input || typeof input !== 'object' || Array.isArray(input)) return [input, ...rest];
16
+ const { name, baseUrl, apiKey, extra, ...fields } = input;
17
+ const inputFields = { ...fields, ...(extra || {}) };
18
+ const channel = {
19
+ wireApi: 'openai',
20
+ providerKey: '',
21
+ authMode: 'api_key',
22
+ oauthProviderId: '',
23
+ transport: '',
24
+ routingGroup: '',
25
+ gatewaySourceType: 'openai_compatible',
26
+ enabled: true,
27
+ model: null,
28
+ allowedModels: [],
29
+ speedTestModel: null,
30
+ presetId: null,
31
+ websiteUrl: '',
32
+ balanceToken: '',
33
+ balanceUserId: null,
34
+ models: [],
35
+ modelMetadataMode: 'auto',
36
+ modelBindings: [],
37
+ providerConfig: {},
38
+ ...inputFields
39
+ };
40
+ channel.enabled = channel.enabled !== false;
41
+ channel.allowedModels = Array.isArray(channel.allowedModels) ? channel.allowedModels : [];
42
+ channel.models = Array.isArray(channel.models) ? channel.models : [];
43
+ channel.modelBindings = Array.isArray(channel.modelBindings) ? channel.modelBindings : [];
44
+ channel.providerApi = providerApiForChannel(channel, inputFields);
45
+ return [name, baseUrl, apiKey, channel];
46
+ }
6
47
 
7
48
  function createDriver(context = {}) {
49
+ const { resetChannelHealth } = require('../../../server/services/channel-health');
8
50
  const driver = createChannelDriver({
9
51
  ...context,
10
52
  platform: 'omp',
11
53
  servicePath: './omp/channels-implementation',
12
54
  localServicePath: '../platforms/drivers/omp/channels-implementation',
13
55
  syncMethod: 'syncCurrentOmpChannel',
14
- createArgs: (input, rest) => typeof input === 'object'
15
- ? (() => {
16
- const { name, baseUrl, apiKey, extra, ...channelExtra } = input;
17
- return [name, baseUrl, apiKey, { ...channelExtra, ...extra }];
18
- })()
19
- : [input, ...rest],
20
- cliMetadata: {
21
- supportsCliCreate: false,
22
- supportsCliToggle: true,
23
- managedProviderConfig: true,
24
- defaultPort: 20092,
25
- createUnavailableMessage: '提示: OMP 渠道请通过 Web UI 或 API 添加后,再在这里启停调度。'
26
- },
56
+ modelListType: context.manifest?.channelModelType || 'openai_compatible',
57
+ createArgs: createChannelArgs,
27
58
  dashboardChannelShape: 'array'
28
59
  });
60
+ const saveOrder = driver.saveOrder;
61
+ driver.saveOrder = input => saveOrder(input?.order || input);
62
+ driver.resetHealth = input => {
63
+ const channelId = typeof input === 'string' ? input : input?.channelId || input?.body?.channelId;
64
+ if (!channelId) throw new Error('Channel id is required');
65
+ resetChannelHealth(channelId, 'omp');
66
+ return { channelId, reset: true };
67
+ };
29
68
  driver.syncManagedProviderExtension = (...args) => (
30
69
  driver._service().syncManagedProviderExtension(...args)
31
70
  );
32
- const getPoolStatus = driver.poolStatus;
33
- driver.poolStatus = () => {
34
- try {
35
- const service = driver._service();
36
- const modeState = service.loadManagedOmpModeState?.();
37
- const routingSummary = channels => buildOmpRoutingSummary(
38
- Array.isArray(channels) ? channels : channels?.channels || [],
39
- modeState?.gateway || {}
40
- );
41
- const result = getPoolStatus();
42
- if (result?.status !== 'ok') return result;
43
- const channels = service.getChannels();
44
- if (channels && typeof channels.then === 'function') {
45
- return channels.then(value => ok('omp', 'channels', 'poolStatus', {
46
- ...result.data,
47
- routingSummary: routingSummary(value)
48
- })).catch(error => failed('omp', 'channels', 'poolStatus', error));
49
- }
50
- return ok('omp', 'channels', 'poolStatus', {
51
- ...result.data,
52
- routingSummary: routingSummary(channels)
53
- });
54
- } catch (error) {
55
- return failed('omp', 'channels', 'poolStatus', error);
56
- }
57
- };
58
71
  driver.catalogMetadata = (input = {}) => {
59
72
  const operation = 'catalogMetadata';
60
73
  const body = input && typeof input === 'object' && Object.prototype.hasOwnProperty.call(input, 'body')
@@ -228,59 +228,6 @@ function channelSupportsOmpModel(channel = {}, modelId = '') {
228
228
  return declared.length === 0 || declared.includes(requested);
229
229
  }
230
230
 
231
- function buildOmpRoutingSummary(channels = [], gateway = {}) {
232
- const groups = [];
233
- const unsupportedChannels = [];
234
- collectRoutingGroups(channels).forEach((groupChannels) => {
235
- try {
236
- assertCompatibleGroup(groupChannels);
237
- } catch {
238
- groupChannels.forEach(channel => unsupportedChannels.push({
239
- id: channel.id,
240
- name: channel.name,
241
- reason: 'incompatible-routing-group'
242
- }));
243
- return;
244
- }
245
- const reason = getGroupUnsupportedReason(groupChannels, gateway);
246
- if (reason) {
247
- groupChannels.forEach(channel => unsupportedChannels.push({
248
- id: channel.id,
249
- name: channel.name,
250
- reason
251
- }));
252
- return;
253
- }
254
-
255
- const modelChannels = new Map();
256
- const unrestrictedChannelIds = [];
257
- groupChannels.forEach((channel) => {
258
- const modelIds = getDeclaredModelIds(channel);
259
- if (modelIds.length === 0) {
260
- unrestrictedChannelIds.push(channel.id);
261
- return;
262
- }
263
- modelIds.forEach((modelId) => {
264
- const key = normalizeOmpModelId(modelId).toLowerCase();
265
- if (!key) return;
266
- if (!modelChannels.has(key)) modelChannels.set(key, { id: modelId, channelIds: [] });
267
- modelChannels.get(key).channelIds.push(channel.id);
268
- });
269
- });
270
-
271
- groups.push({
272
- routingGroup: getRoutingGroup(groupChannels[0]),
273
- providerKey: getProviderKey(groupChannels[0]),
274
- providerApi: getProviderApi(groupChannels[0]),
275
- channelIds: groupChannels.map(channel => channel.id),
276
- channels: groupChannels.map(channel => ({ id: channel.id, name: channel.name || channel.id })),
277
- unrestrictedChannelIds,
278
- models: [...modelChannels.values()]
279
- });
280
- });
281
- return { groups, unsupportedChannels };
282
- }
283
-
284
231
  function buildManagedBaseUrl(host, port, routeToken) {
285
232
  return `http://${host}:${port}/omp/${routeToken}`;
286
233
  }
@@ -381,7 +328,6 @@ function resolveOmpGatewayRoute(pathname, channels, gateway) {
381
328
  }
382
329
 
383
330
  module.exports = {
384
- buildOmpRoutingSummary,
385
331
  channelSupportsOmpModel,
386
332
  normalizeOmpModelId,
387
333
  prepareManagedOmpChannels,
@@ -8,8 +8,7 @@ function createDriver(context = {}) {
8
8
  platform: 'omp',
9
9
  servicePath: './omp/proxy-implementation',
10
10
  localServicePath: '../platforms/drivers/omp/proxy-implementation',
11
- exports: { status: 'getOmpProxyStatus', start: 'startOmpProxyServer', stop: 'stopOmpProxyServer' },
12
- cliMetadata: { managedProviderConfig: true, defaultPort: 20092 }
11
+ exports: { status: 'getOmpProxyStatus', start: 'startOmpProxyServer', stop: 'stopOmpProxyServer' }
13
12
  });
14
13
  }
15
14
 
@@ -2,39 +2,68 @@
2
2
 
3
3
  const { createChannelDriver } = require('../../../shared/channel-driver');
4
4
 
5
+ function createChannelArgs(input, rest) {
6
+ if (!input || typeof input !== 'object' || Array.isArray(input)) return [input, ...rest];
7
+ const { name, baseUrl, apiKey, extra, ...fields } = input;
8
+ const inputFields = { ...fields, ...(extra || {}) };
9
+ const channel = {
10
+ wireApi: 'openai',
11
+ gatewaySourceType: 'codex',
12
+ enabled: true,
13
+ weight: 1,
14
+ maxConcurrency: null,
15
+ model: null,
16
+ modelRedirects: [],
17
+ speedTestModel: null,
18
+ presetId: null,
19
+ providerKey: '',
20
+ websiteUrl: '',
21
+ allowedModels: [],
22
+ authMode: 'api_key',
23
+ oauthProviderId: '',
24
+ balanceToken: '',
25
+ balanceUserId: null,
26
+ ...inputFields
27
+ };
28
+ channel.wireApi = channel.wireApi || 'openai';
29
+ channel.gatewaySourceType = channel.gatewaySourceType || 'codex';
30
+ channel.enabled = channel.enabled !== false;
31
+ channel.weight = channel.weight || 1;
32
+ channel.maxConcurrency = channel.maxConcurrency || null;
33
+ channel.model = channel.model || null;
34
+ channel.modelRedirects = channel.modelRedirects || [];
35
+ channel.speedTestModel = channel.speedTestModel || null;
36
+ channel.presetId = channel.presetId || null;
37
+ channel.providerKey = channel.providerKey || '';
38
+ channel.websiteUrl = channel.websiteUrl || '';
39
+ channel.allowedModels = channel.allowedModels || [];
40
+ channel.authMode = channel.authMode || 'api_key';
41
+ channel.oauthProviderId = channel.oauthProviderId || '';
42
+ channel.balanceToken = channel.balanceToken || '';
43
+ channel.balanceUserId = channel.balanceUserId || null;
44
+ return [name, baseUrl, apiKey, channel];
45
+ }
46
+
5
47
  function createDriver(context = {}) {
48
+ const { resetChannelHealth } = require('../../../server/services/channel-health');
6
49
  const driver = createChannelDriver({
7
50
  ...context,
8
51
  platform: 'opencode',
9
52
  servicePath: './opencode/channels-implementation',
10
53
  localServicePath: '../opencode/channels-implementation',
11
54
  syncMethod: 'syncCurrentOpenCodeChannel',
12
- createArgs: (input, rest) => typeof input === 'object'
13
- ? (() => {
14
- const { name, baseUrl, apiKey, extra, ...channelExtra } = input;
15
- return [name, baseUrl, apiKey, { ...channelExtra, ...extra }];
16
- })()
17
- : [input, ...rest],
18
- cliMetadata: {
19
- supportsCliCreate: true,
20
- supportsCliToggle: true,
21
- defaultPort: 20091,
22
- createQuestions: [
23
- {
24
- type: 'input',
25
- name: 'wireApi',
26
- message: 'Wire API (默认: openai):',
27
- default: 'openai'
28
- },
29
- {
30
- type: 'input',
31
- name: 'model',
32
- message: '默认模型(可选,直接回车跳过):'
33
- }
34
- ]
35
- },
55
+ modelListType: context.manifest?.channelModelType || 'openai_compatible',
56
+ createArgs: createChannelArgs,
36
57
  dashboardChannelShape: 'array'
37
58
  });
59
+ const saveOrder = driver.saveOrder;
60
+ driver.saveOrder = input => saveOrder(input?.order || input);
61
+ driver.resetHealth = input => {
62
+ const channelId = typeof input === 'string' ? input : input?.channelId || input?.body?.channelId;
63
+ if (!channelId) throw new Error('Channel id is required');
64
+ resetChannelHealth(channelId, 'opencode');
65
+ return { channelId, reset: true };
66
+ };
38
67
  const auth = require('../../channel-auth-service');
39
68
  driver.getAuth = context => auth.getChannelAuth('opencode', { channelId: context?.params?.channelId || context?.query?.channelId || '' });
40
69
  driver.syncLocalAuth = context => auth.syncLocalChannelAuth('opencode', { channelId: context?.body?.channelId || context?.params?.channelId || context?.query?.channelId || '' });
@@ -8,8 +8,7 @@ function createDriver(context = {}) {
8
8
  platform: 'opencode',
9
9
  servicePath: './opencode/proxy-implementation',
10
10
  localServicePath: '../opencode/proxy-implementation',
11
- exports: { status: 'getOpenCodeProxyStatus', start: 'startOpenCodeProxyServer', stop: 'stopOpenCodeProxyServer' },
12
- cliMetadata: { defaultPort: 20091 }
11
+ exports: { status: 'getOpenCodeProxyStatus', start: 'startOpenCodeProxyServer', stop: 'stopOpenCodeProxyServer' }
13
12
  });
14
13
  }
15
14