coding-tool-x 3.8.8 → 3.8.10

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 (134) hide show
  1. package/README.md +56 -388
  2. package/dist/web/assets/AgentsDrawer-Bam7kBjG.js +1 -0
  3. package/dist/web/assets/Analytics-D5dUWXEa.js +1 -0
  4. package/dist/web/assets/AssetPathField-CLqalYv-.js +1 -0
  5. package/dist/web/assets/{ChatHistoryDrawer-CrfS0KYV.js → ChatHistoryDrawer-C1At65O3.js} +1 -1
  6. package/dist/web/assets/{CollapseItem-ClMsJuUu.js → CollapseItem-CVWojShK.js} +1 -1
  7. package/dist/web/assets/CommandsDrawer-BEoypGBf.js +1 -0
  8. package/dist/web/assets/ConfigExportDrawer-DBvFDDWK.js +1 -0
  9. package/dist/web/assets/ConfigTemplates-BpaWG-iC.js +1 -0
  10. package/dist/web/assets/{ConfigTemplatesDrawer-BqM32Im7.js → ConfigTemplatesDrawer-C2lvyKMf.js} +1 -1
  11. package/dist/web/assets/ConfigTemplatesPanel-CorOY7kY.js +1 -0
  12. package/dist/web/assets/{Divider-DsdbkHrG.js → Divider-BLLhMTSt.js} +1 -1
  13. package/dist/web/assets/FavoritesDrawer-uW88N6te.js +1 -0
  14. package/dist/web/assets/GatewayConvertDrawer-i5Wt9Dd9.js +1 -0
  15. package/dist/web/assets/Home-BIxWvm29.js +1 -0
  16. package/dist/web/assets/{MarkdownEditor-BMMUufSc.js → MarkdownEditor-Ul0bSGj-.js} +1 -1
  17. package/dist/web/assets/McpDrawer-Cc2xq77I.js +1 -0
  18. package/dist/web/assets/McpFormDrawer-DLHX_nrI.js +1 -0
  19. package/dist/web/assets/{PluginManager-C4kJlpVX.js → PluginManager-BGQ0xF2F.js} +1 -1
  20. package/dist/web/assets/{PluginsDrawer-05R4k15x.js → PluginsDrawer-STwHEqiF.js} +1 -1
  21. package/dist/web/assets/PluginsPanel-1zuNP7da.js +1 -0
  22. package/dist/web/assets/ProjectList-VbIYSHbH.css +1 -0
  23. package/dist/web/assets/ProjectList-_pe2nk9c.js +1 -0
  24. package/dist/web/assets/PromptsDrawer-CJN1xZk7.js +1 -0
  25. package/dist/web/assets/RecentSessionsDrawer-DX1HI8Nm.js +1 -0
  26. package/dist/web/assets/SessionCard-Op6PHvi0.js +1 -0
  27. package/dist/web/assets/SessionList-Da_TAEYr.js +1 -0
  28. package/dist/web/assets/SettingsDrawer-DKLd4Sjy.js +1 -0
  29. package/dist/web/assets/{SkillManager-D9jkNuRM.js → SkillManager-PcAa5EbF.js} +1 -1
  30. package/dist/web/assets/{SkillsDrawer-0Z4IefxC.js → SkillsDrawer-DPdU4p6C.js} +1 -1
  31. package/dist/web/assets/SkillsPanel-B9qzRqjd.js +1 -0
  32. package/dist/web/assets/Upload-e1M9Yp5z.js +1 -0
  33. package/dist/web/assets/{WorkspaceDrawer-eujtWpoq.js → WorkspaceDrawer-oS6mB1VZ.js} +1 -1
  34. package/dist/web/assets/{WorkspaceManager-CQ-8Ej68.js → WorkspaceManager-6VvRWe3r.js} +1 -1
  35. package/dist/web/assets/WorkspacePanel-CYq0Z8iY.js +1 -0
  36. package/dist/web/assets/client-BgcNg4Bt.js +1 -0
  37. package/dist/web/assets/headers-DbowkmZJ.js +1 -0
  38. package/dist/web/assets/{icons-DmFe4b5j.js → icons-B-AzZoeE.js} +1 -1
  39. package/dist/web/assets/index-BlUXw84U.js +2 -0
  40. package/dist/web/assets/index-Bn8BUnwP.css +1 -0
  41. package/dist/web/assets/{light-0eavxNvt.js → light-C2X7hegj.js} +1 -1
  42. package/dist/web/assets/platforms-DrzAtnjY.js +1 -0
  43. package/dist/web/assets/platforms-mqqe_82d.js +1 -0
  44. package/dist/web/assets/project-config-BVt5s-pH.js +1 -0
  45. package/dist/web/assets/sessions-53-Fr68H.js +1 -0
  46. package/dist/web/assets/{sessions-D4yQtfKb.js → sessions-CaWUaysM.js} +1 -1
  47. package/dist/web/assets/use-message-DBsCm7FJ.js +1 -0
  48. package/dist/web/assets/{useResponsiveDrawer-DZd5rNk5.js → useResponsiveDrawer-ChHZrTfl.js} +1 -1
  49. package/dist/web/assets/{workspaces-mjyPVGS8.js → workspaces-DQuZd4qw.js} +1 -1
  50. package/dist/web/index.html +8 -8
  51. package/package.json +4 -2
  52. package/src/commands/daemon.js +27 -4
  53. package/src/commands/export-config.js +17 -211
  54. package/src/config/model-metadata.json +180 -16
  55. package/src/platforms/channel-auth-registry.js +36 -6
  56. package/src/platforms/channel-auth-service.js +13 -3
  57. package/src/platforms/commands-service.js +28 -0
  58. package/src/platforms/driver-registry.js +1 -0
  59. package/src/platforms/drivers/claude/channels-implementation.js +79 -90
  60. package/src/platforms/drivers/claude/native-config-implementation.js +3 -4
  61. package/src/platforms/drivers/codex/channels-implementation.js +34 -49
  62. package/src/platforms/drivers/codex/native-config-implementation.js +3 -51
  63. package/src/platforms/drivers/dsh/api-operations.js +1 -1
  64. package/src/platforms/drivers/dsh/channels-implementation.js +28 -8
  65. package/src/platforms/drivers/dsh/channels.js +0 -18
  66. package/src/platforms/drivers/dsh/common.js +7 -0
  67. package/src/platforms/drivers/dsh/native-config.js +7 -2
  68. package/src/platforms/drivers/gemini/channels-implementation.js +47 -55
  69. package/src/platforms/drivers/gemini/native-config-implementation.js +8 -20
  70. package/src/platforms/drivers/generic-native-config.js +20 -0
  71. package/src/platforms/drivers/native-config-snapshot.js +30 -4
  72. package/src/platforms/drivers/omp/channels-implementation.js +51 -17
  73. package/src/platforms/drivers/omp/native-config-implementation.js +10 -17
  74. package/src/platforms/drivers/omp/prompts.js +58 -2
  75. package/src/platforms/drivers/omp/proxy-implementation.js +25 -1
  76. package/src/platforms/drivers/opencode/channels-implementation.js +31 -22
  77. package/src/platforms/drivers/opencode/channels.js +6 -1
  78. package/src/platforms/drivers/opencode/native-config-implementation.js +3 -67
  79. package/src/platforms/manifest-schema.js +35 -2
  80. package/src/platforms/manifests/claude.json +0 -8
  81. package/src/platforms/manifests/codex.json +0 -8
  82. package/src/platforms/manifests/dsh.json +6 -9
  83. package/src/platforms/manifests/gemini.json +0 -8
  84. package/src/platforms/manifests/omp.json +2 -9
  85. package/src/platforms/manifests/opencode.json +28 -8
  86. package/src/platforms/native-oauth-adapters.js +351 -51
  87. package/src/platforms/oauth-credentials-service.js +344 -13
  88. package/src/server/index.js +37 -1
  89. package/src/server/services/bounded-content.js +60 -0
  90. package/src/server/services/channel-balance.js +10 -4
  91. package/src/server/services/config-export-service.js +2 -2
  92. package/src/server/services/config-templates-service.js +5 -1
  93. package/src/server/services/plugins-service.js +162 -85
  94. package/src/server/services/session-history-maintenance-worker.js +71 -0
  95. package/src/server/services/session-history-maintenance.js +166 -0
  96. package/src/server/services/skill-service.js +23 -3
  97. package/src/server/services/speed-test.js +0 -72
  98. package/src/shared/channel-driver.js +16 -0
  99. package/src/shared/driver-factories/api.js +1 -0
  100. package/src/utils/native-config-patcher.js +474 -0
  101. package/dist/web/assets/AgentsDrawer-DRQuwi0m.js +0 -1
  102. package/dist/web/assets/Analytics-Cf5KwEdj.js +0 -1
  103. package/dist/web/assets/AssetPathField-DyniM0pF.js +0 -1
  104. package/dist/web/assets/CommandsDrawer-CzHelf56.js +0 -1
  105. package/dist/web/assets/ConfigExportDrawer-BEZ8wTby.js +0 -1
  106. package/dist/web/assets/ConfigTemplates-Crg3xWe5.js +0 -1
  107. package/dist/web/assets/ConfigTemplatesPanel-lTdutC8H.js +0 -1
  108. package/dist/web/assets/FavoritesDrawer-DDyWUBJR.js +0 -1
  109. package/dist/web/assets/GatewayConvertDrawer-Rigtt_5J.js +0 -1
  110. package/dist/web/assets/Home-tCSKmSHV.js +0 -1
  111. package/dist/web/assets/McpDrawer-CmE-d7VW.js +0 -1
  112. package/dist/web/assets/McpFormDrawer-ByvS18od.js +0 -1
  113. package/dist/web/assets/PluginsPanel-BCoEs7Og.js +0 -1
  114. package/dist/web/assets/ProjectList-BJt1OIaI.js +0 -1
  115. package/dist/web/assets/ProjectList-DG3yu8xZ.css +0 -1
  116. package/dist/web/assets/PromptsDrawer-DnwBf_oW.js +0 -1
  117. package/dist/web/assets/RecentSessionsDrawer-B7d-38wH.js +0 -1
  118. package/dist/web/assets/SessionCard-BUgjHQwX.js +0 -1
  119. package/dist/web/assets/SessionList-B-bXZRFk.js +0 -1
  120. package/dist/web/assets/SettingsDrawer-BITzjQLl.js +0 -1
  121. package/dist/web/assets/SkillsPanel-BKIVOj7n.js +0 -1
  122. package/dist/web/assets/SpeedTestDrawer-Bx1bYf5i.js +0 -1
  123. package/dist/web/assets/SpeedTestDrawer-Dc0WToJ_.css +0 -1
  124. package/dist/web/assets/Upload-CzC4rGI4.js +0 -1
  125. package/dist/web/assets/WorkspacePanel-Cql5qE2R.js +0 -1
  126. package/dist/web/assets/client-UDwXBP9i.js +0 -1
  127. package/dist/web/assets/headers-BqDmkMjy.js +0 -1
  128. package/dist/web/assets/index-Cmmwj3Fx.js +0 -2
  129. package/dist/web/assets/index-Cnbe696C.css +0 -1
  130. package/dist/web/assets/platforms-BmzzwnSw.js +0 -1
  131. package/dist/web/assets/platforms-DWXgmy2w.js +0 -1
  132. package/dist/web/assets/project-config-B2baPzYL.js +0 -1
  133. package/dist/web/assets/sessions-DqlbocLJ.js +0 -1
  134. package/dist/web/assets/use-message-DqCwefG6.js +0 -1
