coding-tool-x 3.7.1 → 3.7.2

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.
@@ -24,7 +24,10 @@ const { startCodexProxyServer } = require('./codex-proxy-server');
24
24
  const { startGeminiProxyServer } = require('./gemini-proxy-server');
25
25
  const { startOpenCodeProxyServer, collectProxyModelList } = require('./opencode-proxy-server');
26
26
  const { startOmpProxyServer } = require('./omp-proxy-server');
27
- const { createRemoteMutationGuard } = require('./services/network-access');
27
+ const {
28
+ createRemoteMutationGuard,
29
+ isRemoteMutationAllowedByEnv
30
+ } = require('./services/network-access');
28
31
  const { createApiRequestLogger } = require('./services/request-logger');
29
32
  const { inspectWebBuildState, ensureWebDistReady } = require('./services/web-build');
30
33
  const { ensureHttpsCredentials } = require('./services/https-cert');
@@ -151,7 +154,7 @@ async function startServer(port, host = '127.0.0.1', options = {}) {
151
154
  const app = express();
152
155
  const useHttps = options.useHttps === true || process.argv.includes('--https');
153
156
  const lanMode = host === '0.0.0.0';
154
- const allowRemoteMutation = process.env.CC_TOOL_ALLOW_REMOTE_WRITE === 'true';
157
+ const allowRemoteMutation = isRemoteMutationAllowedByEnv(process.env);
155
158
 
156
159
  // Middleware
157
160
  app.use(express.json({ limit: '100mb' }));
@@ -175,7 +178,7 @@ async function startServer(port, host = '127.0.0.1', options = {}) {
175
178
  app.use('/api', createRemoteMutationGuard({
176
179
  enabled: true,
177
180
  allowRemoteMutation,
178
- message: '出于安全考虑,LAN 模式默认仅允许本机执行写操作。可设置 CC_TOOL_ALLOW_REMOTE_WRITE=true 覆盖。'
181
+ message: 'LAN 模式下远程写操作已被 CC_TOOL_ALLOW_REMOTE_WRITE=false 禁止。'
179
182
  }));
180
183
 
181
184
  }
@@ -325,8 +328,13 @@ async function startServer(port, host = '127.0.0.1', options = {}) {
325
328
  console.log(chalk.gray(' [TIP] 首次访问自签名证书时,浏览器可能会提示手动信任本地证书'));
326
329
  }
327
330
 
328
- if (host === '0.0.0.0' && !allowRemoteMutation) {
329
- console.log(chalk.yellow(' [LOCK] 已启用 LAN 安全保护:远程写操作默认禁用'));
331
+ if (host === '0.0.0.0') {
332
+ if (allowRemoteMutation) {
333
+ console.log(chalk.yellow(' [WARN] LAN 远程写操作已启用(默认行为)'));
334
+ console.log(chalk.gray(' 如需禁止,请设置 CC_TOOL_ALLOW_REMOTE_WRITE=false'));
335
+ } else {
336
+ console.log(chalk.yellow(' [LOCK] 已启用 LAN 安全保护:远程写操作被 CC_TOOL_ALLOW_REMOTE_WRITE=false 禁止'));
337
+ }
330
338
  }
331
339
  // 自动恢复代理状态
332
340
  autoRestoreProxies();
@@ -95,6 +95,13 @@ function getModelPriority(channelType, options = {}) {
95
95
  console.warn(`[ModelDetector] Failed to load default models config: ${error.message}`);
96
96
  }
97
97
 
98
+ for (const toolType of candidateTypes) {
99
+ const models = MODEL_PRIORITY[toolType];
100
+ if (Array.isArray(models) && models.length > 0) {
101
+ return normalizeModelCandidates(models);
102
+ }
103
+ }
104
+
98
105
  return [];
99
106
  }
100
107
 
@@ -779,6 +786,12 @@ function buildChannelCacheSignature(channel, payload = {}) {
779
786
  return crypto.createHash('sha1').update(raw).digest('hex');
780
787
  }
781
788
 
789
+ function getChannelCacheKey(channel, signature) {
790
+ const id = String(channel?.id || '').trim();
791
+ if (id) return id;
792
+ return `temporary:${signature}`;
793
+ }
794
+
782
795
  function isSignatureCacheValid(cacheEntry, signatureKey, expectedSignature) {
783
796
  if (!cacheEntry || !signatureKey || !expectedSignature) return false;
784
797
  return cacheEntry[signatureKey] === expectedSignature;
@@ -846,7 +859,6 @@ async function probeModelAvailability(channel, channelType, options = {}) {
846
859
  const toolType = options.toolType;
847
860
  const stopOnFirstAvailable = !!options.stopOnFirstAvailable;
848
861
  const cache = loadModelCache();
849
- const cacheKey = channel.id;
850
862
  const preferredModels = normalizeModelCandidates(options.preferredModels);
851
863
  const probeSignature = buildChannelCacheSignature(channel, {
852
864
  type: 'probe',
@@ -855,6 +867,7 @@ async function probeModelAvailability(channel, channelType, options = {}) {
855
867
  stopOnFirstAvailable,
856
868
  preferredModels
857
869
  });
870
+ const cacheKey = getChannelCacheKey(channel, probeSignature);
858
871
 
859
872
  // Return cached result if channel and probe options are unchanged
860
873
  if (!forceRefresh && isSignatureCacheValid(cache[cacheKey], 'probeSignature', probeSignature)) {
@@ -1007,11 +1020,11 @@ async function fetchModelsFromProvider(channel, channelType, options = {}) {
1007
1020
  }
1008
1021
 
1009
1022
  const cache = loadModelCache();
1010
- const cacheKey = channel.id;
1011
1023
  const listSignature = buildChannelCacheSignature(channel, {
1012
1024
  type: 'model-list',
1013
1025
  channelType: String(channelType || '').trim().toLowerCase()
1014
1026
  });
1027
+ const cacheKey = getChannelCacheKey(channel, listSignature);
1015
1028
 
1016
1029
  // Check cache first, and only reuse when channel/list context is unchanged
1017
1030
  if (!forceRefresh
@@ -2,6 +2,7 @@ const fs = require('fs');
2
2
  const path = require('path');
3
3
  const os = require('os');
4
4
  const crypto = require('crypto');
5
+ const { spawnSync } = require('child_process');
5
6
  const toml = require('toml');
6
7
  const tomlStringify = require('@iarna/toml').stringify;
7
8
  const { NATIVE_PATHS, PATHS } = require('../../config/paths');
@@ -13,7 +14,7 @@ const { syncCodexUserEnvironment } = require('./codex-env-manager');
13
14
  const nativeKeychain = require('./native-keychain');
14
15
  const { maskToken, decodeJwtPayload, removeFileIfExists, sha256 } = require('./oauth-utils');
15
16
 
16
- const SUPPORTED_TOOLS = ['claude', 'codex', 'gemini', 'opencode'];
17
+ const SUPPORTED_TOOLS = ['claude', 'codex', 'gemini', 'opencode', 'omp'];
17
18
  const GEMINI_MAIN_ACCOUNT_KEY = 'main-account';
18
19
  const GEMINI_KEYCHAIN_SERVICE = 'gemini-cli-oauth';
19
20
  const CODEX_KEYCHAIN_SERVICE = 'Codex Auth';
@@ -1006,6 +1007,202 @@ function inspectOpenCodeState() {
1006
1007
  };
1007
1008
  }
1008
1009
 
1010
+ function resolveOmpRuntime() {
1011
+ try {
1012
+ const ompConfig = require('./omp-config');
1013
+ const runtime = ompConfig.resolveOmpRuntime(process.env, {});
1014
+ return runtime && runtime.runtime === 'omp' && runtime.installed ? runtime : null;
1015
+ } catch {
1016
+ return null;
1017
+ }
1018
+ }
1019
+
1020
+ function runOmpCommand(args = [], options = {}) {
1021
+ const runtime = options.runtime || resolveOmpRuntime();
1022
+ if (!runtime) {
1023
+ return { ok: false, status: 127, stdout: '', stderr: 'OMP CLI not installed' };
1024
+ }
1025
+
1026
+ const result = spawnSync(runtime.command, args, {
1027
+ encoding: 'utf8',
1028
+ env: {
1029
+ ...process.env,
1030
+ ...(options.env || {})
1031
+ },
1032
+ timeout: options.timeout || 10000
1033
+ });
1034
+ const status = result?.status === undefined || result?.status === null ? 0 : result.status;
1035
+ return {
1036
+ ok: !result?.error && status === 0,
1037
+ status,
1038
+ stdout: String(result?.stdout || ''),
1039
+ stderr: String(result?.stderr || ''),
1040
+ error: result?.error || null,
1041
+ command: runtime.command
1042
+ };
1043
+ }
1044
+
1045
+ function getOmpAuthSnapshot(options = {}) {
1046
+ try {
1047
+ const { getOmpAuthProviderSnapshot } = require('./omp-auth-providers');
1048
+ return getOmpAuthProviderSnapshot({
1049
+ accountCheck: true,
1050
+ includeStatus: false,
1051
+ ...options
1052
+ });
1053
+ } catch {
1054
+ return null;
1055
+ }
1056
+ }
1057
+
1058
+ function readAllOmpNativeOAuth() {
1059
+ const snapshot = getOmpAuthSnapshot({ forceRefresh: true });
1060
+ if (!snapshot?.available || !Array.isArray(snapshot.providers)) {
1061
+ return [];
1062
+ }
1063
+
1064
+ return snapshot.providers
1065
+ .filter(provider => provider?.loggedIn || Number(provider?.accountCount || 0) > 0)
1066
+ .flatMap((provider) => {
1067
+ const accounts = Array.isArray(provider.accounts) && provider.accounts.length > 0
1068
+ ? provider.accounts
1069
+ : [{ index: 1, identity: '' }];
1070
+ return accounts.map((account) => ({
1071
+ providerId: provider.id,
1072
+ accountId: String(account.index || '').trim(),
1073
+ accountEmail: String(account.identity || '').trim(),
1074
+ storage: 'auth-broker',
1075
+ primaryToken: ''
1076
+ }));
1077
+ });
1078
+ }
1079
+
1080
+ function readOmpNativeOAuth() {
1081
+ return readAllOmpNativeOAuth()[0] || null;
1082
+ }
1083
+
1084
+ function clearOmpOAuth() {
1085
+ const credentials = readAllOmpNativeOAuth();
1086
+ const providerIds = [...new Set(credentials.map(item => item.providerId).filter(Boolean))];
1087
+ providerIds.forEach((providerId) => {
1088
+ runOmpCommand(['auth-broker', 'logout', providerId], { timeout: 10000 });
1089
+ });
1090
+ try {
1091
+ require('./omp-auth-providers').clearOmpAuthProviderCache();
1092
+ } catch {
1093
+ // cache invalidation is best-effort
1094
+ }
1095
+ }
1096
+
1097
+ function disableOmpOAuthCredential(credential = {}) {
1098
+ const providerId = String(credential.providerId || '').trim();
1099
+ if (!providerId) {
1100
+ clearOmpOAuth();
1101
+ return;
1102
+ }
1103
+ runOmpCommand(['auth-broker', 'logout', providerId], { timeout: 10000 });
1104
+ try {
1105
+ require('./omp-auth-providers').clearOmpAuthProviderCache();
1106
+ } catch {
1107
+ // cache invalidation is best-effort
1108
+ }
1109
+ }
1110
+
1111
+ function buildOmpImportPayload(credential = {}) {
1112
+ if (credential.importPayload && typeof credential.importPayload === 'object') {
1113
+ return credential.importPayload;
1114
+ }
1115
+
1116
+ const access = String(credential.accessToken || credential.primaryToken || '').trim();
1117
+ if (!access) {
1118
+ return null;
1119
+ }
1120
+
1121
+ return {
1122
+ provider: credential.providerId,
1123
+ credential_type: credential.credentialType || 'oauth',
1124
+ identity_key: credential.identityKey || credential.accountId || credential.accountEmail || undefined,
1125
+ data: {
1126
+ access,
1127
+ refresh: credential.refreshToken || '',
1128
+ expires: credential.expiresAt || null,
1129
+ accountId: credential.accountId || undefined,
1130
+ accountEmail: credential.accountEmail || undefined
1131
+ }
1132
+ };
1133
+ }
1134
+
1135
+ function hasOmpNativeCredential(providerId) {
1136
+ return readAllOmpNativeOAuth().some((entry) => (
1137
+ !providerId || entry.providerId === providerId
1138
+ ));
1139
+ }
1140
+
1141
+ function applyOmpOAuth(credential = {}) {
1142
+ const providerId = String(credential.providerId || '').trim();
1143
+ if (!providerId) {
1144
+ throw new Error('OMP OAuth credential requires providerId');
1145
+ }
1146
+
1147
+ const payload = buildOmpImportPayload(credential);
1148
+ if (!payload) {
1149
+ if (hasOmpNativeCredential(providerId)) {
1150
+ return { storage: 'auth-broker-existing' };
1151
+ }
1152
+ throw new Error('OMP OAuth credential requires an import payload or access token');
1153
+ }
1154
+
1155
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ctx-omp-oauth-'));
1156
+ const tempPath = path.join(tempDir, `${providerId}.json`);
1157
+ try {
1158
+ fs.writeFileSync(tempPath, JSON.stringify(payload, null, 2), 'utf8');
1159
+ ensureFileMode(tempPath);
1160
+ const result = runOmpCommand(['auth-broker', 'import', tempPath, '--provider', providerId], { timeout: 15000 });
1161
+ if (!result.ok) {
1162
+ const detail = result.stderr.trim() || result.stdout.trim() || result.error?.message || `exit code ${result.status}`;
1163
+ throw new Error(`OMP auth-broker import failed: ${detail}`);
1164
+ }
1165
+ try {
1166
+ require('./omp-auth-providers').clearOmpAuthProviderCache();
1167
+ } catch {
1168
+ // cache invalidation is best-effort
1169
+ }
1170
+ return { storage: 'auth-broker' };
1171
+ } finally {
1172
+ try {
1173
+ removeFileIfExists(tempPath);
1174
+ fs.rmSync(tempDir, { recursive: true, force: true });
1175
+ } catch {
1176
+ // ignore cleanup failures
1177
+ }
1178
+ }
1179
+ }
1180
+
1181
+ function inspectOmpState() {
1182
+ const { getOmpProxyStatus } = require('../omp-proxy-server');
1183
+ const { isManagedOmpProvidersActive } = require('./omp-settings-manager');
1184
+ const proxyStatus = getOmpProxyStatus();
1185
+ const nativeCredentials = readAllOmpNativeOAuth();
1186
+
1187
+ let channelConfigured = false;
1188
+ try {
1189
+ channelConfigured = Boolean(isManagedOmpProvidersActive());
1190
+ } catch {
1191
+ channelConfigured = false;
1192
+ }
1193
+
1194
+ return {
1195
+ tool: 'omp',
1196
+ mode: proxyStatus.running || channelConfigured
1197
+ ? 'proxy'
1198
+ : (nativeCredentials.length > 0 ? 'oauth' : 'idle'),
1199
+ proxyRunning: Boolean(proxyStatus.running || channelConfigured),
1200
+ oauthPresent: nativeCredentials.length > 0,
1201
+ channelConfigured,
1202
+ nativeCredential: nativeCredentials[0] ? buildNativeSummary(nativeCredentials[0]) : null
1203
+ };
1204
+ }
1205
+
1009
1206
  function inspectTool(tool) {
1010
1207
  switch (tool) {
1011
1208
  case 'claude':
@@ -1016,6 +1213,8 @@ function inspectTool(tool) {
1016
1213
  return inspectGeminiState();
1017
1214
  case 'opencode':
1018
1215
  return inspectOpenCodeState();
1216
+ case 'omp':
1217
+ return inspectOmpState();
1019
1218
  default:
1020
1219
  throw new Error(`Unsupported OAuth tool: ${tool}`);
1021
1220
  }
@@ -1031,6 +1230,8 @@ function readNativeOAuth(tool) {
1031
1230
  return readGeminiNativeOAuth();
1032
1231
  case 'opencode':
1033
1232
  return readOpenCodeNativeOAuth();
1233
+ case 'omp':
1234
+ return readOmpNativeOAuth();
1034
1235
  default:
1035
1236
  throw new Error(`Unsupported OAuth tool: ${tool}`);
1036
1237
  }
@@ -1052,6 +1253,8 @@ function readAllNativeOAuth(tool) {
1052
1253
  }
1053
1254
  case 'opencode':
1054
1255
  return readAllOpenCodeNativeOAuth();
1256
+ case 'omp':
1257
+ return readAllOmpNativeOAuth();
1055
1258
  default:
1056
1259
  throw new Error(`Unsupported OAuth tool: ${tool}`);
1057
1260
  }
@@ -1071,6 +1274,9 @@ function clearNativeOAuth(tool) {
1071
1274
  case 'opencode':
1072
1275
  clearOpenCodeOAuth();
1073
1276
  return;
1277
+ case 'omp':
1278
+ clearOmpOAuth();
1279
+ return;
1074
1280
  default:
1075
1281
  throw new Error(`Unsupported OAuth tool: ${tool}`);
1076
1282
  }
@@ -1090,6 +1296,9 @@ function disableNativeOAuthCredential(tool, credential = {}) {
1090
1296
  case 'opencode':
1091
1297
  disableOpenCodeOAuthCredential(credential);
1092
1298
  return;
1299
+ case 'omp':
1300
+ disableOmpOAuthCredential(credential);
1301
+ return;
1093
1302
  default:
1094
1303
  throw new Error(`Unsupported OAuth tool: ${tool}`);
1095
1304
  }
@@ -1105,6 +1314,8 @@ function applyOAuthCredential(tool, credential) {
1105
1314
  return applyGeminiOAuth(credential);
1106
1315
  case 'opencode':
1107
1316
  return applyOpenCodeOAuth(credential);
1317
+ case 'omp':
1318
+ return applyOmpOAuth(credential);
1108
1319
  default:
1109
1320
  throw new Error(`Unsupported OAuth tool: ${tool}`);
1110
1321
  }
@@ -72,6 +72,10 @@ function createRemoteMutationGuard(options = {}) {
72
72
  };
73
73
  }
74
74
 
75
+ function isRemoteMutationAllowedByEnv(env = process.env) {
76
+ return String(env.CC_TOOL_ALLOW_REMOTE_WRITE || '').toLowerCase() !== 'false';
77
+ }
78
+
75
79
  function createRemoteRouteGuard(options = {}) {
76
80
  const enabled = options.enabled === true;
77
81
  const allowRemoteAccess = options.allowRemoteAccess === true;
@@ -113,6 +117,7 @@ module.exports = {
113
117
  isLoopbackRequest,
114
118
  isSameOriginRequest,
115
119
  createRemoteMutationGuard,
120
+ isRemoteMutationAllowedByEnv,
116
121
  createRemoteRouteGuard,
117
122
  createSameOriginGuard
118
123
  };
@@ -240,6 +240,70 @@ function parseOpenCodeImport(rawText) {
240
240
  };
241
241
  }
242
242
 
243
+ function pickOmpCredentialEntry(parsed) {
244
+ if (Array.isArray(parsed)) {
245
+ return parsed.find(item => item && typeof item === 'object') || null;
246
+ }
247
+ if (!parsed || typeof parsed !== 'object') {
248
+ return null;
249
+ }
250
+ if (parsed.provider || parsed.providerId || parsed.provider_id || parsed.providerKey || parsed.credential_type || parsed.data) {
251
+ return parsed;
252
+ }
253
+ const entry = Object.entries(parsed).find(([, value]) => value && typeof value === 'object');
254
+ return entry ? { provider: entry[0], ...entry[1] } : parsed;
255
+ }
256
+
257
+ function parseOmpImport(rawText) {
258
+ const parsed = tryParseJson(rawText);
259
+ if (!parsed || typeof parsed !== 'object') {
260
+ throw new Error('OMP OAuth 仅支持 JSON 导入。');
261
+ }
262
+
263
+ const entry = pickOmpCredentialEntry(parsed);
264
+ if (!entry || typeof entry !== 'object') {
265
+ throw new Error('OMP OAuth 导入缺少 provider。');
266
+ }
267
+
268
+ const data = entry.data && typeof entry.data === 'object' ? entry.data : entry;
269
+ const providerId = safeString(
270
+ entry.providerId
271
+ || entry.provider_id
272
+ || entry.providerKey
273
+ || entry.provider
274
+ || data.providerId
275
+ || data.provider
276
+ );
277
+ if (!providerId) {
278
+ throw new Error('OMP OAuth 导入缺少 providerId。');
279
+ }
280
+
281
+ const accessToken = safeString(
282
+ data.accessToken
283
+ || data.access_token
284
+ || data.access
285
+ || data.token
286
+ || data.authToken
287
+ );
288
+ const credentialType = safeString(entry.credentialType || entry.credential_type || data.type || 'oauth') || 'oauth';
289
+ if (credentialType === 'oauth' && !accessToken && !entry.data) {
290
+ throw new Error('OMP OAuth 导入缺少 access/accessToken。');
291
+ }
292
+
293
+ return {
294
+ providerId,
295
+ credentialType,
296
+ accessToken,
297
+ refreshToken: safeString(data.refreshToken || data.refresh_token || data.refresh),
298
+ expiresAt: safeNumber(data.expiresAt || data.expiry_date || data.expiryDate || data.expires),
299
+ accountId: safeString(data.accountId || data.account_id || entry.accountId || entry.account_id),
300
+ accountEmail: safeString(data.accountEmail || data.email || entry.accountEmail || entry.email),
301
+ identityKey: safeString(entry.identityKey || entry.identity_key || data.identityKey || data.identity_key),
302
+ importPayload: parsed,
303
+ primaryToken: accessToken
304
+ };
305
+ }
306
+
243
307
  function parseCredentialInput(tool, rawText) {
244
308
  switch (tool) {
245
309
  case 'claude':
@@ -250,6 +314,8 @@ function parseCredentialInput(tool, rawText) {
250
314
  return parseGeminiImport(rawText);
251
315
  case 'opencode':
252
316
  return parseOpenCodeImport(rawText);
317
+ case 'omp':
318
+ return parseOmpImport(rawText);
253
319
  default:
254
320
  throw new Error(`Unsupported OAuth tool: ${tool}`);
255
321
  }
@@ -261,7 +327,7 @@ function buildCredentialName(tool, metadata, providedName = '') {
261
327
  return explicit;
262
328
  }
263
329
 
264
- if (tool === 'opencode' && safeString(metadata.providerId)) {
330
+ if ((tool === 'opencode' || tool === 'omp') && safeString(metadata.providerId)) {
265
331
  const accountLabel = safeString(metadata.accountId || metadata.accountEmail);
266
332
  return accountLabel
267
333
  ? `${tool} - ${metadata.providerId} - ${accountLabel}`
@@ -386,6 +452,19 @@ function extractSecrets(tool, metadata) {
386
452
  enterpriseUrl: metadata.enterpriseUrl || '',
387
453
  primaryToken: metadata.primaryToken || metadata.accessToken || ''
388
454
  };
455
+ case 'omp':
456
+ return {
457
+ providerId: metadata.providerId || '',
458
+ credentialType: metadata.credentialType || 'oauth',
459
+ accessToken: metadata.accessToken || '',
460
+ refreshToken: metadata.refreshToken || '',
461
+ expiresAt: metadata.expiresAt || null,
462
+ accountId: metadata.accountId || '',
463
+ accountEmail: metadata.accountEmail || '',
464
+ identityKey: metadata.identityKey || '',
465
+ importPayload: metadata.importPayload || null,
466
+ primaryToken: metadata.primaryToken || metadata.accessToken || ''
467
+ };
389
468
  default:
390
469
  return { primaryToken: metadata.primaryToken || metadata.accessToken || '' };
391
470
  }
@@ -395,7 +474,7 @@ function stableFingerprintValue(tool, metadata) {
395
474
  // 优先使用稳定标识符,避免 access token 轮换导致重复记录
396
475
  const stableId = metadata.accountEmail
397
476
  || metadata.accountId
398
- || (tool === 'opencode' ? metadata.providerId : '')
477
+ || ((tool === 'opencode' || tool === 'omp') ? metadata.providerId : '')
399
478
  || metadata.refreshToken
400
479
  || metadata.primaryToken
401
480
  || metadata.accessToken
@@ -559,6 +638,11 @@ function cleanupManagedArtifacts(tool) {
559
638
 
560
639
  if (tool === 'opencode') {
561
640
  opencodeSettingsManager.deleteBackup?.();
641
+ return;
642
+ }
643
+
644
+ if (tool === 'omp') {
645
+ return;
562
646
  }
563
647
  }
564
648
 
@@ -597,6 +681,14 @@ async function stopProxyIfRunning(tool) {
597
681
  }
598
682
  return false;
599
683
  }
684
+ case 'omp': {
685
+ const { stopOmpProxyServer, getOmpProxyStatus } = require('../omp-proxy-server');
686
+ if (getOmpProxyStatus().running) {
687
+ await stopOmpProxyServer();
688
+ return true;
689
+ }
690
+ return false;
691
+ }
600
692
  default:
601
693
  throw new Error(`Unsupported OAuth tool: ${tool}`);
602
694
  }
@@ -625,6 +717,11 @@ function disableAllChannelsForTool(tool) {
625
717
  disableAllChannels();
626
718
  break;
627
719
  }
720
+ case 'omp': {
721
+ const { disableAllChannels } = require('./omp-channels');
722
+ disableAllChannels();
723
+ break;
724
+ }
628
725
  }
629
726
  } catch (err) {
630
727
  console.warn(`[OAuth] Failed to disable channels for ${tool}:`, err.message);
@@ -635,7 +732,7 @@ async function applyStoredCredential(tool, credentialId) {
635
732
  const entry = findStoredCredential(tool, credentialId);
636
733
  const proxyStopped = await stopProxyIfRunning(tool);
637
734
  cleanupManagedArtifacts(tool);
638
- if (tool !== 'opencode') {
735
+ if (tool !== 'opencode' && tool !== 'omp') {
639
736
  disableAllChannelsForTool(tool);
640
737
  }
641
738
  applyOAuthCredential(tool, entry.secrets);
@@ -818,6 +915,16 @@ async function fetchCredentialUsage(tool, credentialId) {
818
915
  return await fetchCodexUsage(accessToken);
819
916
  }
820
917
  }
918
+ case 'omp': {
919
+ const providerId = entry.providerId || secrets.providerId || '';
920
+ if (providerId.includes('claude') || providerId.includes('anthropic')) {
921
+ return await fetchClaudeUsage(accessToken);
922
+ } else if (providerId.includes('gemini') || providerId.includes('google')) {
923
+ return await fetchGeminiUsage(accessToken);
924
+ } else {
925
+ return await fetchCodexUsage(accessToken);
926
+ }
927
+ }
821
928
  default:
822
929
  return { error: `不支持的工具: ${tool}` };
823
930
  }
@@ -21,6 +21,11 @@ class OmpChannelService extends BaseChannelService {
21
21
  const normalized = super._applyDefaults(channel);
22
22
  normalized.providerKey = normalized.providerKey || normalized.provider || normalized.name || normalized.id;
23
23
  normalized.providerApi = normalized.providerApi || normalized.api || normalized.wireApi || 'openai-completions';
24
+ normalized.authMode = normalized.authMode === 'oauth' ? 'oauth' : 'api_key';
25
+ normalized.oauthProviderId = normalized.oauthProviderId || (normalized.authMode === 'oauth' ? normalized.providerKey : '');
26
+ if (normalized.authMode === 'oauth') {
27
+ normalized.apiKey = normalized.apiKey || '';
28
+ }
24
29
  normalized.model = normalized.model || null;
25
30
  normalized.models = Array.isArray(normalized.models) ? normalized.models : [];
26
31
  normalized.allowedModels = Array.isArray(normalized.allowedModels) ? normalized.allowedModels : [];