coding-tool-x 3.6.13 → 3.7.0

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.
Files changed (86) hide show
  1. package/README.md +46 -33
  2. package/dist/web/assets/{Analytics-CnA75GxO.js → Analytics-Dma_j2Rq.js} +1 -1
  3. package/dist/web/assets/{ConfigTemplates-D8XEscQY.js → ConfigTemplates-C49Y1PLH.js} +1 -1
  4. package/dist/web/assets/Home-DGGC8H4g.js +1 -0
  5. package/dist/web/assets/Home-ewgeIhF7.css +1 -0
  6. package/dist/web/assets/{PluginManager-BzKZ8TYu.js → PluginManager-B1ECTHyY.js} +1 -1
  7. package/dist/web/assets/{ProjectList-DL4JK6ci.css → ProjectList-B_L55-l9.css} +1 -1
  8. package/dist/web/assets/ProjectList-DyiGyT-H.js +1 -0
  9. package/dist/web/assets/SessionList-CH5GwwiJ.js +1 -0
  10. package/dist/web/assets/{SkillManager-fxOvFTYD.js → SkillManager-CRyVhjYQ.js} +1 -1
  11. package/dist/web/assets/{WorkspaceManager-Ch2mM8jm.js → WorkspaceManager-CO9tclVt.js} +1 -1
  12. package/dist/web/assets/{icons-QFXzEgqM.js → icons-Dop5j7Bf.js} +1 -1
  13. package/dist/web/assets/index-CM26UrnJ.css +1 -0
  14. package/dist/web/assets/index-EQq56pRa.js +2 -0
  15. package/dist/web/assets/{naive-ui-kcWDeJwl.js → naive-ui-CVyvdmg1.js} +1 -1
  16. package/dist/web/index.html +4 -4
  17. package/package.json +2 -1
  18. package/src/commands/channels.js +85 -24
  19. package/src/commands/daemon.js +7 -4
  20. package/src/commands/doctor.js +17 -8
  21. package/src/commands/logs.js +48 -6
  22. package/src/commands/port-config.js +83 -75
  23. package/src/commands/proxy-control.js +52 -19
  24. package/src/commands/stats.js +37 -19
  25. package/src/commands/switch.js +5 -1
  26. package/src/commands/toggle-proxy.js +131 -53
  27. package/src/commands/ui.js +3 -4
  28. package/src/commands/workspace.js +3 -35
  29. package/src/config/default.js +19 -2
  30. package/src/config/loader.js +5 -76
  31. package/src/config/paths.js +99 -11
  32. package/src/index.js +11 -16
  33. package/src/server/api/commands.js +1 -1
  34. package/src/server/api/config-export.js +4 -2
  35. package/src/server/api/config-registry.js +40 -34
  36. package/src/server/api/config.js +5 -1
  37. package/src/server/api/dashboard.js +22 -6
  38. package/src/server/api/mcp.js +5 -3
  39. package/src/server/api/pi-channels.js +349 -0
  40. package/src/server/api/pi-config.js +164 -0
  41. package/src/server/api/pi-projects.js +76 -0
  42. package/src/server/api/pi-proxy.js +154 -0
  43. package/src/server/api/pi-sessions.js +334 -0
  44. package/src/server/api/pi-statistics.js +40 -0
  45. package/src/server/api/plugins.js +1 -1
  46. package/src/server/api/prompts.js +3 -2
  47. package/src/server/api/settings.js +2 -0
  48. package/src/server/api/skills.js +1 -1
  49. package/src/server/api/workspaces.js +3 -41
  50. package/src/server/index.js +41 -0
  51. package/src/server/pi-proxy-server.js +66 -0
  52. package/src/server/services/agents-service.js +2 -1
  53. package/src/server/services/base/proxy-utils.js +1 -0
  54. package/src/server/services/channel-balance.js +151 -1
  55. package/src/server/services/channel-scheduler.js +7 -1
  56. package/src/server/services/commands-service.js +11 -2
  57. package/src/server/services/config-export-service.js +275 -18
  58. package/src/server/services/config-registry-service.js +20 -18
  59. package/src/server/services/config-sync-manager.js +115 -6
  60. package/src/server/services/config-sync-service.js +4 -2
  61. package/src/server/services/config-templates-service.js +183 -45
  62. package/src/server/services/mcp-service.js +217 -33
  63. package/src/server/services/pi-auth-providers.js +294 -0
  64. package/src/server/services/pi-channels.js +94 -0
  65. package/src/server/services/pi-config.js +287 -0
  66. package/src/server/services/pi-sessions.js +490 -0
  67. package/src/server/services/pi-settings-manager.js +571 -0
  68. package/src/server/services/pi-statistics-service.js +317 -0
  69. package/src/server/services/plugins-service.js +673 -11
  70. package/src/server/services/prompts-service.js +112 -7
  71. package/src/server/services/proxy-log-helper.js +1 -0
  72. package/src/server/services/proxy-runtime.js +5 -1
  73. package/src/server/services/sessions.js +19 -35
  74. package/src/server/services/skill-service.js +13 -3
  75. package/src/server/services/speed-test.js +9 -3
  76. package/src/server/services/ui-config.js +50 -12
  77. package/src/server/services/workspace-service.js +117 -225
  78. package/src/server/websocket-server.js +7 -1
  79. package/src/utils/session.js +4 -10
  80. package/dist/web/assets/Home-BiaKZMah.js +0 -1
  81. package/dist/web/assets/Home-CIua-b-o.css +0 -1
  82. package/dist/web/assets/ProjectList-BPfG_92X.js +0 -1
  83. package/dist/web/assets/SessionList-BIVtw00v.js +0 -1
  84. package/dist/web/assets/index-BpBFF3b2.js +0 -2
  85. package/dist/web/assets/index-Dsh4Q5Cg.css +0 -1
  86. package/src/utils/cli-flags.js +0 -7