@@ -68,6 +68,10 @@ function normalizeChannelName(value) {
68
68
  .replace(/\s+/g, ' ');
69
69
  }
70
70
 
71
+ function normalizeAuthMode(value) {
72
+ return ['api_key', 'oauth', 'none'].includes(value) ? value : 'api_key';
73
+ }
74
+
71
75
  // 获取渠道存储文件路径
72
76
  function getChannelsFilePath() {
73
77
  const channelsDir = path.dirname(configuredState.channels);
@@ -98,24 +102,7 @@ function loadChannels() {
98
102
  const data = JSON.parse(content);
99
103
  // 确保渠道有必要字段(兼容旧数据)
100
104
  if (data.channels) {
101
- data.channels = data.channels.map(ch => {
102
- const normalized = {
103
- ...ch,
104
- enabled: ch.enabled !== false,
105
- weight: ch.weight || 1,
106
- maxConcurrency: ch.maxConcurrency || null,
107
- balanceToken: ch.balanceToken || '',
108
- balanceUserId: ch.balanceUserId || null,
109
- modelRedirects: ch.modelRedirects || [],
110
- speedTestModel: ch.speedTestModel || null,
111
- wireApi: ch.wireApi || 'openai', // OpenCode 默认使用 OpenAI 兼容格式
112
- gatewaySourceType: normalizeGatewaySourceType(ch.gatewaySourceType, 'codex'),
113
- allowedModels: ch.allowedModels || []
114
- };
115
- normalized.providerKey = ch.providerKey || deriveProviderKey(normalized);
116
- normalized.websiteUrl = resolveChannelWebsiteUrl('opencode', normalized);
117
- return normalized;
118
- });
105
+ data.channels = data.channels.map(ch => normalizeOpenCodeChannel(ch));
119
106
  }
120
107
  return data;
121
108
  } catch (err) {
@@ -191,12 +178,18 @@ function getChannels() {
191
178
  function createChannel(name, baseUrl, apiKey, extraConfig = {}) {
192
179
  const data = loadChannels();
193
180
  const isProxyRunning = getOpenCodeProxyRunning();
181
+ const authMode = normalizeAuthMode(extraConfig.authMode);
194
182
 
195
183
  const newChannel = {
196
184
  id: crypto.randomUUID(),
197
185
  name,
198
- baseUrl,
199
- apiKey,
186
+ baseUrl: authMode === 'oauth' || authMode === 'none' ? '' : baseUrl,
187
+ apiKey: authMode === 'oauth' || authMode === 'none' ? '' : apiKey,
188
+ authMode,
189
+ authRef: authMode === 'oauth' ? (extraConfig.authRef || {}) : undefined,
190
+ authSource: authMode === 'oauth' ? (extraConfig.authSource || 'synced-local') : undefined,
191
+ authStatus: authMode === 'oauth' ? (extraConfig.authStatus || 'available') : undefined,
192
+ oauthProviderId: authMode === 'oauth' ? (extraConfig.oauthProviderId || extraConfig.providerKey || '') : '',
200
193
  wireApi: extraConfig.wireApi || 'openai',
201
194
  enabled: extraConfig.enabled !== false,
202
195
  weight: extraConfig.weight || 1,
@@ -248,7 +241,7 @@ function updateChannel(channelId, updates) {
248
241
 
249
242
  const oldChannel = data.channels[index];
250
243
 
251
- const merged = {
244
+ const merged = normalizeOpenCodeChannel({
252
245
  ...oldChannel,
253
246
  ...updates,
254
247
  id: channelId,
@@ -264,7 +257,7 @@ function updateChannel(channelId, updates) {
264
257
  'codex'
265
258
  ),
266
259
  updatedAt: Date.now()
267
- };
260
+ });
268
261
  merged.providerKey = updates.providerKey || oldChannel.providerKey || deriveProviderKey(merged);
269
262
  merged.websiteUrl = resolveChannelWebsiteUrl('opencode', merged);
270
263
  data.channels[index] = merged;
@@ -289,6 +282,10 @@ function updateChannel(channelId, updates) {
289
282
  if (!isProxyRunning) {
290
283
  if (oldChannel.enabled === false && merged.enabled !== false) {
291
284
  syncManagedChannelConfig(data.channels, merged);
285
+ } else if (oldChannel.enabled !== false && merged.enabled === false) {
286
+ const activeChannel = resolveCurrentManagedChannel(data.channels);
287
+ if (activeChannel) syncManagedChannelConfig(data.channels, activeChannel);
288
+ else clearManagedChannelConfig();
292
289
  } else {
293
290
  const activeChannel = resolveCurrentManagedChannel(data.channels);
294
291
  if (merged.enabled !== false && activeChannel?.id === merged.id) {
@@ -494,9 +491,11 @@ async function getEffectiveApiKey(channel) {
494
491
  }
495
492
 
496
493
  function disableAllChannels() {
494
+ const proxyRunning = getOpenCodeProxyRunning();
497
495
  const data = loadChannels();
498
496
  data.channels.forEach(ch => { ch.enabled = false; });
499
497
  saveChannels(data);
498
+ if (!proxyRunning) clearManagedChannelConfig();
500
499
  }
501
500
 
502
501
  function getCurrentOpenCodeProviderId(config = {}) {
@@ -524,6 +523,7 @@ function findOpenCodeExistingByProvider(channels = [], providerId = '', baseUrl
524
523
  }
525
524
 
526
525
  function normalizeOpenCodeChannel(channel = {}) {
526
+ const authMode = normalizeAuthMode(channel.authMode);
527
527
  const normalized = {
528
528
  ...channel,
529
529
  enabled: channel.enabled !== false,
@@ -537,6 +537,15 @@ function normalizeOpenCodeChannel(channel = {}) {
537
537
  gatewaySourceType: normalizeGatewaySourceType(channel.gatewaySourceType, 'codex'),
538
538
  allowedModels: Array.isArray(channel.allowedModels) ? channel.allowedModels : []
539
539
  };
540
+ normalized.authMode = authMode;
541
+ normalized.authRef = authMode === 'oauth' ? (channel.authRef || {}) : undefined;
542
+ normalized.authSource = authMode === 'oauth' ? (channel.authSource || 'synced-local') : undefined;
543
+ normalized.authStatus = authMode === 'oauth' ? (channel.authStatus || 'available') : undefined;
544
+ normalized.oauthProviderId = authMode === 'oauth' ? (channel.oauthProviderId || channel.providerKey || '') : '';
545
+ if (authMode === 'oauth' || authMode === 'none') {
546
+ normalized.baseUrl = '';
547
+ normalized.apiKey = '';
548
+ }
540
549
  normalized.providerKey = normalized.providerKey || deriveProviderKey(normalized);
541
550
  normalized.websiteUrl = resolveChannelWebsiteUrl('opencode', normalized);
542
551
  return normalized;
@@ -3,7 +3,7 @@
3
3
  const { createChannelDriver } = require('../../../shared/channel-driver');
4
4
 
5
5
  function createDriver(context = {}) {
6
- return createChannelDriver({
6
+ const driver = createChannelDriver({
7
7
  ...context,
8
8
  platform: 'opencode',
9
9
  servicePath: './opencode/channels-implementation',
@@ -35,6 +35,11 @@ function createDriver(context = {}) {
35
35
  },
36
36
  dashboardChannelShape: 'array'
37
37
  });
38
+ const auth = require('../../channel-auth-service');
39
+ driver.getAuth = context => auth.getChannelAuth('opencode', { channelId: context?.params?.channelId || context?.query?.channelId || '' });
40
+ driver.syncLocalAuth = context => auth.syncLocalChannelAuth('opencode', { channelId: context?.body?.channelId || context?.params?.channelId || context?.query?.channelId || '' });
41
+ driver.getAuthQuota = context => auth.fetchChannelAuthQuota('opencode', context?.params?.channelId, { refresh: context?.query?.refresh === 'true' });
42
+ return driver;
38
43
  }
39
44
 
40
45
  module.exports = { createDriver };
@@ -1,5 +1,6 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
+ const { readJsoncFile, writeJsoncFile } = require('../../../utils/native-config-patcher');
3
4
  const { NATIVE_PATHS } = require('../../../config/paths');
4
5
  const { resolveModelMetadata } = require('../../../config/model-metadata');
5
6
  const { resolveModelPricing } = require('../../../server/utils/pricing');
@@ -61,79 +62,18 @@ function selectConfigPath() {
61
62
  return CONFIG_PATHS.opencode;
62
63
  }
63
64
 
64
- function stripJsonComments(input) {
65
- let result = '';
66
- let inString = false;
67
- let stringChar = '';
68
- let i = 0;
69
-
70
- while (i < input.length) {
71
- const ch = input[i];
72
- const next = input[i + 1];
73
-
74
- if (inString) {
75
- result += ch;
76
- if (ch === '\\') {
77
- if (next) {
78
- result += next;
79
- i += 2;
80
- continue;
81
- }
82
- } else if (ch === stringChar) {
83
- inString = false;
84
- }
85
- i += 1;
86
- continue;
87
- }
88
-
89
- if (ch === '"' || ch === '\'') {
90
- inString = true;
91
- stringChar = ch;
92
- result += ch;
93
- i += 1;
94
- continue;
95
- }
96
-
97
- if (ch === '/' && next === '/') {
98
- i += 2;
99
- while (i < input.length && input[i] !== '\n') i += 1;
100
- continue;
101
- }
102
-
103
- if (ch === '/' && next === '*') {
104
- i += 2;
105
- while (i < input.length - 1 && !(input[i] === '*' && input[i + 1] === '/')) i += 1;
106
- i += 2;
107
- continue;
108
- }
109
-
110
- result += ch;
111
- i += 1;
112
- }
113
-
114
- return result;
115
- }
116
-
117
65
  function readConfig(filePath) {
118
66
  if (!fs.existsSync(filePath)) return {};
119
67
 
120
- const raw = fs.readFileSync(filePath, 'utf8');
121
- if (!raw.trim()) return {};
122
-
123
68
  try {
124
- if (filePath.endsWith('.jsonc')) {
125
- return JSON.parse(stripJsonComments(raw));
126
- }
127
- return JSON.parse(raw);
69
+ return readJsoncFile(filePath);
128
70
  } catch (err) {
129
71
  throw new Error(`Failed to parse ${path.basename(filePath)}: ${err.message}`);
130
72
  }
131
73
  }
132
74
 
133
75
  function writeConfig(filePath, config) {
134
- ensureConfigDir();
135
- const content = JSON.stringify(config, null, 2);
136
- fs.writeFileSync(filePath, content, 'utf8');
76
+ writeJsoncFile(filePath, config);
137
77
  }
138
78
 
139
79
  function normalizeOpenCodeModel(modelId, providerId) {
@@ -520,7 +460,6 @@ function setProxyConfig(proxyPort, options = {}) {
520
460
 
521
461
  function setChannelConfig(channel = {}) {
522
462
  const filePath = selectConfigPath();
523
- backupConfig(filePath);
524
463
 
525
464
  const current = readConfig(filePath);
526
465
  const next = (current && typeof current === 'object') ? current : {};
@@ -529,9 +468,6 @@ function setChannelConfig(channel = {}) {
529
468
  next.provider = {};
530
469
  }
531
470
 
532
- const removedProviderKeys = clearManagedProviders(next);
533
- clearManagedModelRef(next, removedProviderKeys);
534
-
535
471
  const baseProviderKey = sanitizeProviderKey(channel.providerKey || channel.name || 'ctx-channel');
536
472
  let providerKey = baseProviderKey;
537
473
  let suffix = 2;
@@ -11,6 +11,7 @@ const DRIVER_IDS = new Set([
11
11
  'generic-openai-compatible',
12
12
  'generic-mcp',
13
13
  'generic-prompt',
14
+ 'generic-native-config',
14
15
  'legacy:claude',
15
16
  'legacy:codex',
16
17
  'legacy:gemini',
@@ -80,7 +81,7 @@ const CAPABILITY_OPERATIONS = Object.freeze({
80
81
  'updateSkill',
81
82
  'deleteSkill'
82
83
  ]),
83
- channels: new Set(['applyToSettings', 'bestForRestore', 'catalogMetadata', 'create', 'current', 'enabled', 'getAuth', 'getAuthQuota', 'list', 'models', 'order', 'poolStatus', 'probeModels', 'remove', 'resetHealth', 'speedTest', 'speedTestAll', 'sync', 'syncLocalAuth', 'update']),
84
+ channels: new Set(['applyToSettings', 'bestForRestore', 'catalogMetadata', 'create', 'current', 'enabled', 'getAuth', 'getAuthQuota', 'list', 'models', 'order', 'poolStatus', 'probeModels', 'remove', 'resetHealth', 'speedTest', 'sync', 'syncLocalAuth', 'update']),
84
85
  health: new Set(['healthCheck']),
85
86
  hooks: new Set(['getHooks', 'saveHooks', 'testHooks']),
86
87
  projects: new Set(['createProject', 'deleteProject', 'listProjects', 'saveProjectOrder']),
@@ -198,6 +199,19 @@ const schema = {
198
199
  sessionMapping: { type: 'object', additionalProperties: { type: 'string' } },
199
200
  sessionGlob: { type: 'string', minLength: 1 },
200
201
  resourceMappings: { type: 'object', additionalProperties: { type: 'string' } },
202
+ nativeConfigSnapshot: {
203
+ type: 'object',
204
+ additionalProperties: {
205
+ type: 'object',
206
+ additionalProperties: false,
207
+ required: ['path', 'format'],
208
+ properties: {
209
+ path: { type: 'string', minLength: 1 },
210
+ format: { enum: ['text', 'json', 'yaml', 'directory'] },
211
+ mode: { type: 'integer', minimum: 0, maximum: 511 }
212
+ }
213
+ }
214
+ },
201
215
  projectResources: {
202
216
  type: 'object',
203
217
  additionalProperties: false,
@@ -333,6 +347,17 @@ function validateProjectResources(manifest, errors) {
333
347
  }
334
348
  }
335
349
 
350
+ function validateNativeConfigSnapshot(manifest, errors) {
351
+ for (const [key, spec] of Object.entries(manifest?.nativeConfigSnapshot || {})) {
352
+ if (!isSafeRelativePath(spec?.path)) {
353
+ errors.push({
354
+ instancePath: `/nativeConfigSnapshot/${key}/path`,
355
+ message: 'must be a safe relative path or a declared native path name'
356
+ });
357
+ }
358
+ }
359
+ }
360
+
336
361
  function validateApiRoutes(manifest, errors) {
337
362
  const api = manifest?.api;
338
363
  if (!api?.routes) return;
@@ -395,7 +420,8 @@ function validateManifest(manifest) {
395
420
  'generic-filesystem': new Set(['resourceSync']),
396
421
  'generic-openai-compatible': new Set(['channels']),
397
422
  'generic-mcp': new Set(['mcp']),
398
- 'generic-prompt': new Set(['prompts'])
423
+ 'generic-prompt': new Set(['prompts']),
424
+ 'generic-native-config': new Set(['nativeConfig'])
399
425
  };
400
426
 
401
427
  for (const [capability, driver] of Object.entries(manifest && manifest.capabilities || {})) {
@@ -424,6 +450,13 @@ function validateManifest(manifest) {
424
450
  }
425
451
  }
426
452
 
453
+ if (manifest?.capabilities?.nativeConfig === 'generic-native-config' && !manifest.nativeConfigSnapshot) {
454
+ errors.push({
455
+ instancePath: '/nativeConfigSnapshot',
456
+ message: 'generic-native-config requires nativeConfigSnapshot declarations'
457
+ });
458
+ }
459
+ validateNativeConfigSnapshot(manifest, errors);
427
460
  validateApiRoutes(manifest, errors);
428
461
  return { valid: errors.length === 0, errors };
429
462
  }
@@ -356,14 +356,6 @@
356
356
  "request": "default",
357
357
  "response": "default"
358
358
  },
359
- {
360
- "method": "POST",
361
- "path": "/channels/speed-test-all",
362
- "capability": "channels",
363
- "operation": "speedTestAll",
364
- "request": "default",
365
- "response": "default"
366
- },
367
359
  {
368
360
  "method": "POST",
369
361
  "path": "/channels/probe-models",
@@ -348,14 +348,6 @@
348
348
  "request": "default",
349
349
  "response": "default"
350
350
  },
351
- {
352
- "method": "POST",
353
- "path": "/channels/speed-test-all",
354
- "capability": "channels",
355
- "operation": "speedTestAll",
356
- "request": "default",
357
- "response": "default"
358
- },
359
351
  {
360
352
  "method": "POST",
361
353
  "path": "/channels/probe-models",
@@ -253,14 +253,6 @@
253
253
  "request": "default",
254
254
  "response": "default"
255
255
  },
256
- {
257
- "method": "POST",
258
- "path": "/channels/speed-test-all",
259
- "capability": "channels",
260
- "operation": "speedTestAll",
261
- "request": "default",
262
- "response": "default"
263
- },
264
256
  {
265
257
  "method": "POST",
266
258
  "path": "/channels/probe-models",
@@ -530,7 +522,7 @@
530
522
  "capabilities": {
531
523
  "projects": "dsh-projects",
532
524
  "sessions": "dsh-sessions",
533
- "nativeConfig": "dsh-native-config",
525
+ "nativeConfig": "generic-native-config",
534
526
  "api": "dsh-api",
535
527
  "channels": "dsh-channels",
536
528
  "proxy": "dsh-proxy",
@@ -538,5 +530,10 @@
538
530
  "nativeLogs": "dsh-native-logs",
539
531
  "mcp": "dsh-api",
540
532
  "prompts": "dsh-api"
533
+ },
534
+ "nativeConfigSnapshot": {
535
+ "settings": { "path": "settings", "format": "text", "mode": 384 },
536
+ "credentials": { "path": "credentials", "format": "text", "mode": 384 },
537
+ "patch": { "path": "patch", "format": "text", "mode": 384 }
541
538
  }
542
539
  }
@@ -347,14 +347,6 @@
347
347
  "request": "default",
348
348
  "response": "default"
349
349
  },
350
- {
351
- "method": "POST",
352
- "path": "/channels/speed-test-all",
353
- "capability": "channels",
354
- "operation": "speedTestAll",
355
- "request": "default",
356
- "response": "default"
357
- },
358
350
  {
359
351
  "method": "POST",
360
352
  "path": "/channels/probe-models",
@@ -365,14 +365,6 @@
365
365
  "request": "default",
366
366
  "response": "default"
367
367
  },
368
- {
369
- "method": "POST",
370
- "path": "/channels/speed-test-all",
371
- "capability": "channels",
372
- "operation": "speedTestAll",
373
- "request": "default",
374
- "response": "default"
375
- },
376
368
  {
377
369
  "method": "POST",
378
370
  "path": "/channels/probe-models",
@@ -491,6 +483,7 @@
491
483
  "api": "legacy:omp",
492
484
  "hooks": "legacy:omp",
493
485
  "resourceSync": "legacy:omp",
494
- "mcp": "legacy:omp"
486
+ "mcp": "legacy:omp",
487
+ "prompts": "legacy:omp"
495
488
  }
496
489
  }
@@ -9,6 +9,10 @@
9
9
  "defaultEnabled": true,
10
10
  "sortOrder": 30,
11
11
  "modelConfig": { "catalogKey": "codex" },
12
+ "auth": {
13
+ "modes": ["api_key", "oauth", "none"],
14
+ "oauth": { "adapter": "opencode", "policy": "single-enabled", "quota": "codex" }
15
+ },
12
16
  "helpLabel": "OpenCode",
13
17
  "terminalColor": "yellow",
14
18
  "cliSelectable": true,
@@ -257,6 +261,30 @@
257
261
  "request": "default",
258
262
  "response": "default"
259
263
  },
264
+ {
265
+ "method": "GET",
266
+ "path": "/channels/auth",
267
+ "capability": "channels",
268
+ "operation": "getAuth",
269
+ "request": "default",
270
+ "response": "default"
271
+ },
272
+ {
273
+ "method": "POST",
274
+ "path": "/channels/auth/sync-local",
275
+ "capability": "channels",
276
+ "operation": "syncLocalAuth",
277
+ "request": "default",
278
+ "response": "default"
279
+ },
280
+ {
281
+ "method": "GET",
282
+ "path": "/channels/:channelId/auth/quota",
283
+ "capability": "channels",
284
+ "operation": "getAuthQuota",
285
+ "request": "default",
286
+ "response": "default"
287
+ },
260
288
  {
261
289
  "method": "POST",
262
290
  "path": "/channels/sync-current",
@@ -321,14 +349,6 @@
321
349
  "request": "default",
322
350
  "response": "default"
323
351
  },
324
- {
325
- "method": "POST",
326
- "path": "/channels/speed-test-all",
327
- "capability": "channels",
328
- "operation": "speedTestAll",
329
- "request": "default",
330
- "response": "default"
331
- },
332
352
  {
333
353
  "method": "POST",
334
354
  "path": "/channels/probe-models",