coding-tool-x 3.7.7 → 3.7.8
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-B6FzPvdJ.js → Analytics-BE5H9JGG.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-DXVnqR5c.js → ConfigTemplates-z118k1BE.js} +1 -1
- package/dist/web/assets/{Home-FHJB7hYI.js → Home-CBhtmOIN.js} +1 -1
- package/dist/web/assets/{PluginManager-D8RToURX.js → PluginManager-CGhfZ3t8.js} +1 -1
- package/dist/web/assets/{ProjectList-Cdz4jnOg.js → ProjectList-BMPLJtgQ.js} +1 -1
- package/dist/web/assets/{SessionList-XYydFlQg.js → SessionList-D9e7wGF1.js} +1 -1
- package/dist/web/assets/{SkillManager-Bu9p7mMb.js → SkillManager-DJpdUmnk.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-Bx0Gj3Mk.js → WorkspaceManager-C0Ud_V7v.js} +1 -1
- package/dist/web/assets/index-CeTaG4IX.js +2 -0
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
- package/src/server/api/omp-channels.js +29 -9
- package/src/server/api/omp-config.js +6 -8
- package/src/server/services/model-detector.js +106 -26
- package/src/server/services/omp-config.js +22 -0
- package/src/server/services/omp-settings-manager.js +31 -16
- package/dist/web/assets/index-DxkAvn4g.js +0 -2
package/dist/web/index.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
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-CeTaG4IX.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-CVyvdmg1.js">
|
package/package.json
CHANGED
|
@@ -24,8 +24,7 @@ const {
|
|
|
24
24
|
const {
|
|
25
25
|
findAuthProviderForKey,
|
|
26
26
|
getCachedOmpAuthProviderSnapshot,
|
|
27
|
-
getOmpAuthProviderCacheMeta
|
|
28
|
-
warmOmpAuthProviderSnapshot
|
|
27
|
+
getOmpAuthProviderCacheMeta
|
|
29
28
|
} = require('../services/omp-auth-providers');
|
|
30
29
|
|
|
31
30
|
const CHANNEL_LIST_AUTH_OPTIONS = { accountCheck: false, includeStatus: false };
|
|
@@ -77,6 +76,8 @@ async function discoverOmpModels(channel, gatewaySourceType, { forceRefresh = fa
|
|
|
77
76
|
models: preferredModels,
|
|
78
77
|
supported: preferredModels.length > 0,
|
|
79
78
|
cached: false,
|
|
79
|
+
stale: false,
|
|
80
|
+
retryAfter: null,
|
|
80
81
|
fallbackUsed: true,
|
|
81
82
|
fetchedAt: new Date().toISOString(),
|
|
82
83
|
error: preferredModels.length > 0 ? null : 'OAuth 渠道未配置 API Key,无法直接调用远端模型列表接口',
|
|
@@ -91,11 +92,27 @@ async function discoverOmpModels(channel, gatewaySourceType, { forceRefresh = fa
|
|
|
91
92
|
});
|
|
92
93
|
const listedModels = Array.isArray(listResult.models) ? uniqueModels(listResult.models) : [];
|
|
93
94
|
|
|
95
|
+
if (listResult.backoff || listResult.stale) {
|
|
96
|
+
return {
|
|
97
|
+
models: listedModels,
|
|
98
|
+
supported: listedModels.length > 0,
|
|
99
|
+
cached: !!listResult.cached,
|
|
100
|
+
stale: true,
|
|
101
|
+
retryAfter: listResult.retryAfter || null,
|
|
102
|
+
fallbackUsed: false,
|
|
103
|
+
fetchedAt: listResult.lastChecked || new Date().toISOString(),
|
|
104
|
+
error: listResult.error || (listedModels.length > 0 ? null : '模型目录缓存已过期'),
|
|
105
|
+
errorHint: listResult.errorHint || '可点击“刷新可选模型”更新,或手动填写模型名称。'
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
94
109
|
if (listedModels.length > 0) {
|
|
95
110
|
return {
|
|
96
111
|
models: listedModels,
|
|
97
112
|
supported: true,
|
|
98
113
|
cached: !!listResult.cached,
|
|
114
|
+
stale: false,
|
|
115
|
+
retryAfter: null,
|
|
99
116
|
fallbackUsed: false,
|
|
100
117
|
fetchedAt: listResult.lastChecked || new Date().toISOString(),
|
|
101
118
|
error: null,
|
|
@@ -114,6 +131,8 @@ async function discoverOmpModels(channel, gatewaySourceType, { forceRefresh = fa
|
|
|
114
131
|
models: probedModels,
|
|
115
132
|
supported: probedModels.length > 0,
|
|
116
133
|
cached: !!probe.cached || !!listResult.cached,
|
|
134
|
+
stale: false,
|
|
135
|
+
retryAfter: null,
|
|
117
136
|
fallbackUsed: probedModels.length > 0,
|
|
118
137
|
fetchedAt: probe.lastChecked || listResult.lastChecked || new Date().toISOString(),
|
|
119
138
|
error: probedModels.length > 0 ? null : (listResult.error || '无法获取可用模型'),
|
|
@@ -142,17 +161,12 @@ function attachAuthProvider(channel, snapshot) {
|
|
|
142
161
|
|
|
143
162
|
function getAuthSnapshotForChannelList() {
|
|
144
163
|
const snapshot = getCachedOmpAuthProviderSnapshot(CHANNEL_LIST_AUTH_OPTIONS);
|
|
145
|
-
const shouldWarm = !snapshot || snapshot.stale;
|
|
146
|
-
if (shouldWarm) {
|
|
147
|
-
warmOmpAuthProviderSnapshot(CHANNEL_LIST_AUTH_OPTIONS);
|
|
148
|
-
}
|
|
149
164
|
const meta = getOmpAuthProviderCacheMeta(CHANNEL_LIST_AUTH_OPTIONS);
|
|
150
165
|
return {
|
|
151
166
|
snapshot,
|
|
152
167
|
meta: {
|
|
153
168
|
...meta,
|
|
154
169
|
stale: !snapshot || Boolean(snapshot.stale),
|
|
155
|
-
refreshing: shouldWarm || meta.refreshing,
|
|
156
170
|
fallback: !snapshot
|
|
157
171
|
}
|
|
158
172
|
};
|
|
@@ -241,7 +255,8 @@ module.exports = () => {
|
|
|
241
255
|
model,
|
|
242
256
|
speedTestModel,
|
|
243
257
|
allowedModels,
|
|
244
|
-
modelRedirects
|
|
258
|
+
modelRedirects,
|
|
259
|
+
forceRefresh
|
|
245
260
|
} = req.body || {};
|
|
246
261
|
if (!baseUrl) {
|
|
247
262
|
return res.status(400).json({ error: 'baseUrl is required' });
|
|
@@ -259,11 +274,14 @@ module.exports = () => {
|
|
|
259
274
|
modelRedirects: Array.isArray(modelRedirects) ? modelRedirects : []
|
|
260
275
|
};
|
|
261
276
|
const resolvedGatewaySourceType = resolveGatewaySourceType(tempChannel);
|
|
262
|
-
const result = await discoverOmpModels(tempChannel, resolvedGatewaySourceType, { forceRefresh: true });
|
|
277
|
+
const result = await discoverOmpModels(tempChannel, resolvedGatewaySourceType, { forceRefresh: forceRefresh === true });
|
|
263
278
|
res.json({
|
|
264
279
|
models: result.models,
|
|
265
280
|
supported: result.supported,
|
|
266
281
|
fallbackUsed: result.fallbackUsed,
|
|
282
|
+
cached: result.cached,
|
|
283
|
+
stale: result.stale,
|
|
284
|
+
retryAfter: result.retryAfter,
|
|
267
285
|
error: result.error,
|
|
268
286
|
errorHint: result.errorHint
|
|
269
287
|
});
|
|
@@ -291,6 +309,8 @@ module.exports = () => {
|
|
|
291
309
|
models: result.models,
|
|
292
310
|
supported: result.supported,
|
|
293
311
|
cached: result.cached,
|
|
312
|
+
stale: result.stale,
|
|
313
|
+
retryAfter: result.retryAfter,
|
|
294
314
|
fallbackUsed: result.fallbackUsed,
|
|
295
315
|
fetchedAt: result.fetchedAt,
|
|
296
316
|
error: result.error,
|
|
@@ -6,8 +6,7 @@ const {
|
|
|
6
6
|
getOmpPaths,
|
|
7
7
|
getOmpStatus,
|
|
8
8
|
readJsonFile,
|
|
9
|
-
readYamlFile
|
|
10
|
-
readOmpSettings
|
|
9
|
+
readYamlFile
|
|
11
10
|
} = require('../services/omp-config');
|
|
12
11
|
const {
|
|
13
12
|
getOmpAuthProviderSnapshot
|
|
@@ -89,13 +88,12 @@ function buildCapabilities() {
|
|
|
89
88
|
|
|
90
89
|
router.get('/', (req, res) => {
|
|
91
90
|
try {
|
|
92
|
-
const paths = getOmpPaths();
|
|
93
|
-
const settings =
|
|
91
|
+
const paths = getOmpPaths(process.env, { resolveRuntime: false });
|
|
92
|
+
const settings = readYamlFile(paths.settings, readJsonFile(paths.settingsJsonLegacy, {}));
|
|
94
93
|
const resources = {
|
|
95
94
|
config: readYamlFile(paths.config, {}),
|
|
96
95
|
settings: readYamlFile(paths.settings, {}),
|
|
97
96
|
auth: fs.existsSync(paths.auth) ? { exists: true, path: paths.auth } : { exists: false, path: paths.auth },
|
|
98
|
-
authProviders: getOmpAuthProviderSnapshot({ accountCheck: false }),
|
|
99
97
|
models: readYamlFile(paths.modelsYml, {}),
|
|
100
98
|
legacyModels: readJsonFile(paths.modelsJsonLegacy, {}),
|
|
101
99
|
packages: normalizeArray(settings.packages),
|
|
@@ -110,7 +108,7 @@ router.get('/', (req, res) => {
|
|
|
110
108
|
|
|
111
109
|
res.json({
|
|
112
110
|
success: true,
|
|
113
|
-
status: getOmpStatus(),
|
|
111
|
+
status: getOmpStatus(process.env, { resolveRuntime: false }),
|
|
114
112
|
capabilities: buildCapabilities(),
|
|
115
113
|
resources
|
|
116
114
|
});
|
|
@@ -143,8 +141,8 @@ router.get('/auth-providers', (req, res) => {
|
|
|
143
141
|
|
|
144
142
|
router.get('/resources', (req, res) => {
|
|
145
143
|
try {
|
|
146
|
-
const paths = getOmpPaths();
|
|
147
|
-
const settings =
|
|
144
|
+
const paths = getOmpPaths(process.env, { resolveRuntime: false });
|
|
145
|
+
const settings = readYamlFile(paths.settings, readJsonFile(paths.settingsJsonLegacy, {}));
|
|
148
146
|
res.json({
|
|
149
147
|
success: true,
|
|
150
148
|
paths,
|
|
@@ -233,6 +233,9 @@ const MODEL_ALIASES = {
|
|
|
233
233
|
};
|
|
234
234
|
|
|
235
235
|
const TEST_TIMEOUT_MS = 10000; // 10 seconds per model test
|
|
236
|
+
const MODEL_LIST_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
237
|
+
const MODEL_LIST_FAILURE_TTL_MS = 5 * 60 * 1000;
|
|
238
|
+
const MODEL_LIST_AUTH_FAILURE_TTL_MS = 60 * 60 * 1000;
|
|
236
239
|
const CLAUDE_CODE_BETA_HEADER = 'claude-code-20250219,interleaved-thinking-2025-05-14';
|
|
237
240
|
|
|
238
241
|
const MODEL_UNAVAILABLE_HINTS = [
|
|
@@ -797,6 +800,84 @@ function isSignatureCacheValid(cacheEntry, signatureKey, expectedSignature) {
|
|
|
797
800
|
return cacheEntry[signatureKey] === expectedSignature;
|
|
798
801
|
}
|
|
799
802
|
|
|
803
|
+
function getCacheAgeMs(timestamp, now = Date.now()) {
|
|
804
|
+
const checkedAt = Date.parse(timestamp || '');
|
|
805
|
+
return Number.isFinite(checkedAt) ? Math.max(0, now - checkedAt) : Infinity;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
function isCacheFresh(timestamp, ttlMs, now = Date.now()) {
|
|
809
|
+
return getCacheAgeMs(timestamp, now) < ttlMs;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
function getModelListFailureTtl(statusCode) {
|
|
813
|
+
return [401, 403, 404, 429].includes(Number(statusCode))
|
|
814
|
+
? MODEL_LIST_AUTH_FAILURE_TTL_MS
|
|
815
|
+
: MODEL_LIST_FAILURE_TTL_MS;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
function buildCachedModelListResult(entry, listSignature, forceRefresh) {
|
|
819
|
+
if (forceRefresh || !isSignatureCacheValid(entry, 'listSignature', listSignature)) {
|
|
820
|
+
return null;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
const failure = entry.modelListFailure;
|
|
824
|
+
if (failure && Date.parse(failure.retryAfter || '') > Date.now()) {
|
|
825
|
+
return {
|
|
826
|
+
models: Array.isArray(entry.fetchedModels) ? entry.fetchedModels : [],
|
|
827
|
+
supported: true,
|
|
828
|
+
cached: true,
|
|
829
|
+
stale: true,
|
|
830
|
+
backoff: true,
|
|
831
|
+
fallbackUsed: false,
|
|
832
|
+
error: failure.error || '模型列表暂时不可用',
|
|
833
|
+
errorHint: failure.errorHint || '请稍后重试,或手动填写模型名称',
|
|
834
|
+
statusCode: failure.statusCode,
|
|
835
|
+
lastChecked: entry.lastChecked || failure.failedAt || null,
|
|
836
|
+
retryAfter: failure.retryAfter
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
if (!Array.isArray(entry.fetchedModels)) {
|
|
841
|
+
return null;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
const stale = !isCacheFresh(entry.lastChecked, MODEL_LIST_CACHE_TTL_MS);
|
|
845
|
+
const result = {
|
|
846
|
+
models: entry.fetchedModels,
|
|
847
|
+
supported: true,
|
|
848
|
+
cached: true,
|
|
849
|
+
fallbackUsed: false,
|
|
850
|
+
error: null,
|
|
851
|
+
lastChecked: entry.lastChecked || null
|
|
852
|
+
};
|
|
853
|
+
if (stale) {
|
|
854
|
+
result.stale = true;
|
|
855
|
+
result.errorHint = '模型目录缓存已过期;可点击“刷新可选模型”更新。';
|
|
856
|
+
}
|
|
857
|
+
return result;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
function saveModelListFailure(cache, cacheKey, listSignature, result = {}) {
|
|
861
|
+
const now = new Date();
|
|
862
|
+
const ttlMs = getModelListFailureTtl(result.statusCode);
|
|
863
|
+
const previous = cache[cacheKey] || {};
|
|
864
|
+
const failure = {
|
|
865
|
+
error: result.error || '模型列表暂时不可用',
|
|
866
|
+
errorHint: result.errorHint || '请稍后重试,或手动填写模型名称',
|
|
867
|
+
statusCode: result.statusCode || null,
|
|
868
|
+
failedAt: now.toISOString(),
|
|
869
|
+
retryAfter: new Date(now.getTime() + ttlMs).toISOString()
|
|
870
|
+
};
|
|
871
|
+
cache[cacheKey] = {
|
|
872
|
+
...previous,
|
|
873
|
+
fetchedModels: Array.isArray(previous.fetchedModels) ? previous.fetchedModels : [],
|
|
874
|
+
listSignature,
|
|
875
|
+
modelListFailure: failure
|
|
876
|
+
};
|
|
877
|
+
saveModelCache(cache);
|
|
878
|
+
return failure;
|
|
879
|
+
}
|
|
880
|
+
|
|
800
881
|
/**
|
|
801
882
|
* Test if a specific model is available for a channel
|
|
802
883
|
* @param {Object} channel - Channel configuration
|
|
@@ -869,8 +950,10 @@ async function probeModelAvailability(channel, channelType, options = {}) {
|
|
|
869
950
|
});
|
|
870
951
|
const cacheKey = getChannelCacheKey(channel, probeSignature);
|
|
871
952
|
|
|
872
|
-
// Return cached result
|
|
873
|
-
if (!forceRefresh
|
|
953
|
+
// Return cached result only while the matching probe result is still fresh.
|
|
954
|
+
if (!forceRefresh
|
|
955
|
+
&& isSignatureCacheValid(cache[cacheKey], 'probeSignature', probeSignature)
|
|
956
|
+
&& isCacheFresh(cache[cacheKey].lastChecked, MODEL_LIST_CACHE_TTL_MS)) {
|
|
874
957
|
return {
|
|
875
958
|
availableModels: cache[cacheKey].availableModels || [],
|
|
876
959
|
preferredTestModel: cache[cacheKey].preferredTestModel || null,
|
|
@@ -1026,21 +1109,17 @@ async function fetchModelsFromProvider(channel, channelType, options = {}) {
|
|
|
1026
1109
|
});
|
|
1027
1110
|
const cacheKey = getChannelCacheKey(channel, listSignature);
|
|
1028
1111
|
|
|
1029
|
-
//
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
models: cache[cacheKey].fetchedModels || [],
|
|
1035
|
-
supported: true,
|
|
1036
|
-
cached: true,
|
|
1037
|
-
fallbackUsed: false,
|
|
1038
|
-
error: null,
|
|
1039
|
-
lastChecked: cache[cacheKey].lastChecked
|
|
1040
|
-
};
|
|
1041
|
-
}
|
|
1112
|
+
// Reuse a fresh catalog immediately. Expired catalogs are also returned as
|
|
1113
|
+
// stale data so opening the editor never blocks on the provider; the user
|
|
1114
|
+
// can explicitly request a refresh.
|
|
1115
|
+
const cachedResult = buildCachedModelListResult(cache[cacheKey], listSignature, forceRefresh);
|
|
1116
|
+
if (cachedResult) return cachedResult;
|
|
1042
1117
|
|
|
1043
1118
|
return new Promise((resolve) => {
|
|
1119
|
+
const resolveFailure = (result) => {
|
|
1120
|
+
saveModelListFailure(cache, cacheKey, listSignature, result);
|
|
1121
|
+
resolve(result);
|
|
1122
|
+
};
|
|
1044
1123
|
try {
|
|
1045
1124
|
const baseUrl = channel.baseUrl.trim().replace(/\/+$/, '');
|
|
1046
1125
|
const endpoint = capability.modelListEndpoint; // e.g. '/v1/models'
|
|
@@ -1095,7 +1174,8 @@ async function fetchModelsFromProvider(channel, channelType, options = {}) {
|
|
|
1095
1174
|
availableModels: cache[cacheKey]?.availableModels || [],
|
|
1096
1175
|
preferredTestModel: cache[cacheKey]?.preferredTestModel || null,
|
|
1097
1176
|
probeSignature: cache[cacheKey]?.probeSignature || null,
|
|
1098
|
-
listSignature
|
|
1177
|
+
listSignature,
|
|
1178
|
+
modelListFailure: null
|
|
1099
1179
|
};
|
|
1100
1180
|
|
|
1101
1181
|
cache[cacheKey] = cacheEntry;
|
|
@@ -1113,7 +1193,7 @@ async function fetchModelsFromProvider(channel, channelType, options = {}) {
|
|
|
1113
1193
|
});
|
|
1114
1194
|
} catch (parseError) {
|
|
1115
1195
|
console.error(`[ModelDetector] Failed to parse models response: ${parseError.message}`);
|
|
1116
|
-
|
|
1196
|
+
resolveFailure({
|
|
1117
1197
|
models: [],
|
|
1118
1198
|
supported: true,
|
|
1119
1199
|
cached: false,
|
|
@@ -1133,7 +1213,7 @@ async function fetchModelsFromProvider(channel, channelType, options = {}) {
|
|
|
1133
1213
|
errorMessage = 'Cloudflare 防护拦截,无法自动获取模型列表';
|
|
1134
1214
|
errorHint = '该 API 端点受 Cloudflare 保护,请手动填写模型名称';
|
|
1135
1215
|
console.warn(`[ModelDetector] Cloudflare protection detected for ${channel.name}, no fallback models injected`);
|
|
1136
|
-
|
|
1216
|
+
resolveFailure({
|
|
1137
1217
|
models: [],
|
|
1138
1218
|
supported: false,
|
|
1139
1219
|
cached: false,
|
|
@@ -1146,7 +1226,7 @@ async function fetchModelsFromProvider(channel, channelType, options = {}) {
|
|
|
1146
1226
|
errorMessage = 'API 密钥认证失败';
|
|
1147
1227
|
errorHint = '请检查 API 密钥是否正确配置';
|
|
1148
1228
|
console.error(`[ModelDetector] Authentication failed for ${channel.name}: ${res.statusCode} - ${errorMessage}`);
|
|
1149
|
-
|
|
1229
|
+
resolveFailure({
|
|
1150
1230
|
models: [],
|
|
1151
1231
|
supported: true,
|
|
1152
1232
|
cached: false,
|
|
@@ -1159,7 +1239,7 @@ async function fetchModelsFromProvider(channel, channelType, options = {}) {
|
|
|
1159
1239
|
errorMessage = '访问被拒绝';
|
|
1160
1240
|
errorHint = '请检查 API 密钥权限或联系服务提供商';
|
|
1161
1241
|
console.error(`[ModelDetector] Access denied for ${channel.name}: ${res.statusCode} - ${errorMessage}`);
|
|
1162
|
-
|
|
1242
|
+
resolveFailure({
|
|
1163
1243
|
models: [],
|
|
1164
1244
|
supported: true,
|
|
1165
1245
|
cached: false,
|
|
@@ -1171,7 +1251,7 @@ async function fetchModelsFromProvider(channel, channelType, options = {}) {
|
|
|
1171
1251
|
}
|
|
1172
1252
|
} else if (res.statusCode === 404) {
|
|
1173
1253
|
console.warn(`[ModelDetector] Model list endpoint not found for ${channel.name}`);
|
|
1174
|
-
|
|
1254
|
+
resolveFailure({
|
|
1175
1255
|
models: [],
|
|
1176
1256
|
supported: false,
|
|
1177
1257
|
cached: false,
|
|
@@ -1182,7 +1262,7 @@ async function fetchModelsFromProvider(channel, channelType, options = {}) {
|
|
|
1182
1262
|
});
|
|
1183
1263
|
} else if (res.statusCode === 429) {
|
|
1184
1264
|
console.warn(`[ModelDetector] Rate limited for ${channel.name}`);
|
|
1185
|
-
|
|
1265
|
+
resolveFailure({
|
|
1186
1266
|
models: [],
|
|
1187
1267
|
supported: true,
|
|
1188
1268
|
cached: false,
|
|
@@ -1206,7 +1286,7 @@ async function fetchModelsFromProvider(channel, channelType, options = {}) {
|
|
|
1206
1286
|
})
|
|
1207
1287
|
.catch((error) => {
|
|
1208
1288
|
console.error(`[ModelDetector] Failed to read models response: ${error.message}`);
|
|
1209
|
-
|
|
1289
|
+
resolveFailure({
|
|
1210
1290
|
models: [],
|
|
1211
1291
|
supported: true,
|
|
1212
1292
|
cached: false,
|
|
@@ -1218,7 +1298,7 @@ async function fetchModelsFromProvider(channel, channelType, options = {}) {
|
|
|
1218
1298
|
|
|
1219
1299
|
req.on('error', (error) => {
|
|
1220
1300
|
console.error(`[ModelDetector] Network error fetching models from ${channel.name}: ${error.message}`);
|
|
1221
|
-
|
|
1301
|
+
resolveFailure({
|
|
1222
1302
|
models: [],
|
|
1223
1303
|
supported: true,
|
|
1224
1304
|
cached: false,
|
|
@@ -1230,7 +1310,7 @@ async function fetchModelsFromProvider(channel, channelType, options = {}) {
|
|
|
1230
1310
|
req.on('timeout', () => {
|
|
1231
1311
|
req.destroy();
|
|
1232
1312
|
console.error(`[ModelDetector] Timeout fetching models from ${channel.name}`);
|
|
1233
|
-
|
|
1313
|
+
resolveFailure({
|
|
1234
1314
|
models: [],
|
|
1235
1315
|
supported: true,
|
|
1236
1316
|
cached: false,
|
|
@@ -1243,7 +1323,7 @@ async function fetchModelsFromProvider(channel, channelType, options = {}) {
|
|
|
1243
1323
|
|
|
1244
1324
|
} catch (error) {
|
|
1245
1325
|
console.error(`[ModelDetector] Error in fetchModelsFromProvider: ${error.message}`);
|
|
1246
|
-
|
|
1326
|
+
resolveFailure({
|
|
1247
1327
|
models: [],
|
|
1248
1328
|
supported: true,
|
|
1249
1329
|
cached: false,
|
|
@@ -229,6 +229,28 @@ function readTextFile(filePath, fallback = '') {
|
|
|
229
229
|
}
|
|
230
230
|
|
|
231
231
|
function getOmpStatus(env = process.env, options = {}) {
|
|
232
|
+
if (options.resolveRuntime === false) {
|
|
233
|
+
const paths = getOmpPaths(env, options);
|
|
234
|
+
return {
|
|
235
|
+
installed: fs.existsSync(paths.agentDir),
|
|
236
|
+
runtime: 'omp',
|
|
237
|
+
command: getOmpCommand(env),
|
|
238
|
+
commandSource: 'not-probed',
|
|
239
|
+
agentDir: paths.agentDir,
|
|
240
|
+
settingsPath: paths.settings,
|
|
241
|
+
authPath: paths.auth,
|
|
242
|
+
modelsPath: paths.modelsYml,
|
|
243
|
+
modelsYmlPath: paths.modelsYml,
|
|
244
|
+
modelsJsonLegacyPath: paths.modelsJsonLegacy,
|
|
245
|
+
sessionsDir: paths.sessions,
|
|
246
|
+
skillsDir: paths.skills,
|
|
247
|
+
promptsDir: paths.prompts,
|
|
248
|
+
commandsDir: paths.commands,
|
|
249
|
+
extensionsDir: paths.extensions,
|
|
250
|
+
themesDir: paths.themes,
|
|
251
|
+
packagesDir: paths.packages
|
|
252
|
+
};
|
|
253
|
+
}
|
|
232
254
|
const runtime = resolveOmpRuntime(env, options);
|
|
233
255
|
const paths = getOmpPaths(env, { ...options, runtime });
|
|
234
256
|
return {
|
|
@@ -20,8 +20,11 @@ const THINKING_MODES = new Set([
|
|
|
20
20
|
|
|
21
21
|
let lastManagedOmpSyncResult = null;
|
|
22
22
|
|
|
23
|
-
function getOmpPaths(
|
|
24
|
-
|
|
23
|
+
function getOmpPaths(env = process.env, options = {}) {
|
|
24
|
+
// Channel CRUD only needs the native OMP file layout. Do not start OMP just
|
|
25
|
+
// to discover a path: OMP_CONFIG_DIR, OMP_PROFILE and OMP_CODING_AGENT_DIR
|
|
26
|
+
// are sufficient to derive it, and a blocked CLI must not block a save.
|
|
27
|
+
return ompConfig.getOmpPaths(env, { ...options, resolveRuntime: false });
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
function ensureOmpDir(...args) {
|
|
@@ -257,7 +260,8 @@ function readOmpCatalogModels(command, providerId, options = {}) {
|
|
|
257
260
|
...process.env,
|
|
258
261
|
...(options.env || {})
|
|
259
262
|
},
|
|
260
|
-
timeout: options.catalogTimeout || 5000
|
|
263
|
+
timeout: options.catalogTimeout || 5000,
|
|
264
|
+
windowsHide: true
|
|
261
265
|
});
|
|
262
266
|
const status = result?.status === undefined || result?.status === null ? 0 : result.status;
|
|
263
267
|
if (result?.error || status !== 0) return [];
|
|
@@ -270,6 +274,9 @@ function readOmpCatalogModels(command, providerId, options = {}) {
|
|
|
270
274
|
}
|
|
271
275
|
|
|
272
276
|
function buildCatalogIndex(channel = {}, options = {}) {
|
|
277
|
+
if (options.catalogFromCli !== true) {
|
|
278
|
+
return new Map();
|
|
279
|
+
}
|
|
273
280
|
const runtime = options.runtime
|
|
274
281
|
|| (typeof ompConfig.resolveOmpRuntime === 'function'
|
|
275
282
|
? ompConfig.resolveOmpRuntime(options.env || process.env, options.runtimeOptions || {})
|
|
@@ -499,7 +506,8 @@ function runModelsJson(command, env, options = {}) {
|
|
|
499
506
|
...process.env,
|
|
500
507
|
...env
|
|
501
508
|
},
|
|
502
|
-
timeout: options.timeout || 5000
|
|
509
|
+
timeout: options.timeout || 5000,
|
|
510
|
+
windowsHide: true
|
|
503
511
|
});
|
|
504
512
|
if (typeof result === 'string' || Buffer.isBuffer(result)) {
|
|
505
513
|
return { status: 0, stdout: result, stderr: '' };
|
|
@@ -515,7 +523,8 @@ function collectVisibleProviderIds(command, env, options = {}) {
|
|
|
515
523
|
...process.env,
|
|
516
524
|
...env
|
|
517
525
|
},
|
|
518
|
-
timeout: options.visibilityTimeout || options.timeout || 5000
|
|
526
|
+
timeout: options.visibilityTimeout || options.timeout || 5000,
|
|
527
|
+
windowsHide: true
|
|
519
528
|
});
|
|
520
529
|
const status = result?.status === undefined || result?.status === null ? 0 : result.status;
|
|
521
530
|
if (result?.error || status !== 0) return [];
|
|
@@ -658,15 +667,17 @@ function collectManagedVisibility(channels = [], modelsConfig = { providers: {}
|
|
|
658
667
|
}
|
|
659
668
|
});
|
|
660
669
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
.
|
|
669
|
-
|
|
670
|
+
if (options.discoverDisabledProviders === true) {
|
|
671
|
+
const env = options.env || process.env;
|
|
672
|
+
const runtime = options.runtime
|
|
673
|
+
|| (typeof ompConfig.resolveOmpRuntime === 'function'
|
|
674
|
+
? ompConfig.resolveOmpRuntime(env, options.runtimeOptions || {})
|
|
675
|
+
: null);
|
|
676
|
+
if (runtime && runtime.runtime === 'omp' && runtime.installed) {
|
|
677
|
+
collectVisibleProviderIds(runtime.command, env, options)
|
|
678
|
+
.filter(providerId => providerId && !isManagedProviderId(providerId))
|
|
679
|
+
.forEach(providerId => pushUnique(managedDisabledProviders, providerId));
|
|
680
|
+
}
|
|
670
681
|
}
|
|
671
682
|
|
|
672
683
|
return {
|
|
@@ -874,7 +885,9 @@ function writeManagedOmpProviders(channels = [], options = {}) {
|
|
|
874
885
|
const backupPath = createModelsBackupIfNeeded(paths.modelsYml, options);
|
|
875
886
|
writeModelsConfig(config, paths.modelsYml);
|
|
876
887
|
const visibility = syncManagedOmpVisibility(channels, config, options);
|
|
877
|
-
const validation =
|
|
888
|
+
const validation = options.validateWithCli === true
|
|
889
|
+
? validateOmpModelsConfig(options)
|
|
890
|
+
: { skipped: true, reason: 'cli-validation-disabled', warnings: [] };
|
|
878
891
|
const warnings = [
|
|
879
892
|
...(visibility.warnings || []),
|
|
880
893
|
...(validation.warnings || [])
|
|
@@ -916,7 +929,9 @@ function removeManagedOmpProviders(options = {}) {
|
|
|
916
929
|
if (before !== after) {
|
|
917
930
|
backupPath = createModelsBackupIfNeeded(paths.modelsYml, options);
|
|
918
931
|
writeModelsConfig(config, paths.modelsYml);
|
|
919
|
-
validation =
|
|
932
|
+
validation = options.validateWithCli === true
|
|
933
|
+
? validateOmpModelsConfig(options)
|
|
934
|
+
: { skipped: true, reason: 'cli-validation-disabled', warnings: [] };
|
|
920
935
|
}
|
|
921
936
|
recordManagedOmpSyncResult({
|
|
922
937
|
path: paths.modelsYml,
|