@@ -0,0 +1,571 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { spawnSync } = require('child_process');
4
+ const yaml = require('js-yaml');
5
+ const piConfig = require('./pi-config');
6
+
7
+ const MANAGED_PROVIDER_PREFIX = 'ctx-';
8
+ const MODELS_BACKUP_MARKER = '.ctx-backup-';
9
+ const THINKING_EFFORT_LEVELS = ['minimal', 'low', 'medium', 'high', 'xhigh'];
10
+ const THINKING_MODES = new Set([
11
+ 'effort',
12
+ 'budget',
13
+ 'google-level',
14
+ 'anthropic-adaptive',
15
+ 'anthropic-budget-effort'
16
+ ]);
17
+
18
+ let lastManagedOmpSyncResult = null;
19
+
20
+ function getPiPaths(...args) {
21
+ return piConfig.getPiPaths(...args);
22
+ }
23
+
24
+ function ensurePiDir(...args) {
25
+ return piConfig.ensurePiDir(...args);
26
+ }
27
+
28
+ function normalizeProviderId(value = '') {
29
+ return String(value || '')
30
+ .trim()
31
+ .toLowerCase()
32
+ .replace(/[^a-z0-9_-]/g, '-')
33
+ .replace(/-+/g, '-')
34
+ .replace(/^-|-$/g, '')
35
+ || 'coding-tool-x';
36
+ }
37
+
38
+ function getManagedProviderId(channel = {}) {
39
+ const baseId = normalizeProviderId(channel.providerKey || channel.provider || channel.name || channel.id);
40
+ return baseId.startsWith(MANAGED_PROVIDER_PREFIX) ? baseId : `${MANAGED_PROVIDER_PREFIX}${baseId}`;
41
+ }
42
+
43
+ function isManagedProviderId(providerId = '') {
44
+ return String(providerId || '').startsWith(MANAGED_PROVIDER_PREFIX);
45
+ }
46
+
47
+ function normalizeProviderApi(value = '') {
48
+ const normalized = String(value || '').trim();
49
+ if (!normalized || normalized === 'openai' || normalized === 'chat' || normalized === 'chat.completions') {
50
+ return 'openai-completions';
51
+ }
52
+ if (normalized === 'responses') {
53
+ return 'openai-responses';
54
+ }
55
+ return normalized;
56
+ }
57
+
58
+ function defaultCost() {
59
+ return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
60
+ }
61
+
62
+ function normalizeCost(value) {
63
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
64
+ return defaultCost();
65
+ }
66
+ const numberOrZero = (item) => {
67
+ const number = Number(item);
68
+ return Number.isFinite(number) ? number : 0;
69
+ };
70
+ return {
71
+ input: numberOrZero(value.input),
72
+ output: numberOrZero(value.output),
73
+ cacheRead: numberOrZero(value.cacheRead ?? value.cache_read),
74
+ cacheWrite: numberOrZero(value.cacheWrite ?? value.cache_write)
75
+ };
76
+ }
77
+
78
+ function normalizePositiveNumber(value, fallback) {
79
+ const number = Number(value);
80
+ return Number.isFinite(number) && number > 0 ? number : fallback;
81
+ }
82
+
83
+ function normalizeModelInput(value) {
84
+ if (!Array.isArray(value)) return ['text', 'image'];
85
+ const filtered = value.filter(item => item === 'text' || item === 'image');
86
+ return filtered.length > 0 ? filtered : ['text', 'image'];
87
+ }
88
+
89
+ function normalizeThinkingEfforts(value) {
90
+ if (!Array.isArray(value)) return undefined;
91
+ const seen = new Set();
92
+ const filtered = value
93
+ .map(item => String(item || '').trim())
94
+ .filter(item => THINKING_EFFORT_LEVELS.includes(item))
95
+ .filter((item) => {
96
+ if (seen.has(item)) return false;
97
+ seen.add(item);
98
+ return true;
99
+ });
100
+ return filtered.length > 0 ? filtered : undefined;
101
+ }
102
+
103
+ function normalizeThinking(value) {
104
+ if (Array.isArray(value)) {
105
+ const efforts = normalizeThinkingEfforts(value);
106
+ return efforts ? { mode: 'effort', efforts } : undefined;
107
+ }
108
+ if (typeof value === 'string') {
109
+ const trimmed = value.trim();
110
+ if (THINKING_EFFORT_LEVELS.includes(trimmed)) {
111
+ return { mode: 'effort', efforts: [trimmed] };
112
+ }
113
+ return undefined;
114
+ }
115
+ if (value && typeof value === 'object') {
116
+ const mode = String(value.mode || '').trim();
117
+ if (!THINKING_MODES.has(mode)) return undefined;
118
+ const efforts = normalizeThinkingEfforts(value.efforts || value.levels);
119
+ const minLevel = String(value.minLevel || '').trim();
120
+ const maxLevel = String(value.maxLevel || '').trim();
121
+ const defaultLevel = String(value.defaultLevel || '').trim();
122
+ const hasRange = THINKING_EFFORT_LEVELS.includes(minLevel) && THINKING_EFFORT_LEVELS.includes(maxLevel);
123
+ if (!efforts && !hasRange) return undefined;
124
+ return compactObject({
125
+ mode,
126
+ efforts,
127
+ defaultLevel: THINKING_EFFORT_LEVELS.includes(defaultLevel) ? defaultLevel : undefined,
128
+ minLevel: efforts ? undefined : minLevel,
129
+ maxLevel: efforts ? undefined : maxLevel,
130
+ effortMap: value.effortMap && typeof value.effortMap === 'object' && !Array.isArray(value.effortMap)
131
+ ? value.effortMap
132
+ : undefined,
133
+ supportsDisplay: typeof value.supportsDisplay === 'boolean' ? value.supportsDisplay : undefined
134
+ });
135
+ }
136
+ return undefined;
137
+ }
138
+
139
+ function compactObject(value = {}) {
140
+ const next = { ...value };
141
+ Object.keys(next).forEach((key) => {
142
+ if (next[key] === undefined) delete next[key];
143
+ });
144
+ return next;
145
+ }
146
+
147
+ function mergeDefined(base = {}, override = {}) {
148
+ const next = { ...base };
149
+ Object.entries(override || {}).forEach(([key, value]) => {
150
+ if (value !== undefined) {
151
+ next[key] = value;
152
+ }
153
+ });
154
+ return next;
155
+ }
156
+
157
+ function buildModelEntry(model = {}, fallbackId = '') {
158
+ const id = String(model.id || model.name || fallbackId || '').trim();
159
+ if (!id) return null;
160
+ return compactObject({
161
+ id,
162
+ name: model.name || id,
163
+ api: model.api ? normalizeProviderApi(model.api) : undefined,
164
+ reasoning: model.reasoning === true,
165
+ thinking: normalizeThinking(model.thinking),
166
+ input: normalizeModelInput(model.input),
167
+ cost: normalizeCost(model.cost),
168
+ contextWindow: normalizePositiveNumber(model.contextWindow || model.context_window, 128000),
169
+ maxTokens: normalizePositiveNumber(model.maxTokens || model.max_tokens, 4096),
170
+ headers: model.headers && typeof model.headers === 'object' && !Array.isArray(model.headers)
171
+ ? model.headers
172
+ : undefined,
173
+ compat: model.compat && typeof model.compat === 'object' && !Array.isArray(model.compat)
174
+ ? model.compat
175
+ : undefined
176
+ });
177
+ }
178
+
179
+ function normalizeModels(channel = {}, options = {}) {
180
+ const models = [];
181
+ const catalogIndex = options.catalogIndex instanceof Map ? options.catalogIndex : new Map();
182
+ const add = (value) => {
183
+ const id = String(value || '').trim();
184
+ if (!id || models.some(model => model.id === id)) return;
185
+ const catalogModel = catalogIndex.get(id.toLowerCase()) || {};
186
+ const entry = buildModelEntry({ ...catalogModel, id }, id);
187
+ if (entry) models.push(entry);
188
+ };
189
+
190
+ if (Array.isArray(channel.models)) {
191
+ channel.models.forEach((model) => {
192
+ if (typeof model === 'string') {
193
+ add(model);
194
+ return;
195
+ }
196
+ if (!model || typeof model !== 'object') return;
197
+ const id = String(model.id || model.name || '').trim();
198
+ if (!id || models.some(item => item.id === id)) return;
199
+ const catalogModel = catalogIndex.get(id.toLowerCase()) || {};
200
+ const entry = buildModelEntry(mergeDefined(catalogModel, model), id);
201
+ if (entry) models.push(entry);
202
+ });
203
+ }
204
+
205
+ add(channel.model);
206
+ add(channel.speedTestModel);
207
+ if (Array.isArray(channel.allowedModels)) {
208
+ channel.allowedModels.forEach(add);
209
+ }
210
+
211
+ return models.map(compactObject);
212
+ }
213
+
214
+ function readOmpCatalogModels(command, providerId, options = {}) {
215
+ const runner = options.catalogRunner || options.modelsRunner || spawnSync;
216
+ const result = runner(command, ['models', providerId, '--json'], {
217
+ encoding: 'utf8',
218
+ env: {
219
+ ...process.env,
220
+ ...(options.env || {})
221
+ },
222
+ timeout: options.catalogTimeout || 5000
223
+ });
224
+ const status = result?.status === undefined || result?.status === null ? 0 : result.status;
225
+ if (result?.error || status !== 0) return [];
226
+ try {
227
+ const parsed = JSON.parse(normalizeCommandOutput(result?.stdout).trim() || '{}');
228
+ return Array.isArray(parsed.models) ? parsed.models : [];
229
+ } catch {
230
+ return [];
231
+ }
232
+ }
233
+
234
+ function buildCatalogIndex(channel = {}, options = {}) {
235
+ const runtime = options.runtime
236
+ || (typeof piConfig.resolvePiRuntime === 'function'
237
+ ? piConfig.resolvePiRuntime(options.env || process.env, options.runtimeOptions || {})
238
+ : null);
239
+ if (!runtime || runtime.runtime !== 'omp' || !runtime.installed) {
240
+ return new Map();
241
+ }
242
+
243
+ const rawProviderId = normalizeProviderId(channel.providerKey || channel.provider || channel.name || '');
244
+ const providerId = rawProviderId.startsWith(MANAGED_PROVIDER_PREFIX)
245
+ ? rawProviderId.slice(MANAGED_PROVIDER_PREFIX.length)
246
+ : rawProviderId;
247
+ if (!providerId) return new Map();
248
+
249
+ const index = new Map();
250
+ readOmpCatalogModels(runtime.command, providerId, options).forEach((model) => {
251
+ const id = String(model?.id || model?.name || '').trim();
252
+ if (id) {
253
+ index.set(id.toLowerCase(), model);
254
+ }
255
+ });
256
+ return index;
257
+ }
258
+
259
+ function buildProviderEntry(channel = {}, options = {}) {
260
+ const catalogIndex = options.catalogIndex instanceof Map
261
+ ? options.catalogIndex
262
+ : buildCatalogIndex(channel, options);
263
+ const entry = {
264
+ baseUrl: String(channel.baseUrl || '').trim(),
265
+ api: normalizeProviderApi(channel.providerApi || channel.api || channel.wireApi),
266
+ models: normalizeModels(channel, { catalogIndex })
267
+ };
268
+
269
+ const apiKey = String(channel.apiKey || '').trim();
270
+ if (apiKey) {
271
+ entry.apiKey = apiKey;
272
+ }
273
+
274
+ if (channel.headers && typeof channel.headers === 'object' && !Array.isArray(channel.headers)) {
275
+ entry.headers = channel.headers;
276
+ }
277
+
278
+ return entry;
279
+ }
280
+
281
+ function readModelsConfig(filePath = getPiPaths().modelsYml) {
282
+ try {
283
+ if (!fs.existsSync(filePath)) {
284
+ return { providers: {} };
285
+ }
286
+ const raw = fs.readFileSync(filePath, 'utf8');
287
+ if (!raw.trim()) {
288
+ return { providers: {} };
289
+ }
290
+ const parsed = yaml.load(raw);
291
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
292
+ return { providers: {} };
293
+ }
294
+ if (!parsed.providers || typeof parsed.providers !== 'object' || Array.isArray(parsed.providers)) {
295
+ parsed.providers = {};
296
+ }
297
+ return parsed;
298
+ } catch (error) {
299
+ throw new Error(`Failed to read OMP models.yml: ${error.message}`);
300
+ }
301
+ }
302
+
303
+ function writeModelsConfig(config, filePath = getPiPaths().modelsYml) {
304
+ ensurePiDir(path.dirname(filePath));
305
+ const doc = yaml.dump(config || { providers: {} }, {
306
+ lineWidth: 120,
307
+ noRefs: true,
308
+ sortKeys: false
309
+ });
310
+ fs.writeFileSync(filePath, doc, 'utf8');
311
+ }
312
+
313
+ function getModelsBackupPrefix(filePath) {
314
+ return `${path.basename(filePath)}${MODELS_BACKUP_MARKER}`;
315
+ }
316
+
317
+ function hasExistingModelsBackup(filePath) {
318
+ const dirPath = path.dirname(filePath);
319
+ if (!fs.existsSync(dirPath)) return false;
320
+ const prefix = getModelsBackupPrefix(filePath);
321
+ return fs.readdirSync(dirPath).some(entry => entry.startsWith(prefix));
322
+ }
323
+
324
+ function formatBackupTimestamp(date = new Date()) {
325
+ return date.toISOString().replace(/[:.]/g, '-');
326
+ }
327
+
328
+ function createModelsBackupIfNeeded(filePath, options = {}) {
329
+ if (options.backup === false || !fs.existsSync(filePath) || hasExistingModelsBackup(filePath)) {
330
+ return null;
331
+ }
332
+
333
+ const dirPath = path.dirname(filePath);
334
+ const prefix = getModelsBackupPrefix(filePath);
335
+ const timestamp = formatBackupTimestamp(options.now instanceof Date ? options.now : new Date());
336
+ let backupPath = path.join(dirPath, `${prefix}${timestamp}`);
337
+ let counter = 1;
338
+ while (fs.existsSync(backupPath)) {
339
+ backupPath = path.join(dirPath, `${prefix}${timestamp}-${counter}`);
340
+ counter += 1;
341
+ }
342
+ fs.copyFileSync(filePath, backupPath);
343
+ return backupPath;
344
+ }
345
+
346
+ function normalizeCommandOutput(value) {
347
+ if (value === undefined || value === null) return '';
348
+ return Buffer.isBuffer(value) ? value.toString('utf8') : String(value);
349
+ }
350
+
351
+ function pushUniqueWarning(warnings, value) {
352
+ const warning = String(value || '').trim();
353
+ if (warning && !warnings.includes(warning)) {
354
+ warnings.push(warning);
355
+ }
356
+ }
357
+
358
+ function collectValidationWarnings(stdout, stderr) {
359
+ const warnings = [];
360
+ normalizeCommandOutput(stderr)
361
+ .split(/\r?\n/)
362
+ .map(line => line.trim())
363
+ .filter(Boolean)
364
+ .forEach(line => pushUniqueWarning(warnings, line));
365
+
366
+ const rawStdout = normalizeCommandOutput(stdout).trim();
367
+ if (rawStdout) {
368
+ try {
369
+ const parsed = JSON.parse(rawStdout);
370
+ if (Array.isArray(parsed?.warnings)) {
371
+ parsed.warnings.forEach(warning => pushUniqueWarning(warnings, warning));
372
+ }
373
+ } catch {
374
+ // OMP's JSON payload is informational here; stderr carries schema warnings.
375
+ }
376
+ }
377
+ return warnings;
378
+ }
379
+
380
+ function runModelsJson(command, env, options = {}) {
381
+ const runner = options.modelsRunner || spawnSync;
382
+ const result = runner(command, ['models', '--json'], {
383
+ encoding: 'utf8',
384
+ env: {
385
+ ...process.env,
386
+ ...env
387
+ },
388
+ timeout: options.timeout || 5000
389
+ });
390
+ if (typeof result === 'string' || Buffer.isBuffer(result)) {
391
+ return { status: 0, stdout: result, stderr: '' };
392
+ }
393
+ return result || { status: 0, stdout: '', stderr: '' };
394
+ }
395
+
396
+ function validateOmpModelsConfig(options = {}) {
397
+ const env = options.env || process.env;
398
+ const runtime = options.runtime
399
+ || (typeof piConfig.resolvePiRuntime === 'function'
400
+ ? piConfig.resolvePiRuntime(env, options.runtimeOptions || {})
401
+ : null);
402
+
403
+ if (!runtime || runtime.runtime !== 'omp' || !runtime.installed) {
404
+ return {
405
+ skipped: true,
406
+ reason: 'omp-not-available',
407
+ command: runtime?.command || 'omp',
408
+ warnings: []
409
+ };
410
+ }
411
+
412
+ const result = runModelsJson(runtime.command, env, options);
413
+ if (result.error) {
414
+ throw new Error(`Failed to validate OMP models.yml with "${runtime.command} models --json": ${result.error.message}`);
415
+ }
416
+
417
+ const status = result.status === undefined || result.status === null ? 0 : result.status;
418
+ const stdout = normalizeCommandOutput(result.stdout);
419
+ const stderr = normalizeCommandOutput(result.stderr);
420
+ const warnings = collectValidationWarnings(stdout, stderr);
421
+ if (status !== 0) {
422
+ const details = stderr.trim() || stdout.trim() || `exit code ${status}`;
423
+ throw new Error(`OMP models.yml validation failed: ${details}`);
424
+ }
425
+
426
+ return {
427
+ skipped: false,
428
+ command: runtime.command,
429
+ warnings,
430
+ validatedAt: new Date().toISOString()
431
+ };
432
+ }
433
+
434
+ function recordManagedOmpSyncResult(result) {
435
+ lastManagedOmpSyncResult = {
436
+ success: true,
437
+ warnings: [],
438
+ ...result
439
+ };
440
+ return lastManagedOmpSyncResult;
441
+ }
442
+
443
+ function getLastManagedOmpSyncResult() {
444
+ return lastManagedOmpSyncResult
445
+ ? {
446
+ ...lastManagedOmpSyncResult,
447
+ warnings: [...(lastManagedOmpSyncResult.warnings || [])],
448
+ validation: lastManagedOmpSyncResult.validation
449
+ ? {
450
+ ...lastManagedOmpSyncResult.validation,
451
+ warnings: [...(lastManagedOmpSyncResult.validation.warnings || [])]
452
+ }
453
+ : lastManagedOmpSyncResult.validation
454
+ }
455
+ : null;
456
+ }
457
+
458
+ function removeLegacyManagedExtension(paths = getPiPaths()) {
459
+ const target = paths.managedProviderExtension;
460
+ if (target && fs.existsSync(target)) {
461
+ fs.unlinkSync(target);
462
+ }
463
+ }
464
+
465
+ function pruneManagedProviders(providers = {}) {
466
+ Object.keys(providers).forEach((providerId) => {
467
+ if (isManagedProviderId(providerId)) {
468
+ delete providers[providerId];
469
+ }
470
+ });
471
+ }
472
+
473
+ function buildManagedModelsConfig(channels = [], baseConfig = readModelsConfig(), options = {}) {
474
+ const next = {
475
+ ...baseConfig,
476
+ providers: {
477
+ ...(baseConfig.providers || {})
478
+ }
479
+ };
480
+ pruneManagedProviders(next.providers);
481
+
482
+ channels
483
+ .filter(channel => channel && channel.enabled !== false && channel.baseUrl)
484
+ .forEach((channel) => {
485
+ next.providers[getManagedProviderId(channel)] = buildProviderEntry(channel, options);
486
+ });
487
+
488
+ return next;
489
+ }
490
+
491
+ function writeManagedOmpProviders(channels = [], options = {}) {
492
+ const paths = getPiPaths();
493
+ const config = buildManagedModelsConfig(channels, readModelsConfig(paths.modelsYml), options);
494
+ const backupPath = createModelsBackupIfNeeded(paths.modelsYml, options);
495
+ writeModelsConfig(config, paths.modelsYml);
496
+ const validation = validateOmpModelsConfig(options);
497
+ recordManagedOmpSyncResult({
498
+ path: paths.modelsYml,
499
+ modelsPath: paths.modelsYml,
500
+ backupPath,
501
+ changed: true,
502
+ validation,
503
+ warnings: validation.warnings || []
504
+ });
505
+ removeLegacyManagedExtension(paths);
506
+ return paths.modelsYml;
507
+ }
508
+
509
+ function removeManagedOmpProviders(options = {}) {
510
+ const paths = getPiPaths();
511
+ const config = readModelsConfig(paths.modelsYml);
512
+ const before = Object.keys(config.providers || {}).length;
513
+ pruneManagedProviders(config.providers);
514
+ const after = Object.keys(config.providers || {}).length;
515
+ let backupPath = null;
516
+ let validation = {
517
+ skipped: true,
518
+ reason: before === after ? 'no-managed-providers' : 'not-run',
519
+ warnings: []
520
+ };
521
+ if (before !== after) {
522
+ backupPath = createModelsBackupIfNeeded(paths.modelsYml, options);
523
+ writeModelsConfig(config, paths.modelsYml);
524
+ validation = validateOmpModelsConfig(options);
525
+ }
526
+ recordManagedOmpSyncResult({
527
+ path: paths.modelsYml,
528
+ modelsPath: paths.modelsYml,
529
+ backupPath,
530
+ changed: before !== after,
531
+ validation,
532
+ warnings: validation.warnings || []
533
+ });
534
+ removeLegacyManagedExtension(paths);
535
+ }
536
+
537
+ function isManagedOmpProvidersActive() {
538
+ const paths = getPiPaths();
539
+ if (!fs.existsSync(paths.modelsYml)) {
540
+ return false;
541
+ }
542
+ const config = readModelsConfig(paths.modelsYml);
543
+ return Object.keys(config.providers || {}).some(isManagedProviderId);
544
+ }
545
+
546
+ function buildManagedExtensionSource(channels = []) {
547
+ return yaml.dump(buildManagedModelsConfig(channels, { providers: {} }), {
548
+ lineWidth: 120,
549
+ noRefs: true,
550
+ sortKeys: false
551
+ });
552
+ }
553
+
554
+ module.exports = {
555
+ MANAGED_PROVIDER_PREFIX,
556
+ buildManagedExtensionSource,
557
+ buildManagedModelsConfig,
558
+ buildProviderEntry,
559
+ getManagedProviderId,
560
+ getLastManagedOmpSyncResult,
561
+ isManagedOmpProvidersActive,
562
+ isManagedProviderExtensionActive: isManagedOmpProvidersActive,
563
+ normalizeProviderId,
564
+ readModelsConfig,
565
+ removeManagedOmpProviders,
566
+ removeManagedProviderExtension: removeManagedOmpProviders,
567
+ validateOmpModelsConfig,
568
+ writeManagedOmpProviders,
569
+ writeManagedProviderExtension: writeManagedOmpProviders,
570
+ writeModelsConfig
571
+ };