codex-configurator 0.2.4 → 0.2.6

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.
@@ -1,10 +1,21 @@
1
1
  import { createRequire } from 'node:module';
2
2
 
3
3
  const require = createRequire(import.meta.url);
4
- const CONFIG_REFERENCE_DATA = require('./reference/config-reference.json');
4
+ const BUNDLED_CONFIG_SCHEMA_DATA = require('./reference/config-schema.json');
5
5
 
6
- const DOCUMENT_ID = 'config.toml';
6
+ const ROOT_PLACEHOLDER = '<name>';
7
7
  const PLACEHOLDER_SEGMENT = /^<[^>]+>$/;
8
+ const KEY_SEGMENT_PLACEHOLDERS = {
9
+ agents: '<name>',
10
+ apps: '<id>',
11
+ js_repl_node_module_dirs: '<path>',
12
+ mcp_servers: '<id>',
13
+ model_providers: '<name>',
14
+ notice: '<name>',
15
+ profiles: '<name>',
16
+ projects: '<path>',
17
+ tools: '<tool>',
18
+ };
8
19
  const KIND_PRIORITY = {
9
20
  value: 1,
10
21
  array: 2,
@@ -24,6 +35,887 @@ const isCustomIdPlaceholder = (segment) =>
24
35
 
25
36
  const normalizeType = (type) => String(type || '').replace(/\s+/g, ' ').trim();
26
37
 
38
+ const isObject = (value) =>
39
+ value !== null && typeof value === 'object' && !Array.isArray(value);
40
+
41
+ const buildSchemaRevision = (schema) => JSON.stringify(schema);
42
+
43
+ const isPrimitiveValue = (type) =>
44
+ type === 'string' || type === 'number' || type === 'integer' || type === 'boolean';
45
+
46
+ const isObjectLikeReference = (schema) => {
47
+ if (!schema || typeof schema !== 'object') {
48
+ return false;
49
+ }
50
+
51
+ if (Array.isArray(schema.type) && schema.type.includes('object')) {
52
+ return true;
53
+ }
54
+
55
+ if (schema.type === 'object') {
56
+ return true;
57
+ }
58
+
59
+ if (schema.properties || schema.additionalProperties || schema.patternProperties) {
60
+ return true;
61
+ }
62
+
63
+ if (Array.isArray(schema.oneOf) || Array.isArray(schema.allOf) || Array.isArray(schema.anyOf)) {
64
+ return true;
65
+ }
66
+
67
+ return false;
68
+ };
69
+
70
+ const isScalarLikeReference = (schema) => {
71
+ if (!schema || typeof schema !== 'object') {
72
+ return false;
73
+ }
74
+
75
+ if (isObjectLikeReference(schema)) {
76
+ return false;
77
+ }
78
+
79
+ if (Array.isArray(schema.type) && schema.type.length > 0) {
80
+ return schema.type.some(isPrimitiveValue);
81
+ }
82
+
83
+ if (isPrimitiveValue(normalizeType(schema.type))) {
84
+ return true;
85
+ }
86
+
87
+ return Array.isArray(schema.enum);
88
+ };
89
+
90
+ const getOneOfTypeLabel = (branches, context) => {
91
+ const unique = [...new Set(branches.map((branch) => getTypeLabel(branch, context)).filter(Boolean))];
92
+ if (unique.length === 0) {
93
+ return 'value';
94
+ }
95
+
96
+ return unique.length === 1 ? unique[0] : unique.join(' | ');
97
+ };
98
+
99
+ const getPlaceholderForSegment = (segment, fallback = ROOT_PLACEHOLDER) =>
100
+ segment && KEY_SEGMENT_PLACEHOLDERS[segment] ? KEY_SEGMENT_PLACEHOLDERS[segment] : fallback;
101
+
102
+ const getResolvedDefinition = (schema, definitions, visitedRefs = new Set()) => {
103
+ if (!schema || typeof schema !== 'object' || typeof schema.$ref !== 'string') {
104
+ return { schema, refName: null };
105
+ }
106
+
107
+ const match = schema.$ref.match(/^#\/definitions\/(.+)$/);
108
+ if (!match) {
109
+ return { schema, refName: null };
110
+ }
111
+
112
+ const [, refName] = match;
113
+ const definition = definitions.get(refName);
114
+ if (!definition || visitedRefs.has(definition)) {
115
+ return { schema: definition || schema, refName: null };
116
+ }
117
+
118
+ const nextVisitedRefs = new Set(visitedRefs);
119
+ nextVisitedRefs.add(definition);
120
+
121
+ const [merged] = walkDefinitions(definition, definitions, nextVisitedRefs);
122
+ return {
123
+ schema: merged,
124
+ refName,
125
+ };
126
+ };
127
+
128
+ const normalizeDefinition = (schema, definitions, visitedRefs = new Set()) => {
129
+ if (!schema || typeof schema !== 'object') {
130
+ return schema;
131
+ }
132
+
133
+ const { schema: dereferenced, refName } = getResolvedDefinition(
134
+ schema,
135
+ definitions,
136
+ visitedRefs
137
+ );
138
+ if (dereferenced !== schema) {
139
+ return { ...normalizeDefinition(dereferenced, definitions, visitedRefs), __sourceRef: refName };
140
+ }
141
+
142
+ if (Array.isArray(dereferenced.allOf)) {
143
+ const merged = {};
144
+ dereferenced.allOf.forEach((branch) => {
145
+ const child = normalizeDefinition(branch, definitions, visitedRefs);
146
+ Object.assign(merged, child);
147
+ if (child.properties && !merged.properties) {
148
+ merged.properties = {};
149
+ }
150
+ if (child.properties) {
151
+ merged.properties = { ...merged.properties, ...child.properties };
152
+ }
153
+ if (child.additionalProperties && !merged.additionalProperties) {
154
+ merged.additionalProperties = child.additionalProperties;
155
+ }
156
+ if (child.patternProperties && !merged.patternProperties) {
157
+ merged.patternProperties = child.patternProperties;
158
+ }
159
+ if (child.required && !merged.required) {
160
+ merged.required = child.required;
161
+ }
162
+ if (child.required) {
163
+ merged.required = Array.from(new Set([...(merged.required || []), ...child.required]));
164
+ }
165
+ });
166
+
167
+ return {
168
+ ...merged,
169
+ ...dereferenced,
170
+ ...{ allOf: undefined },
171
+ __sourceRef: merged.__sourceRef || dereferenced.__sourceRef,
172
+ };
173
+ }
174
+
175
+ if (Array.isArray(dereferenced.oneOf)) {
176
+ return {
177
+ ...dereferenced,
178
+ oneOf: dereferenced.oneOf.map((branch) =>
179
+ normalizeDefinition(branch, definitions, visitedRefs)
180
+ ),
181
+ __sourceRef: dereferenced.__sourceRef,
182
+ };
183
+ }
184
+
185
+ if (Array.isArray(dereferenced.anyOf)) {
186
+ return {
187
+ ...dereferenced,
188
+ anyOf: dereferenced.anyOf.map((branch) =>
189
+ normalizeDefinition(branch, definitions, visitedRefs)
190
+ ),
191
+ __sourceRef: dereferenced.__sourceRef,
192
+ };
193
+ }
194
+
195
+ return { ...dereferenced, __sourceRef: dereferenced.__sourceRef };
196
+ };
197
+
198
+ const walkDefinitions = (schema, definitions, visitedRefs = new Set()) => {
199
+ const nodes = [];
200
+ const normalized = normalizeDefinition(schema, definitions, visitedRefs);
201
+
202
+ if (!isObject(normalized)) {
203
+ return [normalized];
204
+ }
205
+
206
+ nodes.push(normalized);
207
+
208
+ if (Array.isArray(normalized.allOf)) {
209
+ normalized.allOf.forEach((branch) => {
210
+ nodes.push(...walkDefinitions(branch, definitions, visitedRefs));
211
+ });
212
+ }
213
+
214
+ return nodes;
215
+ };
216
+
217
+ const getEnumSignature = (schema) =>
218
+ Array.isArray(schema?.enum) ? schema.enum.map((value) => String(value)).join(' | ') : null;
219
+
220
+ const mapBranchTypeLabel = (schema, context) => {
221
+ if (schema?.properties && typeof schema.properties === 'object') {
222
+ const parts = Object.entries(schema.properties).map(([segment, child]) => {
223
+ const childNormalized = normalizeDefinition(child, context.definitions);
224
+ const childType =
225
+ childNormalized?.type === 'object' && childNormalized?.properties
226
+ ? mapBranchTypeLabel(childNormalized, context)
227
+ : getTypeLabel(childNormalized, context);
228
+ return `${segment} = ${childType}`;
229
+ });
230
+ return `{ ${parts.join(', ')} }`;
231
+ }
232
+
233
+ return getTypeLabel(schema, context);
234
+ };
235
+
236
+ const getTypeLabel = (schema, context) => {
237
+ const normalized = normalizeDefinition(schema, context.definitions);
238
+ if (!normalized || typeof normalized !== 'object') {
239
+ return 'value';
240
+ }
241
+
242
+ if (Array.isArray(normalized.oneOf) || Array.isArray(normalized.anyOf)) {
243
+ const branches = (normalized.oneOf || normalized.anyOf || []).map((branch) =>
244
+ getTypeLabel(branch, context)
245
+ );
246
+ const unique = [...new Set(branches)].filter(Boolean);
247
+ return unique.length === 1 ? unique[0] : unique.join(' | ');
248
+ }
249
+
250
+ if (
251
+ typeof normalized.__sourceRef === 'string' &&
252
+ normalized.__sourceRef === 'AbsolutePathBuf'
253
+ ) {
254
+ return 'string (path)';
255
+ }
256
+
257
+ if (Array.isArray(normalized.enum)) {
258
+ const enumLabel = getEnumSignature(normalized);
259
+ if (enumLabel) {
260
+ return enumLabel;
261
+ }
262
+ }
263
+
264
+ if (Array.isArray(normalized.type)) {
265
+ if (normalized.type.length === 1) {
266
+ return String(normalized.type[0]);
267
+ }
268
+ }
269
+
270
+ const normalizedType = String(normalized.type || '').trim();
271
+ if (normalizedType === 'array') {
272
+ return `array<${getTypeLabel(normalized.items || { type: 'string' }, context)}>`;
273
+ }
274
+
275
+ if (normalizedType === 'object') {
276
+ if (normalized.additionalProperties && normalized.type === 'object') {
277
+ return `map<${getTypeLabel(normalized.additionalProperties, context)}>`;
278
+ }
279
+ return 'table';
280
+ }
281
+
282
+ if (isPrimitiveValue(normalizedType)) {
283
+ return normalizedType;
284
+ }
285
+
286
+ const enumLabel = getEnumSignature(normalized);
287
+ if (enumLabel) {
288
+ return enumLabel;
289
+ }
290
+
291
+ if (typeof normalized.type === 'string' && normalized.type.length > 0) {
292
+ return normalized.type;
293
+ }
294
+
295
+ return 'value';
296
+ };
297
+
298
+ const toMapType = (valueSchema, context) => {
299
+ const valueType = getTypeLabel(valueSchema, context);
300
+ return `map<${valueType}>`;
301
+ };
302
+
303
+ const mergeEnumValues = (base = [], added = []) => {
304
+ const next = [...base];
305
+ const seen = new Set(base.map(String));
306
+
307
+ added.forEach((value) => {
308
+ const text = String(value ?? '');
309
+ if (!seen.has(text)) {
310
+ next.push(text);
311
+ seen.add(text);
312
+ }
313
+ });
314
+
315
+ return next;
316
+ };
317
+
318
+ const mergeEnumDescriptions = (base = {}, added = {}) => {
319
+ const next = { ...base };
320
+ if (!added || typeof added !== 'object') {
321
+ return next;
322
+ }
323
+
324
+ Object.entries(added).forEach(([enumValue, description]) => {
325
+ const key = String(enumValue);
326
+ if (!Object.prototype.hasOwnProperty.call(next, key) || String(next[key]).trim() === '') {
327
+ next[key] = String(description || '').trim();
328
+ }
329
+ });
330
+
331
+ return next;
332
+ };
333
+
334
+ const getOneOfEnumDescriptions = (branches) => {
335
+ const mapped = {};
336
+ if (!Array.isArray(branches)) {
337
+ return mapped;
338
+ }
339
+
340
+ branches.forEach((branch) => {
341
+ const description = String(branch?.description || '').trim();
342
+ if (!description) {
343
+ return;
344
+ }
345
+
346
+ const enumValues = Array.isArray(branch?.enum) ? branch.enum : [];
347
+ enumValues.forEach((value) => {
348
+ mapped[String(value)] = description;
349
+ });
350
+ });
351
+
352
+ return mapped;
353
+ };
354
+
355
+ const mergeOptionType = (current, next) => {
356
+ if (!current) {
357
+ return next;
358
+ }
359
+ if (current === next) {
360
+ return current;
361
+ }
362
+
363
+ if (current.includes(next) || next.includes(current)) {
364
+ return current;
365
+ }
366
+
367
+ return `${current} | ${next}`;
368
+ };
369
+
370
+ const getSingleDiscriminatorValue = (schema) => {
371
+ if (!schema || typeof schema !== 'object') {
372
+ return null;
373
+ }
374
+
375
+ if (Object.prototype.hasOwnProperty.call(schema, 'const')) {
376
+ return schema.const;
377
+ }
378
+
379
+ if (Array.isArray(schema.enum) && schema.enum.length === 1) {
380
+ return schema.enum[0];
381
+ }
382
+
383
+ return null;
384
+ };
385
+
386
+ const getObjectBranchLabel = (branch, index) => {
387
+ const title = String(branch?.title || '').trim();
388
+ if (title) {
389
+ return title;
390
+ }
391
+
392
+ const properties = branch?.properties && typeof branch.properties === 'object'
393
+ ? branch.properties
394
+ : {};
395
+ const propertyEntries = Object.entries(properties);
396
+
397
+ for (const [propertyKey, propertySchema] of propertyEntries) {
398
+ const discriminatorValue = getSingleDiscriminatorValue(propertySchema);
399
+ if (discriminatorValue !== null && typeof discriminatorValue !== 'undefined') {
400
+ return String(discriminatorValue);
401
+ }
402
+
403
+ const propertyTitle = String(propertySchema?.title || '').trim();
404
+ if (propertyTitle) {
405
+ return propertyTitle;
406
+ }
407
+
408
+ if (Array.isArray(propertySchema?.enum) && propertySchema.enum.length > 0) {
409
+ return `${propertyKey}=${String(propertySchema.enum[0])}`;
410
+ }
411
+ }
412
+
413
+ const requiredKeys = Array.isArray(branch?.required)
414
+ ? branch.required.map((key) => String(key))
415
+ : [];
416
+ if (requiredKeys.length === 1) {
417
+ return requiredKeys[0];
418
+ }
419
+
420
+ const signatureKeys = propertyEntries
421
+ .map(([propertyKey]) => String(propertyKey))
422
+ .sort((left, right) => left.localeCompare(right));
423
+ if (signatureKeys.length > 0) {
424
+ return signatureKeys.join('+');
425
+ }
426
+
427
+ if (requiredKeys.length > 0) {
428
+ return requiredKeys
429
+ .slice()
430
+ .sort((left, right) => left.localeCompare(right))
431
+ .join('+');
432
+ }
433
+
434
+ return `object_${index + 1}`;
435
+ };
436
+
437
+ const getObjectBranchFixedValues = (branch) => {
438
+ const properties = branch?.properties && typeof branch.properties === 'object'
439
+ ? branch.properties
440
+ : {};
441
+ const fixedValues = {};
442
+
443
+ Object.entries(properties).forEach(([key, propertySchema]) => {
444
+ const fixedValue = getSingleDiscriminatorValue(propertySchema);
445
+ if (fixedValue !== null && typeof fixedValue !== 'undefined') {
446
+ fixedValues[String(key)] = fixedValue;
447
+ }
448
+ });
449
+
450
+ return fixedValues;
451
+ };
452
+
453
+ const buildMixedVariantInfo = (scalarType, objectBranches) => {
454
+ const seenLabels = new Map();
455
+ const objectVariants = objectBranches.map((branch, index) => {
456
+ const baseLabel = getObjectBranchLabel(branch, index);
457
+ const occurrence = (seenLabels.get(baseLabel) || 0) + 1;
458
+ seenLabels.set(baseLabel, occurrence);
459
+ const label = occurrence === 1 ? baseLabel : `${baseLabel} (${occurrence})`;
460
+ const requiredKeys = Array.isArray(branch?.required)
461
+ ? branch.required.map((key) => String(key))
462
+ : [];
463
+ const fixedValues = getObjectBranchFixedValues(branch);
464
+
465
+ return {
466
+ id: `object_${index + 1}`,
467
+ label,
468
+ requiredKeys,
469
+ fixedValues,
470
+ };
471
+ });
472
+
473
+ const requiredObjectKeys = [
474
+ ...new Set(
475
+ objectVariants
476
+ .flatMap((variant) => variant.requiredKeys)
477
+ .map((key) => String(key))
478
+ ),
479
+ ];
480
+
481
+ return {
482
+ kind: 'scalar_object',
483
+ scalarType,
484
+ requiredObjectKeys,
485
+ objectVariants,
486
+ };
487
+ };
488
+
489
+ const mergeVariantInfo = (current, next) => {
490
+ if (!next || typeof next !== 'object') {
491
+ return current || null;
492
+ }
493
+
494
+ if (!current || typeof current !== 'object') {
495
+ return {
496
+ ...next,
497
+ requiredObjectKeys: Array.isArray(next.requiredObjectKeys)
498
+ ? [...next.requiredObjectKeys]
499
+ : [],
500
+ objectVariants: Array.isArray(next.objectVariants)
501
+ ? next.objectVariants.map((variant) => ({
502
+ ...variant,
503
+ requiredKeys: Array.isArray(variant.requiredKeys)
504
+ ? [...variant.requiredKeys]
505
+ : [],
506
+ fixedValues:
507
+ variant.fixedValues && typeof variant.fixedValues === 'object'
508
+ ? { ...variant.fixedValues }
509
+ : {},
510
+ }))
511
+ : [],
512
+ };
513
+ }
514
+
515
+ if (current.kind !== 'scalar_object' || next.kind !== 'scalar_object') {
516
+ return current;
517
+ }
518
+
519
+ const mergedById = new Map();
520
+
521
+ [...(current.objectVariants || []), ...(next.objectVariants || [])].forEach((variant) => {
522
+ const variantId = String(variant?.id || '');
523
+ if (!variantId) {
524
+ return;
525
+ }
526
+
527
+ const previous = mergedById.get(variantId);
528
+ if (!previous) {
529
+ mergedById.set(variantId, {
530
+ id: variantId,
531
+ label: String(variant.label || variantId),
532
+ requiredKeys: Array.isArray(variant.requiredKeys)
533
+ ? variant.requiredKeys.map((key) => String(key))
534
+ : [],
535
+ fixedValues:
536
+ variant.fixedValues && typeof variant.fixedValues === 'object'
537
+ ? { ...variant.fixedValues }
538
+ : {},
539
+ });
540
+ return;
541
+ }
542
+
543
+ mergedById.set(variantId, {
544
+ ...previous,
545
+ requiredKeys: [
546
+ ...new Set([
547
+ ...previous.requiredKeys,
548
+ ...(Array.isArray(variant.requiredKeys)
549
+ ? variant.requiredKeys.map((key) => String(key))
550
+ : []),
551
+ ]),
552
+ ],
553
+ fixedValues: {
554
+ ...previous.fixedValues,
555
+ ...(variant.fixedValues && typeof variant.fixedValues === 'object'
556
+ ? variant.fixedValues
557
+ : {}),
558
+ },
559
+ });
560
+ });
561
+
562
+ return {
563
+ kind: 'scalar_object',
564
+ scalarType: mergeOptionType(
565
+ String(current.scalarType || ''),
566
+ String(next.scalarType || '')
567
+ ),
568
+ requiredObjectKeys: [
569
+ ...new Set([
570
+ ...(Array.isArray(current.requiredObjectKeys)
571
+ ? current.requiredObjectKeys.map((key) => String(key))
572
+ : []),
573
+ ...(Array.isArray(next.requiredObjectKeys)
574
+ ? next.requiredObjectKeys.map((key) => String(key))
575
+ : []),
576
+ ]),
577
+ ],
578
+ objectVariants: [...mergedById.values()],
579
+ };
580
+ };
581
+
582
+ const addReferenceOption = (optionsByKey, pathSegments, schema, context = {}, overrides = {}) => {
583
+ const normalizedPath = normalizeSegments(pathSegments);
584
+ const key = normalizedPath.join('.');
585
+ if (!key) {
586
+ return;
587
+ }
588
+
589
+ const description = String(schema?.description || '').trim();
590
+ const hasDefaultValue = schema && Object.prototype.hasOwnProperty.call(schema, 'default');
591
+ const defaultValue = hasDefaultValue ? schema.default : undefined;
592
+ const typeLabel = overrides.type || getTypeLabel(schema, context);
593
+ const existing = optionsByKey.get(key);
594
+ const enumValues = Array.isArray(schema?.enum)
595
+ ? schema.enum.map((value) => String(value))
596
+ : [];
597
+ const enumOptionDescriptions = {
598
+ ...(overrides.enumOptionDescriptions || {}),
599
+ };
600
+ const variantInfo = overrides.variantInfo || null;
601
+
602
+ if (!existing) {
603
+ optionsByKey.set(key, {
604
+ key,
605
+ key_path: normalizedPath,
606
+ type: typeLabel,
607
+ enum_values: enumValues,
608
+ enumValues: enumValues,
609
+ enumOptionDescriptions,
610
+ description,
611
+ deprecated: schema?.deprecated === true,
612
+ defaultValue,
613
+ variantInfo,
614
+ });
615
+ return;
616
+ }
617
+
618
+ existing.type = mergeOptionType(existing.type, typeLabel);
619
+ existing.enum_values = mergeEnumValues(existing.enum_values, enumValues);
620
+ existing.enumValues = mergeEnumValues(existing.enumValues, enumValues);
621
+ existing.enumOptionDescriptions = mergeEnumDescriptions(
622
+ existing.enumOptionDescriptions,
623
+ enumOptionDescriptions
624
+ );
625
+ existing.variantInfo = mergeVariantInfo(existing.variantInfo, variantInfo);
626
+ if (existing.defaultValue === undefined && hasDefaultValue) {
627
+ existing.defaultValue = defaultValue;
628
+ }
629
+ if (!existing.description && description) {
630
+ existing.description = description;
631
+ }
632
+ };
633
+
634
+ const collectSchemaOptions = (schema, pathSegments, optionsByKey, context) => {
635
+ const normalized = normalizeDefinition(schema, context.definitions);
636
+ if (!normalized || typeof normalized !== 'object') {
637
+ return;
638
+ }
639
+
640
+ const normalizedPath = normalizeSegments(pathSegments);
641
+ if (Array.isArray(normalized.allOf) && normalized.allOf.length > 1) {
642
+ return;
643
+ }
644
+
645
+ if (Array.isArray(normalized.oneOf)) {
646
+ const branchSchemas = normalized.oneOf.map((branch) =>
647
+ normalizeDefinition(branch, context.definitions)
648
+ );
649
+ const enumDescriptionsByValue = getOneOfEnumDescriptions(branchSchemas);
650
+ const scalarBranches = branchSchemas.filter(isScalarLikeReference);
651
+ const objectBranches = branchSchemas.filter(isObjectLikeReference);
652
+
653
+ if (scalarBranches.length > 0 && objectBranches.length > 0) {
654
+ const scalarTypeLabel = getOneOfTypeLabel(scalarBranches, context);
655
+ const scalarEnumValues = scalarBranches.flatMap((branch) =>
656
+ Array.isArray(branch.enum) ? branch.enum : []
657
+ );
658
+ if (normalizedPath.length > 0) {
659
+ const scalarSchema = {
660
+ ...normalized,
661
+ enum: scalarEnumValues,
662
+ type: scalarTypeLabel,
663
+ };
664
+ addReferenceOption(
665
+ optionsByKey,
666
+ normalizedPath,
667
+ scalarSchema,
668
+ context,
669
+ {
670
+ type: scalarTypeLabel,
671
+ enumOptionDescriptions: enumDescriptionsByValue,
672
+ variantInfo: buildMixedVariantInfo(scalarTypeLabel, objectBranches),
673
+ }
674
+ );
675
+ }
676
+
677
+ objectBranches.forEach((branch) => {
678
+ const branchNormalized = normalizeDefinition(branch, context.definitions);
679
+ if (branchNormalized?.properties) {
680
+ Object.entries(branchNormalized.properties).forEach(([segment, value]) => {
681
+ collectSchemaOptions(value, [...normalizedPath, segment], optionsByKey, context);
682
+ });
683
+ }
684
+ });
685
+
686
+ return;
687
+ }
688
+
689
+ const branchDescriptions = branchSchemas.map((branch) => {
690
+ if (branch?.type === 'object' && branch?.properties) {
691
+ return mapBranchTypeLabel(branch, context);
692
+ }
693
+ return getTypeLabel(branch, context);
694
+ });
695
+ const unique = [...new Set(branchDescriptions)];
696
+ if (normalizedPath.length > 0) {
697
+ const typeLabel = unique.length === 1 ? unique[0] : unique.join(' | ');
698
+ const scalarEnumValues = branchSchemas
699
+ .flatMap((branch) => (Array.isArray(branch.enum) ? branch.enum : []))
700
+ .filter(
701
+ (value, index, values) =>
702
+ values.findIndex((item) => Object.is(item, value)) === index
703
+ );
704
+
705
+ addReferenceOption(
706
+ optionsByKey,
707
+ normalizedPath,
708
+ {
709
+ ...normalized,
710
+ ...(scalarEnumValues.length > 0 ? { enum: scalarEnumValues } : {}),
711
+ },
712
+ context,
713
+ {
714
+ type: typeLabel,
715
+ enumOptionDescriptions: enumDescriptionsByValue,
716
+ }
717
+ );
718
+ }
719
+
720
+ normalized.oneOf.forEach((branch) => {
721
+ const branchNormalized = normalizeDefinition(branch, context.definitions);
722
+ if (branchNormalized?.properties) {
723
+ Object.entries(branchNormalized.properties).forEach(([segment, value]) => {
724
+ collectSchemaOptions(value, [...normalizedPath, segment], optionsByKey, context);
725
+ });
726
+ }
727
+ });
728
+ return;
729
+ }
730
+
731
+ if (normalized.type === 'array') {
732
+ if (!normalized.items) {
733
+ addReferenceOption(optionsByKey, normalizedPath, { ...normalized, type: 'array<string>' }, context);
734
+ return;
735
+ }
736
+
737
+ const itemType = normalizeDefinition(normalized.items, context.definitions);
738
+ if (
739
+ itemType &&
740
+ (itemType.type === 'object' ||
741
+ itemType.properties ||
742
+ itemType.additionalProperties ||
743
+ itemType.oneOf ||
744
+ itemType.allOf)
745
+ ) {
746
+ const itemPath = [...normalizedPath, '<index>'];
747
+ if (itemType.properties) {
748
+ Object.entries(itemType.properties).forEach(([segment, value]) => {
749
+ collectSchemaOptions(value, [...itemPath, segment], optionsByKey, context);
750
+ });
751
+ }
752
+ if (itemType.additionalProperties && itemType.properties) {
753
+ const placeholder = getPlaceholderForSegment(itemPath[itemPath.length - 1], '<index>');
754
+ collectSchemaOptions(
755
+ itemType.additionalProperties,
756
+ [...itemPath, placeholder],
757
+ optionsByKey,
758
+ context
759
+ );
760
+ }
761
+ addReferenceOption(optionsByKey, normalizedPath, { ...normalized }, context);
762
+ return;
763
+ }
764
+
765
+ addReferenceOption(
766
+ optionsByKey,
767
+ normalizedPath,
768
+ { ...normalized, type: `array<${getTypeLabel(itemType, context)}>` },
769
+ context
770
+ );
771
+ return;
772
+ }
773
+
774
+ if (normalized.type === 'object' || !normalized.type) {
775
+ const properties = normalized.properties || {};
776
+ const hasProperties = Object.keys(properties).length > 0;
777
+ const hasAdditionalProperties =
778
+ normalized.additionalProperties && typeof normalized.additionalProperties === 'object';
779
+ const hasPatternProperties =
780
+ normalized.patternProperties && typeof normalized.patternProperties === 'object';
781
+ if (!hasProperties && (hasAdditionalProperties || hasPatternProperties)) {
782
+ if (hasAdditionalProperties) {
783
+ const placeholder = getPlaceholderForSegment(
784
+ normalizedPath[normalizedPath.length - 1],
785
+ '<name>'
786
+ );
787
+ const additionalProperties = normalizeDefinition(
788
+ normalized.additionalProperties,
789
+ context.definitions
790
+ );
791
+ const additionalHasChildren =
792
+ additionalProperties?.type === 'object' &&
793
+ (Object.keys(additionalProperties.properties || {}).length > 0 ||
794
+ typeof additionalProperties.additionalProperties === 'object');
795
+
796
+ if (additionalHasChildren) {
797
+ collectSchemaOptions(
798
+ additionalProperties,
799
+ [...normalizedPath, placeholder],
800
+ optionsByKey,
801
+ context
802
+ );
803
+ } else {
804
+ const mapType = toMapType(additionalProperties, context);
805
+ addReferenceOption(
806
+ optionsByKey,
807
+ normalizedPath,
808
+ { ...normalized, type: mapType },
809
+ context
810
+ );
811
+ }
812
+ } else {
813
+ addReferenceOption(optionsByKey, normalizedPath, { ...normalized }, context);
814
+ }
815
+ return;
816
+ }
817
+
818
+ if (hasProperties && normalizedPath.length > 0) {
819
+ addReferenceOption(
820
+ optionsByKey,
821
+ normalizedPath,
822
+ { ...normalized, type: 'table' },
823
+ context
824
+ );
825
+ }
826
+
827
+ Object.entries(properties).forEach(([segment, child]) => {
828
+ collectSchemaOptions(child, [...normalizedPath, segment], optionsByKey, context);
829
+ });
830
+
831
+ if (hasAdditionalProperties) {
832
+ const placeholder = getPlaceholderForSegment(
833
+ normalizedPath[normalizedPath.length - 1],
834
+ '<index>'
835
+ );
836
+ const normalizedAdditional = normalizeDefinition(
837
+ normalized.additionalProperties,
838
+ context.definitions
839
+ );
840
+
841
+ if (normalizedAdditional?.properties) {
842
+ Object.keys(normalizedAdditional.properties || {}).forEach((segment) => {
843
+ collectSchemaOptions(
844
+ normalizedAdditional.properties[segment],
845
+ [...normalizedPath, placeholder, segment],
846
+ optionsByKey,
847
+ context
848
+ );
849
+ });
850
+ } else if (normalizedAdditional?.type === 'object') {
851
+ collectSchemaOptions(
852
+ { ...normalizedAdditional, type: 'object' },
853
+ [...normalizedPath, placeholder],
854
+ optionsByKey,
855
+ context
856
+ );
857
+ } else {
858
+ const mapType = getTypeLabel(normalizedAdditional, context);
859
+ addReferenceOption(
860
+ optionsByKey,
861
+ [...normalizedPath, placeholder],
862
+ { ...normalizedAdditional, type: mapType },
863
+ context
864
+ );
865
+ }
866
+ }
867
+
868
+ if (hasPatternProperties) {
869
+ const patternEntries = Object.entries(normalized.patternProperties);
870
+ patternEntries.forEach(([pattern, child]) => {
871
+ const childType = normalizeDefinition(child, context.definitions);
872
+ if (childType?.type === 'object' || childType?.properties) {
873
+ collectSchemaOptions(childType, [...normalizedPath, `<${pattern}>`], optionsByKey, context);
874
+ } else {
875
+ const mapType = getTypeLabel(childType, context);
876
+ addReferenceOption(
877
+ optionsByKey,
878
+ [...normalizedPath, `<${pattern}>`],
879
+ { ...childType, type: mapType },
880
+ context
881
+ );
882
+ }
883
+ });
884
+ }
885
+
886
+ return;
887
+ }
888
+
889
+ if (normalizedPath.length > 0) {
890
+ addReferenceOption(optionsByKey, normalizedPath, normalized, context);
891
+ }
892
+ };
893
+
894
+ const buildReferenceOptions = (schema) => {
895
+ const definitions = new Map(Object.entries(schema?.definitions || {}));
896
+ const normalizedSchema = normalizeDefinition(schema, definitions);
897
+ if (!isObject(normalizedSchema)) {
898
+ return [];
899
+ }
900
+
901
+ const optionsByKey = new Map();
902
+
903
+ collectSchemaOptions(normalizedSchema, [], optionsByKey, { definitions });
904
+
905
+ const options = [...optionsByKey.values()].sort((left, right) =>
906
+ left.key.localeCompare(right.key)
907
+ );
908
+
909
+ options.forEach((option) => {
910
+ option.top_level = option.key_path[0];
911
+ option.enum_values = option.enum_values || [];
912
+ option.enumValues = option.enumValues || option.enum_values || [];
913
+ option.keyPath = option.key_path;
914
+ });
915
+
916
+ return options;
917
+ };
918
+
27
919
  const mapTypeToKind = (type) => {
28
920
  const normalizedType = normalizeType(type);
29
921
 
@@ -76,28 +968,62 @@ const fullPathMatches = (referencePath, actualPath) =>
76
968
  const countConcreteSegments = (segments) =>
77
969
  segments.reduce((count, segment) => count + (isPlaceholderSegment(segment) ? 0 : 1), 0);
78
970
 
79
- const configDocument =
80
- CONFIG_REFERENCE_DATA?.documents?.find((document) => document?.id === DOCUMENT_ID) || null;
971
+ const buildFeatureKeys = (referenceOptions) => [
972
+ ...new Set(
973
+ referenceOptions
974
+ .filter(
975
+ (option) =>
976
+ option.keyPath.length === 2 &&
977
+ option.keyPath[0] === 'features' &&
978
+ !isPlaceholderSegment(option.keyPath[1])
979
+ )
980
+ .map((option) => option.keyPath[1])
981
+ ),
982
+ ].sort((left, right) => left.localeCompare(right));
81
983
 
82
- const referenceOptions = Array.isArray(configDocument?.options)
83
- ? configDocument.options.map((option) => {
84
- const keyPath = normalizeSegments(option?.key_path);
85
- const key = String(option?.key || keyPath.join('.'));
984
+ const prepareReferenceState = (schema) => {
985
+ if (!isObject(schema)) {
986
+ return null;
987
+ }
86
988
 
87
- return {
88
- key,
89
- keyPath,
90
- type: normalizeType(option?.type),
91
- enumValues: Array.isArray(option?.enum_values)
92
- ? option.enum_values.map((value) => String(value))
93
- : [],
94
- description: String(option?.description || ''),
95
- deprecated: option?.deprecated === true,
96
- };
97
- })
98
- : [];
989
+ const referenceOptions = buildReferenceOptions(schema);
990
+ if (!Array.isArray(referenceOptions) || referenceOptions.length === 0) {
991
+ return null;
992
+ }
99
993
 
100
- const optionsByKey = new Map(referenceOptions.map((option) => [option.key, option]));
994
+ return {
995
+ schema,
996
+ revision: buildSchemaRevision(schema),
997
+ referenceOptions,
998
+ optionsByKey: new Map(referenceOptions.map((option) => [option.key, option])),
999
+ featureKeys: buildFeatureKeys(referenceOptions),
1000
+ };
1001
+ };
1002
+
1003
+ let activeReferenceState = prepareReferenceState(BUNDLED_CONFIG_SCHEMA_DATA);
1004
+ if (!activeReferenceState) {
1005
+ throw new Error('Bundled config schema is invalid.');
1006
+ }
1007
+
1008
+ const getReferenceState = () => activeReferenceState;
1009
+
1010
+ export const isReferenceSchemaValid = (schema) => Boolean(prepareReferenceState(schema));
1011
+
1012
+ export const getReferenceSchemaRevision = () => getReferenceState().revision;
1013
+
1014
+ export const setReferenceSchema = (schema) => {
1015
+ const nextState = prepareReferenceState(schema);
1016
+ if (!nextState) {
1017
+ return false;
1018
+ }
1019
+
1020
+ if (activeReferenceState?.revision === nextState.revision) {
1021
+ return true;
1022
+ }
1023
+
1024
+ activeReferenceState = nextState;
1025
+ return true;
1026
+ };
101
1027
 
102
1028
  const mergeDefinition = (map, definition) => {
103
1029
  const existing = map.get(definition.key);
@@ -117,6 +1043,7 @@ const mergeDefinition = (map, definition) => {
117
1043
  };
118
1044
 
119
1045
  export const getReferenceOptionForPath = (pathSegments) => {
1046
+ const { optionsByKey, referenceOptions } = getReferenceState();
120
1047
  const normalizedPath = normalizeSegments(pathSegments);
121
1048
  if (normalizedPath.length === 0) {
122
1049
  return null;
@@ -145,7 +1072,58 @@ export const getReferenceOptionForPath = (pathSegments) => {
145
1072
  return candidates[0];
146
1073
  };
147
1074
 
1075
+ const getVariantObjectPaths = (normalizedPath, referenceOptions) => {
1076
+ const depth = normalizedPath.length;
1077
+ const paths = referenceOptions
1078
+ .filter((option) => pathPrefixMatches(option.keyPath, normalizedPath))
1079
+ .filter((option) => option.keyPath.length > depth)
1080
+ .map((option) => option.keyPath.slice(depth).join('.'))
1081
+ .filter((relativePath) => relativePath.length > 0);
1082
+
1083
+ return [...new Set(paths)].sort((left, right) => left.localeCompare(right));
1084
+ };
1085
+
1086
+ export const getReferenceVariantForPath = (pathSegments = []) => {
1087
+ const { referenceOptions } = getReferenceState();
1088
+ const normalizedPath = normalizeSegments(pathSegments);
1089
+ if (normalizedPath.length === 0) {
1090
+ return null;
1091
+ }
1092
+
1093
+ const entry = getReferenceOptionForPath(normalizedPath);
1094
+ const variantInfo = entry?.variantInfo;
1095
+ if (!variantInfo || variantInfo.kind !== 'scalar_object') {
1096
+ return null;
1097
+ }
1098
+
1099
+ return {
1100
+ kind: 'scalar_object',
1101
+ scalarType: String(variantInfo.scalarType || entry.type || 'value'),
1102
+ scalarOptions: Array.isArray(entry.enumValues)
1103
+ ? entry.enumValues.map((value) => String(value))
1104
+ : [],
1105
+ requiredObjectKeys: Array.isArray(variantInfo.requiredObjectKeys)
1106
+ ? variantInfo.requiredObjectKeys.map((key) => String(key))
1107
+ : [],
1108
+ objectVariants: Array.isArray(variantInfo.objectVariants)
1109
+ ? variantInfo.objectVariants.map((variant) => ({
1110
+ id: String(variant.id),
1111
+ label: String(variant.label),
1112
+ requiredKeys: Array.isArray(variant.requiredKeys)
1113
+ ? variant.requiredKeys.map((key) => String(key))
1114
+ : [],
1115
+ fixedValues:
1116
+ variant.fixedValues && typeof variant.fixedValues === 'object'
1117
+ ? { ...variant.fixedValues }
1118
+ : {},
1119
+ }))
1120
+ : [],
1121
+ objectSchemaPaths: getVariantObjectPaths(normalizedPath, referenceOptions),
1122
+ };
1123
+ };
1124
+
148
1125
  export const getReferenceTableDefinitions = (pathSegments = []) => {
1126
+ const { referenceOptions } = getReferenceState();
149
1127
  const normalizedPath = normalizeSegments(pathSegments);
150
1128
  const childDefinitions = new Map();
151
1129
  const depth = normalizedPath.length;
@@ -179,22 +1157,10 @@ export const getReferenceTableDefinitions = (pathSegments = []) => {
179
1157
 
180
1158
  export const getReferenceRootDefinitions = () => getReferenceTableDefinitions([]);
181
1159
 
182
- const featureKeys = [
183
- ...new Set(
184
- referenceOptions
185
- .filter(
186
- (option) =>
187
- option.keyPath.length === 2 &&
188
- option.keyPath[0] === 'features' &&
189
- !isPlaceholderSegment(option.keyPath[1])
190
- )
191
- .map((option) => option.keyPath[1])
192
- ),
193
- ].sort((left, right) => left.localeCompare(right));
194
-
195
- export const getReferenceFeatureKeys = () => featureKeys;
1160
+ export const getReferenceFeatureKeys = () => [...getReferenceState().featureKeys];
196
1161
 
197
1162
  export const getReferenceCustomIdPlaceholder = (pathSegments = []) => {
1163
+ const { referenceOptions } = getReferenceState();
198
1164
  const normalizedPath = normalizeSegments(pathSegments);
199
1165
  const depth = normalizedPath.length;
200
1166
  const placeholders = new Set();
@@ -219,6 +1185,7 @@ export const getReferenceCustomIdPlaceholder = (pathSegments = []) => {
219
1185
  };
220
1186
 
221
1187
  export const getReferenceDescendantOptions = (pathSegments = []) => {
1188
+ const { referenceOptions } = getReferenceState();
222
1189
  const normalizedPath = normalizeSegments(pathSegments);
223
1190
 
224
1191
  return referenceOptions