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.
@@ -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-DxkAvn4g.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-CCP7sRMP.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
- <link rel="modulepreload" crossorigin href="/assets/naive-ui-CVyvdmg1.js">
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-S8wrU2Nf.css">
15
+ <link rel="stylesheet" crossorigin href="/assets/index-C5nLTbOk.css">
16
16
  </head>
17
17
  <body>
18
18
  <div id="app"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coding-tool-x",
3
- "version": "3.7.7",
3
+ "version": "3.7.9",
4
4
  "description": "Vibe Coding 增强工作助手 - 智能会话管理、动态渠道切换、全局搜索、实时监控",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -91,7 +91,15 @@
91
91
  },
92
92
  "gpt-5.5": {
93
93
  "limit": { "context": 1050000, "output": 128000 },
94
- "pricing": { "input": 5, "output": 30, "cacheRead": 0.5 }
94
+ "pricing": { "input": 5, "output": 30, "cacheRead": 0.5 },
95
+ "reasoning": true,
96
+ "thinking": {
97
+ "mode": "effort",
98
+ "efforts": ["low", "medium", "high", "xhigh"],
99
+ "defaultLevel": "medium"
100
+ },
101
+ "input": ["text", "image"],
102
+ "supportsTools": true
95
103
  },
96
104
  "gpt-5.4": {
97
105
  "limit": { "context": 1050000, "output": 128000 },
@@ -24,12 +24,39 @@ const {
24
24
  const {
25
25
  findAuthProviderForKey,
26
26
  getCachedOmpAuthProviderSnapshot,
27
- getOmpAuthProviderCacheMeta,
28
- warmOmpAuthProviderSnapshot
27
+ getOmpAuthProviderCacheMeta
29
28
  } = require('../services/omp-auth-providers');
29
+ const {
30
+ MODEL_SCHEMA_VERSION,
31
+ MODEL_METADATA_MODES,
32
+ getPublicModelFieldSchema,
33
+ normalizeCatalogModelList,
34
+ redactSensitiveFields,
35
+ validateModelDefinitions,
36
+ validateProviderConfig
37
+ } = require('../services/model-definition-schema');
38
+ const { getOmpCatalogModels } = require('../services/omp-settings-manager');
30
39
 
31
40
  const CHANNEL_LIST_AUTH_OPTIONS = { accountCheck: false, includeStatus: false };
32
41
 
42
+ function validateOmpModelPayload(payload = {}) {
43
+ if (payload.modelMetadataMode !== undefined && !MODEL_METADATA_MODES.includes(payload.modelMetadataMode)) {
44
+ return `modelMetadataMode must be one of: ${MODEL_METADATA_MODES.join(', ')}`;
45
+ }
46
+ if (payload.models !== undefined) {
47
+ const validation = validateModelDefinitions(payload.models);
48
+ if (!validation.valid) return validation.error;
49
+ }
50
+ if (payload.modelBindings !== undefined && !Array.isArray(payload.modelBindings)) {
51
+ return 'modelBindings must be an array';
52
+ }
53
+ if (payload.providerConfig !== undefined) {
54
+ const validation = validateProviderConfig(payload.providerConfig);
55
+ if (!validation.valid) return validation.error;
56
+ }
57
+ return null;
58
+ }
59
+
33
60
  function uniqueModels(models = []) {
34
61
  const seen = new Set();
35
62
  const result = [];
@@ -77,6 +104,8 @@ async function discoverOmpModels(channel, gatewaySourceType, { forceRefresh = fa
77
104
  models: preferredModels,
78
105
  supported: preferredModels.length > 0,
79
106
  cached: false,
107
+ stale: false,
108
+ retryAfter: null,
80
109
  fallbackUsed: true,
81
110
  fetchedAt: new Date().toISOString(),
82
111
  error: preferredModels.length > 0 ? null : 'OAuth 渠道未配置 API Key,无法直接调用远端模型列表接口',
@@ -91,11 +120,27 @@ async function discoverOmpModels(channel, gatewaySourceType, { forceRefresh = fa
91
120
  });
92
121
  const listedModels = Array.isArray(listResult.models) ? uniqueModels(listResult.models) : [];
93
122
 
123
+ if (listResult.backoff || listResult.stale) {
124
+ return {
125
+ models: listedModels,
126
+ supported: listedModels.length > 0,
127
+ cached: !!listResult.cached,
128
+ stale: true,
129
+ retryAfter: listResult.retryAfter || null,
130
+ fallbackUsed: false,
131
+ fetchedAt: listResult.lastChecked || new Date().toISOString(),
132
+ error: listResult.error || (listedModels.length > 0 ? null : '模型目录缓存已过期'),
133
+ errorHint: listResult.errorHint || '可点击“刷新可选模型”更新,或手动填写模型名称。'
134
+ };
135
+ }
136
+
94
137
  if (listedModels.length > 0) {
95
138
  return {
96
139
  models: listedModels,
97
140
  supported: true,
98
141
  cached: !!listResult.cached,
142
+ stale: false,
143
+ retryAfter: null,
99
144
  fallbackUsed: false,
100
145
  fetchedAt: listResult.lastChecked || new Date().toISOString(),
101
146
  error: null,
@@ -114,6 +159,8 @@ async function discoverOmpModels(channel, gatewaySourceType, { forceRefresh = fa
114
159
  models: probedModels,
115
160
  supported: probedModels.length > 0,
116
161
  cached: !!probe.cached || !!listResult.cached,
162
+ stale: false,
163
+ retryAfter: null,
117
164
  fallbackUsed: probedModels.length > 0,
118
165
  fetchedAt: probe.lastChecked || listResult.lastChecked || new Date().toISOString(),
119
166
  error: probedModels.length > 0 ? null : (listResult.error || '无法获取可用模型'),
@@ -142,17 +189,12 @@ function attachAuthProvider(channel, snapshot) {
142
189
 
143
190
  function getAuthSnapshotForChannelList() {
144
191
  const snapshot = getCachedOmpAuthProviderSnapshot(CHANNEL_LIST_AUTH_OPTIONS);
145
- const shouldWarm = !snapshot || snapshot.stale;
146
- if (shouldWarm) {
147
- warmOmpAuthProviderSnapshot(CHANNEL_LIST_AUTH_OPTIONS);
148
- }
149
192
  const meta = getOmpAuthProviderCacheMeta(CHANNEL_LIST_AUTH_OPTIONS);
150
193
  return {
151
194
  snapshot,
152
195
  meta: {
153
196
  ...meta,
154
197
  stale: !snapshot || Boolean(snapshot.stale),
155
- refreshing: shouldWarm || meta.refreshing,
156
198
  fallback: !snapshot
157
199
  }
158
200
  };
@@ -241,7 +283,8 @@ module.exports = () => {
241
283
  model,
242
284
  speedTestModel,
243
285
  allowedModels,
244
- modelRedirects
286
+ modelRedirects,
287
+ forceRefresh
245
288
  } = req.body || {};
246
289
  if (!baseUrl) {
247
290
  return res.status(400).json({ error: 'baseUrl is required' });
@@ -259,11 +302,14 @@ module.exports = () => {
259
302
  modelRedirects: Array.isArray(modelRedirects) ? modelRedirects : []
260
303
  };
261
304
  const resolvedGatewaySourceType = resolveGatewaySourceType(tempChannel);
262
- const result = await discoverOmpModels(tempChannel, resolvedGatewaySourceType, { forceRefresh: true });
305
+ const result = await discoverOmpModels(tempChannel, resolvedGatewaySourceType, { forceRefresh: forceRefresh === true });
263
306
  res.json({
264
307
  models: result.models,
265
308
  supported: result.supported,
266
309
  fallbackUsed: result.fallbackUsed,
310
+ cached: result.cached,
311
+ stale: result.stale,
312
+ retryAfter: result.retryAfter,
267
313
  error: result.error,
268
314
  errorHint: result.errorHint
269
315
  });
@@ -291,6 +337,8 @@ module.exports = () => {
291
337
  models: result.models,
292
338
  supported: result.supported,
293
339
  cached: result.cached,
340
+ stale: result.stale,
341
+ retryAfter: result.retryAfter,
294
342
  fallbackUsed: result.fallbackUsed,
295
343
  fetchedAt: result.fetchedAt,
296
344
  error: result.error,
@@ -302,6 +350,35 @@ module.exports = () => {
302
350
  }
303
351
  });
304
352
 
353
+ router.post('/catalog-metadata', (req, res) => {
354
+ try {
355
+ const providerKey = String(req.body?.providerKey || '').trim();
356
+ if (!providerKey) {
357
+ return res.status(400).json({ error: 'providerKey is required' });
358
+ }
359
+ const rawModels = getOmpCatalogModels(providerKey, {
360
+ forceCatalogRefresh: req.body?.forceRefresh === true,
361
+ catalogTimeout: 5000
362
+ });
363
+ const normalizedCatalog = normalizeCatalogModelList(rawModels);
364
+ const models = normalizedCatalog.models.map(model => redactSensitiveFields(model));
365
+ const warnings = normalizedCatalog.warnings.slice();
366
+ if (models.length === 0) {
367
+ warnings.push('OMP catalog did not return model metadata; existing manual definitions were left unchanged.');
368
+ }
369
+ res.json({
370
+ schemaVersion: MODEL_SCHEMA_VERSION,
371
+ fieldSchema: getPublicModelFieldSchema(),
372
+ providerKey,
373
+ models,
374
+ warnings
375
+ });
376
+ } catch (error) {
377
+ console.error('[OMP Channels API] Error reading OMP catalog metadata:', error);
378
+ res.status(502).json({ error: 'Failed to read OMP model metadata' });
379
+ }
380
+ });
381
+
305
382
  router.post('/', (req, res) => {
306
383
  try {
307
384
  const {
@@ -324,9 +401,18 @@ module.exports = () => {
324
401
  presetId,
325
402
  websiteUrl,
326
403
  balanceToken,
327
- balanceUserId
404
+ balanceUserId,
405
+ models,
406
+ modelMetadataMode,
407
+ modelBindings,
408
+ providerConfig
328
409
  } = req.body || {};
329
410
 
411
+ const modelPayloadError = validateOmpModelPayload(req.body || {});
412
+ if (modelPayloadError) {
413
+ return res.status(400).json({ error: modelPayloadError });
414
+ }
415
+
330
416
  if (!name || !baseUrl) {
331
417
  return res.status(400).json({ error: 'Missing required fields: name and baseUrl' });
332
418
  }
@@ -352,7 +438,11 @@ module.exports = () => {
352
438
  presetId,
353
439
  websiteUrl: websiteUrl || '',
354
440
  balanceToken: balanceToken || '',
355
- balanceUserId: balanceUserId || null
441
+ balanceUserId: balanceUserId || null,
442
+ models: Array.isArray(models) ? models : [],
443
+ modelMetadataMode: modelMetadataMode || 'auto',
444
+ modelBindings: Array.isArray(modelBindings) ? modelBindings : [],
445
+ providerConfig: providerConfig || {}
356
446
  });
357
447
  res.json(channel);
358
448
  broadcastSchedulerState('omp', getSchedulerState('omp'));
@@ -364,6 +454,10 @@ module.exports = () => {
364
454
 
365
455
  router.put('/:channelId', (req, res) => {
366
456
  try {
457
+ const modelPayloadError = validateOmpModelPayload(req.body || {});
458
+ if (modelPayloadError) {
459
+ return res.status(400).json({ error: modelPayloadError });
460
+ }
367
461
  const channel = updateChannel(req.params.channelId, req.body || {});
368
462
  res.json(channel);
369
463
  broadcastSchedulerState('omp', getSchedulerState('omp'));
@@ -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 = readOmpSettings();
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 = readOmpSettings();
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,
@@ -7,21 +7,68 @@ const {
7
7
  saveDefaultSpeedTestModels
8
8
  } = require('../../config/model-metadata');
9
9
  const { loadConfig, saveConfig } = require('../../config/loader');
10
+ const {
11
+ MODEL_SCHEMA_VERSION,
12
+ getPublicModelFieldSchema,
13
+ isPlainObject,
14
+ redactSensitiveFields,
15
+ resolveModelDefinition,
16
+ validateModelDefinitions
17
+ } = require('../services/model-definition-schema');
18
+
19
+ function mergeMetadata(base = {}, override = {}) {
20
+ const result = { ...base };
21
+ for (const [key, value] of Object.entries(override || {})) {
22
+ if (isPlainObject(value) && isPlainObject(result[key])) result[key] = { ...result[key], ...value };
23
+ else if (value !== undefined) result[key] = value;
24
+ }
25
+ return result;
26
+ }
27
+
28
+ function normalizeDefinitions(value) {
29
+ if (Array.isArray(value)) {
30
+ return Object.fromEntries(value
31
+ .filter(item => item && typeof item === 'object' && item.id)
32
+ .map(item => [String(item.id), item]));
33
+ }
34
+ return isPlainObject(value) ? value : {};
35
+ }
36
+
37
+ function buildResolvedModelSettings(definitions, overrides) {
38
+ const ids = new Set([
39
+ ...Object.keys(MODEL_METADATA),
40
+ ...Object.keys(overrides || {}),
41
+ ...Object.keys(definitions || {})
42
+ ]);
43
+ const resolvedModels = {};
44
+ const provenance = {};
45
+ const warnings = {};
46
+ for (const id of ids) {
47
+ const stored = definitions[id] || {
48
+ id,
49
+ metadataMode: 'hybrid',
50
+ overrides: overrides[id] || {}
51
+ };
52
+ const resolved = resolveModelDefinition(stored, { builtin: MODEL_METADATA[id] || {} });
53
+ resolvedModels[id] = resolved.spec;
54
+ provenance[id] = resolved.provenance;
55
+ if (resolved.warnings.length > 0) warnings[id] = resolved.warnings;
56
+ }
57
+ return { resolvedModels, provenance, warnings };
58
+ }
10
59
 
11
60
  function handleGetModelSettings(req, res) {
12
61
  try {
13
62
  const config = loadConfig();
14
63
  const overrides = config.modelMetadataOverrides || {};
64
+ const definitions = normalizeDefinitions(config.modelDefinitions);
15
65
  const defaultSpeedTestModels = getDefaultSpeedTestModels();
16
66
 
17
67
  // Build merged table: built-in + user overrides
18
68
  const merged = {};
19
69
  for (const [id, meta] of Object.entries(MODEL_METADATA)) {
20
70
  merged[id] = overrides[id]
21
- ? {
22
- limit: { ...meta.limit, ...(overrides[id].limit || {}) },
23
- pricing: { ...meta.pricing, ...(overrides[id].pricing || {}) }
24
- }
71
+ ? mergeMetadata(meta, overrides[id])
25
72
  : meta;
26
73
  }
27
74
 
@@ -32,9 +79,16 @@ function handleGetModelSettings(req, res) {
32
79
  }
33
80
  }
34
81
 
82
+ const resolved = buildResolvedModelSettings(definitions, overrides);
35
83
  res.json({
36
- models: merged,
37
- overrides,
84
+ schemaVersion: MODEL_SCHEMA_VERSION,
85
+ fieldSchema: getPublicModelFieldSchema(),
86
+ models: redactSensitiveFields(merged),
87
+ overrides: redactSensitiveFields(overrides),
88
+ definitions: redactSensitiveFields(definitions),
89
+ resolvedModels: redactSensitiveFields(resolved.resolvedModels),
90
+ provenance: resolved.provenance,
91
+ warnings: resolved.warnings,
38
92
  builtinModelIds: Object.keys(MODEL_METADATA),
39
93
  lastUpdated: METADATA_LAST_UPDATED,
40
94
  defaultSpeedTestModels
@@ -52,7 +106,7 @@ router.get('/model-metadata', handleGetModelSettings);
52
106
 
53
107
  function handleSaveModelSettings(req, res) {
54
108
  try {
55
- const { overrides, defaultSpeedTestModels } = req.body || {};
109
+ const { overrides, definitions, defaultSpeedTestModels } = req.body || {};
56
110
  if (overrides !== undefined && (typeof overrides !== 'object' || overrides === null || Array.isArray(overrides))) {
57
111
  return res.status(400).json({ error: 'overrides must be an object' });
58
112
  }
@@ -63,6 +117,9 @@ function handleSaveModelSettings(req, res) {
63
117
  if (typeof modelId !== 'string' || !modelId.trim()) {
64
118
  return res.status(400).json({ error: `Invalid model ID: "${modelId}"` });
65
119
  }
120
+ if (!isPlainObject(meta)) {
121
+ return res.status(400).json({ error: `${modelId}: metadata must be an object` });
122
+ }
66
123
  if (meta.limit !== undefined) {
67
124
  if (typeof meta.limit !== 'object') {
68
125
  return res.status(400).json({ error: `${modelId}: limit must be an object` });
@@ -87,19 +144,46 @@ function handleSaveModelSettings(req, res) {
87
144
  }
88
145
  }
89
146
 
147
+ if (definitions !== undefined && !Array.isArray(definitions) && !isPlainObject(definitions)) {
148
+ return res.status(400).json({ error: 'definitions must be an object or array' });
149
+ }
150
+ if (Array.isArray(definitions)) {
151
+ const validation = validateModelDefinitions(definitions);
152
+ if (!validation.valid) {
153
+ return res.status(400).json({ error: validation.error });
154
+ }
155
+ }
156
+ const normalizedDefinitions = definitions === undefined ? undefined : normalizeDefinitions(definitions);
157
+ if (normalizedDefinitions) {
158
+ const definitionList = Object.entries(normalizedDefinitions).map(([id, definition]) => ({
159
+ ...(isPlainObject(definition) ? definition : {}),
160
+ id: isPlainObject(definition) && definition.id ? definition.id : id
161
+ }));
162
+ const validation = validateModelDefinitions(definitionList);
163
+ if (!validation.valid) {
164
+ return res.status(400).json({ error: validation.error });
165
+ }
166
+ }
167
+
90
168
  const config = loadConfig();
91
169
  const newConfig = {
92
170
  ...config,
93
171
  modelMetadataOverrides: overrides && typeof overrides === 'object'
94
172
  ? overrides
95
- : (config.modelMetadataOverrides || {})
173
+ : (config.modelMetadataOverrides || {}),
174
+ modelDefinitions: normalizedDefinitions === undefined
175
+ ? (config.modelDefinitions || {})
176
+ : normalizedDefinitions,
177
+ modelMetadataSchemaVersion: MODEL_SCHEMA_VERSION
96
178
  };
97
179
  saveConfig(newConfig);
98
180
  const persistedDefaultSpeedTestModels = saveDefaultSpeedTestModels(defaultSpeedTestModels);
99
181
 
100
182
  res.json({
101
183
  success: true,
102
- overrides: newConfig.modelMetadataOverrides,
184
+ schemaVersion: MODEL_SCHEMA_VERSION,
185
+ overrides: redactSensitiveFields(newConfig.modelMetadataOverrides),
186
+ definitions: redactSensitiveFields(newConfig.modelDefinitions),
103
187
  defaultSpeedTestModels: persistedDefaultSpeedTestModels
104
188
  });
105
189
  } catch (error) {