coding-tool-x 3.7.7 → 3.7.9
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-D14GGF2y.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-DXVnqR5c.js → ConfigTemplates-C0TeUSZg.js} +1 -1
- package/dist/web/assets/{Home-FHJB7hYI.js → Home-CUh_BsGm.js} +1 -1
- package/dist/web/assets/{PluginManager-D8RToURX.js → PluginManager-DPZvPrT4.js} +1 -1
- package/dist/web/assets/{ProjectList-Cdz4jnOg.js → ProjectList-DUaiCvsF.js} +1 -1
- package/dist/web/assets/{SessionList-XYydFlQg.js → SessionList-XdHNM61o.js} +1 -1
- package/dist/web/assets/{SkillManager-Bu9p7mMb.js → SkillManager-I4LFlsVN.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-Bx0Gj3Mk.js → WorkspaceManager-zMI7Lhzf.js} +1 -1
- package/dist/web/assets/index-C5nLTbOk.css +1 -0
- package/dist/web/assets/index-CCP7sRMP.js +2 -0
- package/dist/web/assets/{naive-ui-CVyvdmg1.js → naive-ui-BALVpMgd.js} +1 -1
- package/dist/web/index.html +3 -3
- package/package.json +1 -1
- package/src/config/model-metadata.json +9 -1
- package/src/server/api/omp-channels.js +105 -11
- package/src/server/api/omp-config.js +6 -8
- package/src/server/api/settings.js +93 -9
- package/src/server/services/model-definition-schema.js +570 -0
- package/src/server/services/model-detector.js +106 -26
- package/src/server/services/omp-channels.js +9 -1
- package/src/server/services/omp-config.js +22 -0
- package/src/server/services/omp-settings-manager.js +153 -167
- package/dist/web/assets/index-DxkAvn4g.js +0 -2
- package/dist/web/assets/index-S8wrU2Nf.css +0 -1
|
@@ -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,
|
|
@@ -21,7 +21,7 @@ const {
|
|
|
21
21
|
upsertSyncedChannels
|
|
22
22
|
} = require('./channel-sync-utils');
|
|
23
23
|
|
|
24
|
-
const OMP_THINKING_SUFFIX_RE = /:(minimal|low|medium|high|xhigh)$/;
|
|
24
|
+
const OMP_THINKING_SUFFIX_RE = /:(minimal|low|medium|high|xhigh|off)$/;
|
|
25
25
|
|
|
26
26
|
class OmpChannelService extends BaseChannelService {
|
|
27
27
|
constructor() {
|
|
@@ -44,6 +44,14 @@ class OmpChannelService extends BaseChannelService {
|
|
|
44
44
|
}
|
|
45
45
|
normalized.model = normalized.model || null;
|
|
46
46
|
normalized.models = Array.isArray(normalized.models) ? normalized.models : [];
|
|
47
|
+
normalized.modelMetadataMode = ['auto', 'hybrid', 'manual'].includes(normalized.modelMetadataMode)
|
|
48
|
+
? normalized.modelMetadataMode
|
|
49
|
+
: 'auto';
|
|
50
|
+
normalized.modelBindings = Array.isArray(normalized.modelBindings) ? normalized.modelBindings : [];
|
|
51
|
+
normalized.providerConfig = normalized.providerConfig && typeof normalized.providerConfig === 'object'
|
|
52
|
+
&& !Array.isArray(normalized.providerConfig)
|
|
53
|
+
? normalized.providerConfig
|
|
54
|
+
: {};
|
|
47
55
|
normalized.allowedModels = Array.isArray(normalized.allowedModels) ? normalized.allowedModels : [];
|
|
48
56
|
normalized.speedTestModel = normalized.speedTestModel || null;
|
|
49
57
|
normalized.modelRedirects = Array.isArray(normalized.modelRedirects) ? normalized.modelRedirects : [];
|
|
@@ -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 {
|