coding-tool-x 3.7.9 → 3.7.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/web/assets/{Analytics-D14GGF2y.js → Analytics-Ck_00Brj.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-C0TeUSZg.js → ConfigTemplates-Cpw2vUa8.js} +1 -1
- package/dist/web/assets/Home-Bft3Kztl.js +1 -0
- package/dist/web/assets/Home-CHZJkkjH.css +1 -0
- package/dist/web/assets/{PluginManager-DPZvPrT4.js → PluginManager-2RlvEJCT.js} +1 -1
- package/dist/web/assets/{ProjectList-DUaiCvsF.js → ProjectList-_ecCngpl.js} +1 -1
- package/dist/web/assets/{SessionList-XdHNM61o.js → SessionList-DlWcw3hO.js} +1 -1
- package/dist/web/assets/{SkillManager-I4LFlsVN.js → SkillManager-CnY7IXkX.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-zMI7Lhzf.js → WorkspaceManager-3mqghi09.js} +1 -1
- package/dist/web/assets/{index-C5nLTbOk.css → index-BceXgSSe.css} +1 -1
- package/dist/web/assets/index-k5oP7c-A.js +2 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/src/commands/toggle-proxy.js +7 -8
- package/src/commands/ui.js +11 -1
- package/src/config/default.js +1 -1
- package/src/index.js +41 -15
- package/src/server/api/omp-channels.js +16 -5
- package/src/server/api/omp-config.js +1 -1
- package/src/server/api/omp-proxy.js +13 -42
- package/src/server/index.js +3 -3
- package/src/server/omp-proxy-server.js +129 -10
- package/src/server/services/channel-scheduler.js +59 -9
- package/src/server/services/omp-auth-gateway-client.js +62 -0
- package/src/server/services/omp-channels.js +109 -12
- package/src/server/services/omp-gateway-routing.js +295 -0
- package/src/server/services/omp-gateway.js +919 -0
- package/src/server/services/omp-session-log-observer.js +138 -0
- package/src/server/services/omp-sessions.js +80 -0
- package/src/server/services/omp-settings-manager.js +358 -89
- package/dist/web/assets/Home-CUh_BsGm.js +0 -1
- package/dist/web/assets/Home-CqnBXFxj.css +0 -1
- package/dist/web/assets/index-CCP7sRMP.js +0 -2
package/dist/web/index.html
CHANGED
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
<link rel="icon" href="/favicon.ico">
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
7
|
<title>coding-tool-x · AI 编程工作台</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-k5oP7c-A.js"></script>
|
|
9
9
|
<link rel="modulepreload" crossorigin href="/assets/markdown-BNJfEgCA.js">
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/vue-vendor-B5RqjEjQ.js">
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/naive-ui-BALVpMgd.js">
|
|
12
12
|
<link rel="modulepreload" crossorigin href="/assets/icons-nGdSKQn2.js">
|
|
13
13
|
<link rel="modulepreload" crossorigin href="/assets/vendors-DQA4Hw9v.js">
|
|
14
14
|
<link rel="stylesheet" crossorigin href="/assets/markdown-BfC0goYb.css">
|
|
15
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
15
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BceXgSSe.css">
|
|
16
16
|
</head>
|
|
17
17
|
<body>
|
|
18
18
|
<div id="app"></div>
|
package/package.json
CHANGED
|
@@ -205,9 +205,10 @@ async function handleStartProxy(cliType, services) {
|
|
|
205
205
|
|
|
206
206
|
console.log(chalk.cyan('动态切换功能说明:'));
|
|
207
207
|
if (services.managedProviderConfig) {
|
|
208
|
-
console.log(chalk.gray('•
|
|
209
|
-
console.log(chalk.gray(`• 可以通过 Web UI 或"渠道管理"功能调整启用的 ${toolName}
|
|
210
|
-
console.log(chalk.gray(
|
|
208
|
+
console.log(chalk.gray('• 开启后会启动 OMP 专用本地网关,并把受管 provider 指向该网关'));
|
|
209
|
+
console.log(chalk.gray(`• 可以通过 Web UI 或"渠道管理"功能调整启用的 ${toolName} 路由组`));
|
|
210
|
+
console.log(chalk.gray('• 同路由组内由 coding-tool-x 动态选择渠道,跨模型 fallback 仍由 OMP 处理'));
|
|
211
|
+
console.log(chalk.gray(`• OMP 网关地址: http://127.0.0.1:${defaultPort}\n`));
|
|
211
212
|
} else {
|
|
212
213
|
console.log(chalk.gray('• 开启后会在本地启动一个代理服务'));
|
|
213
214
|
console.log(chalk.gray(`• 可以在不重启 ${toolName} 的情况下动态管理渠道`));
|
|
@@ -217,7 +218,7 @@ async function handleStartProxy(cliType, services) {
|
|
|
217
218
|
|
|
218
219
|
console.log(chalk.yellow('[WARN] 重要提示:'));
|
|
219
220
|
if (services.managedProviderConfig) {
|
|
220
|
-
console.log(chalk.yellow('•
|
|
221
|
+
console.log(chalk.yellow('• 启用后会写入指向本地网关的 OMP models.yml provider 配置'));
|
|
221
222
|
console.log(chalk.yellow('• 关闭会移除受管 provider,不会清理 OMP 原生 auth.json\n'));
|
|
222
223
|
} else {
|
|
223
224
|
console.log(chalk.yellow('• 开启期间请勿关闭 CLI 终端窗口'));
|
|
@@ -253,9 +254,7 @@ async function handleStartProxy(cliType, services) {
|
|
|
253
254
|
|
|
254
255
|
if (services.managedProviderConfig) {
|
|
255
256
|
console.log(chalk.green('[OK] OMP models.yml 受管 provider 已启用'));
|
|
256
|
-
|
|
257
|
-
console.log(chalk.gray(`状态端口: http://127.0.0.1:${proxyResult.port}`));
|
|
258
|
-
}
|
|
257
|
+
console.log(chalk.gray('OMP 新增会话用量日志观察已启用'));
|
|
259
258
|
(proxyResult.warnings || []).forEach((warning) => {
|
|
260
259
|
console.log(chalk.yellow(`[WARN] ${warning}`));
|
|
261
260
|
});
|
|
@@ -325,7 +324,7 @@ async function handleStopProxy(cliType, services) {
|
|
|
325
324
|
if (services.managedProviderConfig) {
|
|
326
325
|
console.log(chalk.gray(`• 受管配置: ${chalk.green('已启用')}`));
|
|
327
326
|
console.log(chalk.gray(`• 配置模式: ${proxyStatus.mode || 'models-yml-provider-config'}`));
|
|
328
|
-
console.log(chalk.gray(`•
|
|
327
|
+
console.log(chalk.gray(`• 会话日志观察: ${proxyStatus.sessionLogObserver?.running ? chalk.green('运行中') : chalk.yellow('未运行')}\n`));
|
|
329
328
|
} else {
|
|
330
329
|
console.log(chalk.gray(`• 代理服务: ${chalk.green('运行中')}`));
|
|
331
330
|
console.log(chalk.gray(`• 代理端口: ${proxyStatus.port}`));
|
package/src/commands/ui.js
CHANGED
|
@@ -87,6 +87,16 @@ async function handleUI() {
|
|
|
87
87
|
// 忽略错误
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
// OMP 动态网关持有 models.yml 事务,退出前必须先恢复原生配置。
|
|
91
|
+
try {
|
|
92
|
+
const { getOmpProxyStatus, stopOmpProxyServer } = require('../server/omp-proxy-server');
|
|
93
|
+
if (getOmpProxyStatus().running) {
|
|
94
|
+
await stopOmpProxyServer({ forceAfterMs: 1500 });
|
|
95
|
+
}
|
|
96
|
+
} catch (err) {
|
|
97
|
+
console.error(chalk.yellow(`[WARN] OMP 动态网关退出清理失败: ${err.message}`));
|
|
98
|
+
}
|
|
99
|
+
|
|
90
100
|
console.log(chalk.green('[OK] Web UI 已停止\n'));
|
|
91
101
|
process.exit(0);
|
|
92
102
|
});
|
|
@@ -99,7 +109,7 @@ async function handleUI() {
|
|
|
99
109
|
|
|
100
110
|
} catch (error) {
|
|
101
111
|
console.error(chalk.red('启动服务器失败:'), error.message);
|
|
102
|
-
|
|
112
|
+
throw error;
|
|
103
113
|
}
|
|
104
114
|
}
|
|
105
115
|
|
package/src/config/default.js
CHANGED
|
@@ -21,7 +21,7 @@ const DEFAULT_CONFIG = {
|
|
|
21
21
|
codexProxy: 20089, // Codex 代理服务端口
|
|
22
22
|
geminiProxy: 20090, // Gemini 代理服务端口
|
|
23
23
|
opencodeProxy: 20091, // OpenCode 代理服务端口
|
|
24
|
-
ompProxy: 20092 // OMP
|
|
24
|
+
ompProxy: 20092 // OMP 专用动态网关端口
|
|
25
25
|
},
|
|
26
26
|
maxLogs: 100,
|
|
27
27
|
statsInterval: 30,
|
package/src/index.js
CHANGED
|
@@ -34,9 +34,7 @@ function getVersion() {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
function finishCli(code = 0) {
|
|
37
|
-
|
|
38
|
-
PluginManager.shutdownPlugins();
|
|
39
|
-
process.exit(code);
|
|
37
|
+
return shutdownProcess(code);
|
|
40
38
|
}
|
|
41
39
|
|
|
42
40
|
// 显示帮助信息
|
|
@@ -128,20 +126,49 @@ function showHelp() {
|
|
|
128
126
|
console.log(chalk.gray(' 问题: https://github.com/ZeaoZhang/coding-tool/issues\n'));
|
|
129
127
|
}
|
|
130
128
|
|
|
129
|
+
let processShutdownPromise = null;
|
|
130
|
+
|
|
131
|
+
async function stopOwnedOmpGatewayBeforeExit() {
|
|
132
|
+
try {
|
|
133
|
+
const {
|
|
134
|
+
getOmpProxyStatus,
|
|
135
|
+
stopOmpProxyServer
|
|
136
|
+
} = require('./server/omp-proxy-server');
|
|
137
|
+
if (getOmpProxyStatus().running) {
|
|
138
|
+
await stopOmpProxyServer({ forceAfterMs: 1500 });
|
|
139
|
+
}
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.error(chalk.yellow(`[WARN] OMP 动态网关退出清理失败: ${error.message}`));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function shutdownProcess(code = 0, error = null) {
|
|
146
|
+
if (processShutdownPromise) return processShutdownPromise;
|
|
147
|
+
processShutdownPromise = (async () => {
|
|
148
|
+
await stopOwnedOmpGatewayBeforeExit();
|
|
149
|
+
eventBus.emitSync('cli:shutdown', {});
|
|
150
|
+
PluginManager.shutdownPlugins();
|
|
151
|
+
if (error) {
|
|
152
|
+
console.error(error);
|
|
153
|
+
}
|
|
154
|
+
process.exit(code);
|
|
155
|
+
})();
|
|
156
|
+
return processShutdownPromise;
|
|
157
|
+
}
|
|
158
|
+
|
|
131
159
|
// 全局错误处理
|
|
132
160
|
process.on('uncaughtException', (err) => {
|
|
133
161
|
// 忽略终端相关的错误(通常在 Ctrl+C 时发生)
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
throw err;
|
|
162
|
+
const terminalError = err.code === 'EIO' || err.code === 'ENOTTY' || err.code === 'EPIPE';
|
|
163
|
+
void shutdownProcess(terminalError ? 0 : 1, terminalError ? null : err);
|
|
138
164
|
});
|
|
139
165
|
|
|
140
|
-
//
|
|
141
|
-
process.on('SIGINT',
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
166
|
+
// 处理进程退出信号,OMP 必须先排空并恢复原生配置。
|
|
167
|
+
process.on('SIGINT', () => {
|
|
168
|
+
void shutdownProcess(0);
|
|
169
|
+
});
|
|
170
|
+
process.on('SIGTERM', () => {
|
|
171
|
+
void shutdownProcess(0);
|
|
145
172
|
});
|
|
146
173
|
|
|
147
174
|
/**
|
|
@@ -416,7 +443,7 @@ async function main() {
|
|
|
416
443
|
eventBus.emitSync('cli:command:after', { command: args[0], args: args.slice(1), result });
|
|
417
444
|
if (!result.success) {
|
|
418
445
|
console.error(chalk.red(result.error));
|
|
419
|
-
|
|
446
|
+
return shutdownProcess(1);
|
|
420
447
|
}
|
|
421
448
|
return;
|
|
422
449
|
}
|
|
@@ -712,6 +739,5 @@ async function main() {
|
|
|
712
739
|
|
|
713
740
|
// 启动应用
|
|
714
741
|
main().catch((error) => {
|
|
715
|
-
|
|
716
|
-
process.exit(1);
|
|
742
|
+
void shutdownProcess(1, error);
|
|
717
743
|
});
|
|
@@ -294,7 +294,7 @@ module.exports = () => {
|
|
|
294
294
|
baseUrl,
|
|
295
295
|
apiKey: apiKey || '',
|
|
296
296
|
gatewaySourceType: gatewaySourceType || 'openai_compatible',
|
|
297
|
-
authMode:
|
|
297
|
+
authMode: ['api_key', 'oauth', 'none'].includes(authMode) ? authMode : 'api_key',
|
|
298
298
|
oauthProviderId: oauthProviderId || '',
|
|
299
299
|
model: model || null,
|
|
300
300
|
speedTestModel: speedTestModel || null,
|
|
@@ -356,9 +356,18 @@ module.exports = () => {
|
|
|
356
356
|
if (!providerKey) {
|
|
357
357
|
return res.status(400).json({ error: 'providerKey is required' });
|
|
358
358
|
}
|
|
359
|
+
const requestedModelIds = uniqueModels([
|
|
360
|
+
req.body?.model,
|
|
361
|
+
req.body?.speedTestModel,
|
|
362
|
+
...(Array.isArray(req.body?.allowedModels) ? req.body.allowedModels : []),
|
|
363
|
+
...(Array.isArray(req.body?.models)
|
|
364
|
+
? req.body.models.map(model => typeof model === 'string' ? model : (model?.id || model?.name))
|
|
365
|
+
: [])
|
|
366
|
+
]);
|
|
359
367
|
const rawModels = getOmpCatalogModels(providerKey, {
|
|
360
368
|
forceCatalogRefresh: req.body?.forceRefresh === true,
|
|
361
|
-
catalogTimeout: 5000
|
|
369
|
+
catalogTimeout: 5000,
|
|
370
|
+
requestedModelIds
|
|
362
371
|
});
|
|
363
372
|
const normalizedCatalog = normalizeCatalogModelList(rawModels);
|
|
364
373
|
const models = normalizedCatalog.models.map(model => redactSensitiveFields(model));
|
|
@@ -398,6 +407,7 @@ module.exports = () => {
|
|
|
398
407
|
providerApi,
|
|
399
408
|
authMode,
|
|
400
409
|
oauthProviderId,
|
|
410
|
+
routingGroup,
|
|
401
411
|
presetId,
|
|
402
412
|
websiteUrl,
|
|
403
413
|
balanceToken,
|
|
@@ -416,8 +426,8 @@ module.exports = () => {
|
|
|
416
426
|
if (!name || !baseUrl) {
|
|
417
427
|
return res.status(400).json({ error: 'Missing required fields: name and baseUrl' });
|
|
418
428
|
}
|
|
419
|
-
const normalizedAuthMode =
|
|
420
|
-
if (!apiKey && normalizedAuthMode
|
|
429
|
+
const normalizedAuthMode = ['api_key', 'oauth', 'none'].includes(authMode) ? authMode : 'api_key';
|
|
430
|
+
if (!apiKey && normalizedAuthMode === 'api_key') {
|
|
421
431
|
return res.status(400).json({ error: 'API Key is required' });
|
|
422
432
|
}
|
|
423
433
|
|
|
@@ -426,7 +436,8 @@ module.exports = () => {
|
|
|
426
436
|
providerApi: providerApi || wireApi || 'openai-completions',
|
|
427
437
|
providerKey,
|
|
428
438
|
authMode: normalizedAuthMode,
|
|
429
|
-
oauthProviderId: oauthProviderId || providerKey || '',
|
|
439
|
+
oauthProviderId: normalizedAuthMode === 'oauth' ? (oauthProviderId || providerKey || '') : '',
|
|
440
|
+
routingGroup: String(routingGroup || '').trim(),
|
|
430
441
|
enabled,
|
|
431
442
|
weight,
|
|
432
443
|
maxConcurrency,
|
|
@@ -81,7 +81,7 @@ function buildCapabilities() {
|
|
|
81
81
|
},
|
|
82
82
|
notes: [
|
|
83
83
|
'The omp channel is backed by OMP and uses omp as its platform key.',
|
|
84
|
-
'
|
|
84
|
+
'Dynamic mode exposes managed ctx-* providers through the loopback OMP HTTP gateway; disabling it restores native OMP configuration.'
|
|
85
85
|
]
|
|
86
86
|
};
|
|
87
87
|
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
const express = require('express');
|
|
2
|
-
const fs = require('fs');
|
|
3
|
-
const path = require('path');
|
|
4
2
|
const router = express.Router();
|
|
5
3
|
const {
|
|
6
4
|
startOmpProxyServer,
|
|
@@ -10,10 +8,10 @@ const {
|
|
|
10
8
|
const {
|
|
11
9
|
getChannels,
|
|
12
10
|
getEnabledChannels,
|
|
13
|
-
markChannelAsRecentlyUsed
|
|
11
|
+
markChannelAsRecentlyUsed,
|
|
12
|
+
loadManagedOmpActiveChannelId
|
|
14
13
|
} = require('../services/omp-channels');
|
|
15
14
|
const { getSchedulerState } = require('../services/channel-scheduler');
|
|
16
|
-
const { PATHS, ensureStorageDirMigrated } = require('../../config/paths');
|
|
17
15
|
|
|
18
16
|
function sanitizeChannel(channel) {
|
|
19
17
|
if (!channel) return null;
|
|
@@ -21,7 +19,10 @@ function sanitizeChannel(channel) {
|
|
|
21
19
|
id: channel.id,
|
|
22
20
|
name: channel.name,
|
|
23
21
|
baseUrl: channel.baseUrl,
|
|
24
|
-
websiteUrl: channel.websiteUrl
|
|
22
|
+
websiteUrl: channel.websiteUrl,
|
|
23
|
+
providerKey: channel.providerKey,
|
|
24
|
+
providerApi: channel.providerApi,
|
|
25
|
+
routingGroup: channel.routingGroup || ''
|
|
25
26
|
};
|
|
26
27
|
}
|
|
27
28
|
|
|
@@ -35,34 +36,6 @@ function selectLatestEnabledChannel(channels) {
|
|
|
35
36
|
}, enabledChannels[0]);
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
function saveActiveChannelId(channelId) {
|
|
39
|
-
ensureStorageDirMigrated();
|
|
40
|
-
const filePath = PATHS.activeChannel.omp;
|
|
41
|
-
const dir = path.dirname(filePath);
|
|
42
|
-
if (!fs.existsSync(dir)) {
|
|
43
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
44
|
-
}
|
|
45
|
-
fs.writeFileSync(filePath, JSON.stringify({ activeChannelId: channelId }, null, 2), 'utf8');
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function loadActiveChannelId() {
|
|
49
|
-
ensureStorageDirMigrated();
|
|
50
|
-
try {
|
|
51
|
-
if (!fs.existsSync(PATHS.activeChannel.omp)) return null;
|
|
52
|
-
const data = JSON.parse(fs.readFileSync(PATHS.activeChannel.omp, 'utf8'));
|
|
53
|
-
return data.activeChannelId || null;
|
|
54
|
-
} catch {
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function removeActiveChannelFile() {
|
|
60
|
-
ensureStorageDirMigrated();
|
|
61
|
-
if (fs.existsSync(PATHS.activeChannel.omp)) {
|
|
62
|
-
fs.unlinkSync(PATHS.activeChannel.omp);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
39
|
function resolveActiveChannel(channels, activeChannelId = null) {
|
|
67
40
|
if (!Array.isArray(channels) || channels.length === 0) return null;
|
|
68
41
|
if (activeChannelId) {
|
|
@@ -80,12 +53,12 @@ router.get('/status', (req, res) => {
|
|
|
80
53
|
const proxy = getOmpProxyStatus();
|
|
81
54
|
const { channels } = getChannels();
|
|
82
55
|
const enabledChannels = channels.filter(ch => ch.enabled !== false);
|
|
83
|
-
const activeChannel = resolveActiveChannel(channels,
|
|
56
|
+
const activeChannel = resolveActiveChannel(channels, loadManagedOmpActiveChannelId());
|
|
84
57
|
res.json({
|
|
85
58
|
proxy,
|
|
86
59
|
config: {
|
|
87
|
-
mode: '
|
|
88
|
-
nativeProxyProtocol:
|
|
60
|
+
mode: 'http-gateway',
|
|
61
|
+
nativeProxyProtocol: true
|
|
89
62
|
},
|
|
90
63
|
activeChannel: sanitizeChannel(activeChannel),
|
|
91
64
|
enabledChannelsCount: enabledChannels.length,
|
|
@@ -107,11 +80,10 @@ router.post('/start', async (req, res) => {
|
|
|
107
80
|
|
|
108
81
|
let currentChannel = selectLatestEnabledChannel(enabledChannels) || enabledChannels[0];
|
|
109
82
|
currentChannel = markChannelAsRecentlyUsed(currentChannel.id);
|
|
110
|
-
saveActiveChannelId(currentChannel.id);
|
|
111
83
|
|
|
112
|
-
const proxyResult = await startOmpProxyServer();
|
|
84
|
+
const proxyResult = await startOmpProxyServer({ activeChannelId: currentChannel.id });
|
|
113
85
|
if (!proxyResult.success) {
|
|
114
|
-
return res.status(500).json({ error: 'Failed to
|
|
86
|
+
return res.status(500).json({ error: 'Failed to start OMP gateway' });
|
|
115
87
|
}
|
|
116
88
|
|
|
117
89
|
const { broadcastProxyState, broadcastSchedulerState } = require('../websocket-server');
|
|
@@ -125,10 +97,10 @@ router.post('/start', async (req, res) => {
|
|
|
125
97
|
success: true,
|
|
126
98
|
port: proxyResult.port,
|
|
127
99
|
activeChannel: sanitizeChannel(activeChannel),
|
|
128
|
-
mode: '
|
|
100
|
+
mode: 'http-gateway',
|
|
129
101
|
sync: proxyResult.sync || null,
|
|
130
102
|
warnings: proxyResult.warnings || [],
|
|
131
|
-
message: `OMP
|
|
103
|
+
message: `OMP gateway and managed providers enabled, active channel: ${activeChannel.name}`
|
|
132
104
|
});
|
|
133
105
|
} catch (error) {
|
|
134
106
|
console.error('[OMP Proxy] Start failed:', error);
|
|
@@ -139,7 +111,6 @@ router.post('/start', async (req, res) => {
|
|
|
139
111
|
router.post('/stop', async (req, res) => {
|
|
140
112
|
try {
|
|
141
113
|
const result = await stopOmpProxyServer();
|
|
142
|
-
removeActiveChannelFile();
|
|
143
114
|
const { broadcastProxyState, broadcastSchedulerState } = require('../websocket-server');
|
|
144
115
|
const { channels } = getChannels();
|
|
145
116
|
broadcastProxyState('omp', getOmpProxyStatus(), null, channels);
|
package/src/server/index.js
CHANGED
|
@@ -465,13 +465,13 @@ function autoRestoreProxies() {
|
|
|
465
465
|
startOmpProxyServer({ preserveStartTime: true })
|
|
466
466
|
.then((result) => {
|
|
467
467
|
if (result.success) {
|
|
468
|
-
console.log(chalk.green(`[OK] OMP
|
|
468
|
+
console.log(chalk.green(`[OK] OMP 动态网关已自动恢复: http://127.0.0.1:${result.port}`));
|
|
469
469
|
} else {
|
|
470
|
-
console.error(chalk.red(`[ERROR] OMP
|
|
470
|
+
console.error(chalk.red(`[ERROR] OMP 动态网关恢复失败: ${result.error || 'Unknown error'}`));
|
|
471
471
|
}
|
|
472
472
|
})
|
|
473
473
|
.catch((err) => {
|
|
474
|
-
console.error(chalk.red(`[ERROR] OMP
|
|
474
|
+
console.error(chalk.red(`[ERROR] OMP 动态网关恢复失败: ${err.message}`));
|
|
475
475
|
});
|
|
476
476
|
}
|
|
477
477
|
}
|
|
@@ -1,17 +1,96 @@
|
|
|
1
|
+
const crypto = require('crypto');
|
|
1
2
|
const { loadConfig } = require('../config/loader');
|
|
2
3
|
const { saveProxyStartTime, clearProxyStartTime, getProxyStartTime, getProxyRuntime } = require('./services/proxy-runtime');
|
|
3
|
-
const {
|
|
4
|
+
const {
|
|
5
|
+
syncManagedOmpProviders,
|
|
6
|
+
disableManagedOmpProviders,
|
|
7
|
+
getEnabledChannels,
|
|
8
|
+
isManagedOmpModeEnabled,
|
|
9
|
+
enableManagedOmpMode,
|
|
10
|
+
disableManagedOmpMode,
|
|
11
|
+
loadManagedOmpActiveChannelId,
|
|
12
|
+
loadManagedOmpModeState
|
|
13
|
+
} = require('./services/omp-channels');
|
|
14
|
+
const {
|
|
15
|
+
startOmpSessionLogObserver,
|
|
16
|
+
stopOmpSessionLogObserver,
|
|
17
|
+
getOmpSessionLogObserverStatus
|
|
18
|
+
} = require('./services/omp-session-log-observer');
|
|
19
|
+
const { createOmpGateway } = require('./services/omp-gateway');
|
|
20
|
+
const { prepareManagedOmpChannels } = require('./services/omp-gateway-routing');
|
|
21
|
+
const { probeOmpAuthGateways } = require('./services/omp-auth-gateway-client');
|
|
4
22
|
|
|
5
|
-
let running = false;
|
|
6
23
|
let currentPort = null;
|
|
7
24
|
let lastSyncResult = null;
|
|
25
|
+
let lastRoutingResult = { routes: [], unsupportedChannels: [] };
|
|
26
|
+
let lifecycle = Promise.resolve();
|
|
8
27
|
|
|
9
|
-
|
|
28
|
+
const gateway = createOmpGateway({
|
|
29
|
+
getChannels: () => getEnabledChannels()
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
function withLifecycleLock(operation) {
|
|
33
|
+
const next = lifecycle.then(operation, operation);
|
|
34
|
+
lifecycle = next.catch(() => {});
|
|
35
|
+
return next;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function startOmpProxyServerUnlocked(options = {}) {
|
|
39
|
+
if (gateway.status().listening) {
|
|
40
|
+
return {
|
|
41
|
+
success: true,
|
|
42
|
+
port: gateway.status().port,
|
|
43
|
+
sync: lastSyncResult,
|
|
44
|
+
warnings: lastSyncResult?.warnings || []
|
|
45
|
+
};
|
|
46
|
+
}
|
|
10
47
|
const preserveStartTime = options.preserveStartTime || false;
|
|
11
48
|
const config = loadConfig();
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
49
|
+
const wasEnabled = isManagedOmpModeEnabled();
|
|
50
|
+
const previousState = loadManagedOmpModeState?.() || {
|
|
51
|
+
activeChannelId: loadManagedOmpActiveChannelId()
|
|
52
|
+
};
|
|
53
|
+
const channels = getEnabledChannels();
|
|
54
|
+
const secret = crypto.randomBytes(32).toString('hex');
|
|
55
|
+
const desiredPort = options.port ?? config.ports?.ompProxy ?? 20092;
|
|
56
|
+
const gatewayStatus = await gateway.start({
|
|
57
|
+
host: '127.0.0.1',
|
|
58
|
+
port: desiredPort,
|
|
59
|
+
secret
|
|
60
|
+
});
|
|
61
|
+
currentPort = gatewayStatus.port;
|
|
62
|
+
try {
|
|
63
|
+
const oauthSupport = await probeOmpAuthGateways(channels);
|
|
64
|
+
const descriptor = {
|
|
65
|
+
...gateway.descriptor(),
|
|
66
|
+
supportedOAuthChannelIds: oauthSupport.supportedOAuthChannelIds
|
|
67
|
+
};
|
|
68
|
+
lastRoutingResult = prepareManagedOmpChannels(channels, descriptor);
|
|
69
|
+
enableManagedOmpMode(options.activeChannelId, descriptor);
|
|
70
|
+
lastSyncResult = syncManagedOmpProviders(channels, {
|
|
71
|
+
gateway: descriptor,
|
|
72
|
+
activeChannelId: options.activeChannelId
|
|
73
|
+
});
|
|
74
|
+
if (oauthSupport.warnings.length > 0) {
|
|
75
|
+
lastSyncResult = {
|
|
76
|
+
...lastSyncResult,
|
|
77
|
+
warnings: [
|
|
78
|
+
...(lastSyncResult?.warnings || []),
|
|
79
|
+
...oauthSupport.warnings
|
|
80
|
+
]
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
} catch (error) {
|
|
84
|
+
if (wasEnabled) {
|
|
85
|
+
enableManagedOmpMode(previousState.activeChannelId, previousState.gateway || null);
|
|
86
|
+
} else {
|
|
87
|
+
disableManagedOmpMode();
|
|
88
|
+
}
|
|
89
|
+
await gateway.stop().catch(() => {});
|
|
90
|
+
currentPort = null;
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
startOmpSessionLogObserver();
|
|
15
94
|
saveProxyStartTime('omp', preserveStartTime);
|
|
16
95
|
return {
|
|
17
96
|
success: true,
|
|
@@ -21,12 +100,34 @@ async function startOmpProxyServer(options = {}) {
|
|
|
21
100
|
};
|
|
22
101
|
}
|
|
23
102
|
|
|
24
|
-
|
|
103
|
+
function startOmpProxyServer(options = {}) {
|
|
104
|
+
return withLifecycleLock(() => startOmpProxyServerUnlocked(options));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function stopOmpProxyServerUnlocked(options = {}) {
|
|
25
108
|
const clearStartTime = options.clearStartTime !== false;
|
|
26
109
|
const stoppedPort = currentPort;
|
|
27
|
-
|
|
28
|
-
|
|
110
|
+
if (!gateway.status().listening && !isManagedOmpModeEnabled()) {
|
|
111
|
+
return {
|
|
112
|
+
success: true,
|
|
113
|
+
port: stoppedPort,
|
|
114
|
+
sync: lastSyncResult,
|
|
115
|
+
warnings: lastSyncResult?.warnings || []
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
gateway.beginDraining();
|
|
119
|
+
stopOmpSessionLogObserver();
|
|
120
|
+
try {
|
|
121
|
+
lastSyncResult = disableManagedOmpProviders();
|
|
122
|
+
} catch (error) {
|
|
123
|
+
startOmpSessionLogObserver();
|
|
124
|
+
gateway.cancelDraining();
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
disableManagedOmpMode();
|
|
128
|
+
await gateway.stop({ forceAfterMs: options.forceAfterMs });
|
|
29
129
|
currentPort = null;
|
|
130
|
+
lastRoutingResult = { routes: [], unsupportedChannels: [] };
|
|
30
131
|
if (clearStartTime) {
|
|
31
132
|
clearProxyStartTime('omp');
|
|
32
133
|
}
|
|
@@ -38,8 +139,14 @@ async function stopOmpProxyServer(options = {}) {
|
|
|
38
139
|
};
|
|
39
140
|
}
|
|
40
141
|
|
|
142
|
+
function stopOmpProxyServer(options = {}) {
|
|
143
|
+
return withLifecycleLock(() => stopOmpProxyServerUnlocked(options));
|
|
144
|
+
}
|
|
145
|
+
|
|
41
146
|
function getOmpProxyStatus() {
|
|
42
147
|
const config = loadConfig();
|
|
148
|
+
const gatewayStatus = gateway.status();
|
|
149
|
+
const running = gatewayStatus.listening;
|
|
43
150
|
const startTime = getProxyStartTime('omp', { allowRecovery: running });
|
|
44
151
|
const runtime = getProxyRuntime('omp', { allowRecovery: running });
|
|
45
152
|
const enabledCount = getEnabledChannels().length;
|
|
@@ -50,11 +157,23 @@ function getOmpProxyStatus() {
|
|
|
50
157
|
defaultPort: config.ports?.ompProxy || 20092,
|
|
51
158
|
startTime,
|
|
52
159
|
runtime,
|
|
53
|
-
mode: '
|
|
160
|
+
mode: 'http-gateway',
|
|
161
|
+
listening: gatewayStatus.listening,
|
|
162
|
+
draining: gatewayStatus.draining,
|
|
163
|
+
inflightRequests: gatewayStatus.inflightRequests,
|
|
164
|
+
routingGroups: lastRoutingResult.routes.map(route => ({
|
|
165
|
+
providerKey: route.providerKey,
|
|
166
|
+
providerApi: route.providerApi,
|
|
167
|
+
routingGroup: route.routingGroup,
|
|
168
|
+
managedProviderId: route.managedProviderId,
|
|
169
|
+
channelsCount: route.channelIds.length
|
|
170
|
+
})),
|
|
171
|
+
unsupportedChannels: lastRoutingResult.unsupportedChannels,
|
|
54
172
|
enabledChannelsCount: enabledCount,
|
|
55
173
|
modelsPath: lastSyncResult?.modelsPath || lastSyncResult?.path || null,
|
|
56
174
|
backupPath: lastSyncResult?.backupPath || null,
|
|
57
175
|
modelsValidation: validation,
|
|
176
|
+
sessionLogObserver: getOmpSessionLogObserverStatus(),
|
|
58
177
|
warnings: lastSyncResult?.warnings || []
|
|
59
178
|
};
|
|
60
179
|
}
|