@osovv/vv-opencode 0.16.0 → 0.17.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.
@@ -1,10 +1,10 @@
1
1
  // FILE: src/lib/vvoc-config.ts
2
- // VERSION: 1.1.0
2
+ // VERSION: 2.0.0
3
3
  // START_MODULE_CONTRACT
4
- // PURPOSE: Define the canonical vvoc.json document shape, normalization rules, JSON Schema, and validation helpers.
5
- // SCOPE: Versioned schema constants, default section generation, strict and lenient config parsing, section rendering/parsing helpers, and schema-based validation errors for vvoc-owned configuration.
6
- // DEPENDS: [ajv/dist/2020, src/lib/package.ts]
7
- // LINKS: [M-CLI-CONFIG, M-CLI-CONFIG-VALIDATE, M-PLUGIN-GUARDIAN, M-PLUGIN-MEMORY-STORE, M-PLUGIN-SECRETS-REDACTION-INTERNAL-CONFIG]
4
+ // PURPOSE: Define the canonical vvoc.json document shape, schema versions, normalization rules, and validation helpers.
5
+ // SCOPE: Versioned schema constants, preset-aware default config generation, strict and lenient config parsing, section rendering/parsing helpers, and schema plus semantic validation for vvoc-owned configuration.
6
+ // DEPENDS: [ajv/dist/2020, src/lib/agent-models.ts, src/lib/package.ts]
7
+ // LINKS: [M-CLI-CONFIG, M-CLI-CONFIG-VALIDATE, M-CLI-PRESET, M-PLUGIN-GUARDIAN, M-PLUGIN-MEMORY-STORE, M-PLUGIN-SECRETS-REDACTION-INTERNAL-CONFIG]
8
8
  // ROLE: RUNTIME
9
9
  // MAP_MODE: EXPORTS
10
10
  // END_MODULE_CONTRACT
@@ -13,20 +13,26 @@
13
13
  // VVOC_CONFIG_VERSION - Canonical vvoc config document version.
14
14
  // VVOC_CONFIG_SCHEMA_URL - Hosted JSON Schema URL for the canonical vvoc config.
15
15
  // VVOC_CONFIG_SCHEMA - JSON Schema document for vvoc.json.
16
+ // VvocPresetAgents - Partial per-target model override map for a named preset.
17
+ // VvocPreset - Declarative preset shape stored in vvoc.json.
18
+ // VvocPresets - Top-level preset map stored in vvoc.json.
16
19
  // GuardianConfig - Fully seeded guardian section shape.
17
20
  // GuardianConfigOverrides - Partial guardian section override shape.
18
21
  // MemoryConfig - Fully seeded memory section shape.
19
22
  // MemoryConfigOverrides - Partial memory section override shape.
20
23
  // SecretsRedactionConfig - Fully seeded secrets-redaction section shape.
21
24
  // VvocConfig - Fully seeded canonical vvoc config document shape.
25
+ // ParsedVvocConfig - Parsed vvoc config plus the document schema/version found on disk.
22
26
  // createGuardianConfig - Builds a fully seeded guardian section from optional overrides.
23
27
  // createMemoryConfig - Builds a fully seeded memory section from optional overrides.
24
28
  // createDefaultSecretsRedactionConfig - Builds the seeded secrets-redaction section.
29
+ // createDefaultVvocPresets - Builds the seeded named preset map.
25
30
  // createDefaultVvocConfig - Builds the fully seeded canonical vvoc config document.
26
31
  // parseGuardianConfigText - Strictly parses a guardian section JSON snippet.
27
32
  // renderGuardianConfig - Renders a guardian section JSON snippet.
28
33
  // parseMemoryConfigText - Strictly parses a memory section JSON snippet.
29
34
  // renderMemoryConfig - Renders a memory section JSON snippet.
35
+ // parseVersionedVvocConfigText - Strictly parses vvoc.json and returns the source version plus normalized config.
30
36
  // parseVvocConfigText - Strictly parses the canonical vvoc config document.
31
37
  // loadLenientVvocConfigText - Parses vvoc.json leniently for runtime fallback with warnings.
32
38
  // renderVvocConfig - Renders canonical vvoc.json.
@@ -34,17 +40,19 @@
34
40
  // END_MODULE_MAP
35
41
  //
36
42
  // START_CHANGE_SUMMARY
37
- // LAST_CHANGE: [v1.1.0 - Switched canonical vvoc schema URLs to package-version-pinned jsDelivr URLs.]
43
+ // LAST_CHANGE: [v2.0.0 - Added vvoc.json schema v2 with declarative named presets and version-aware v1 normalization.]
38
44
  // END_CHANGE_SUMMARY
39
45
  import { Ajv2020 } from "ajv/dist/2020.js";
46
+ import { SUPPORTED_MODEL_TARGET_NAMES, normalizeModelTargetOverride, } from "./agent-models.js";
40
47
  import { PACKAGE_NAME, PACKAGE_VERSION } from "./package.js";
41
48
  const DEFAULT_GUARDIAN_TIMEOUT_MS = 90_000;
42
49
  const DEFAULT_GUARDIAN_APPROVAL_RISK_THRESHOLD = 80;
43
50
  const DEFAULT_MEMORY_SEARCH_LIMIT = 8;
44
51
  const DEFAULT_SECRETS_REDACTION_TTL_MS = 3_600_000;
45
52
  const DEFAULT_SECRETS_REDACTION_MAX_MAPPINGS = 10_000;
46
- export const VVOC_CONFIG_VERSION = 1;
47
- export const VVOC_CONFIG_SCHEMA_URL = `https://cdn.jsdelivr.net/npm/${PACKAGE_NAME}@${PACKAGE_VERSION}/schemas/vvoc/v1.json`;
53
+ const VVOC_CONFIG_V1_SCHEMA_URL = `https://cdn.jsdelivr.net/npm/${PACKAGE_NAME}@${PACKAGE_VERSION}/schemas/vvoc/v1.json`;
54
+ export const VVOC_CONFIG_VERSION = 2;
55
+ export const VVOC_CONFIG_SCHEMA_URL = `https://cdn.jsdelivr.net/npm/${PACKAGE_NAME}@${PACKAGE_VERSION}/schemas/vvoc/v2.json`;
48
56
  const JSON_SCHEMA_DRAFT_2020_12 = "https://json-schema.org/draft/2020-12/schema";
49
57
  const BUILTIN_SECRETS_REDACTION_PATTERNS = [
50
58
  "email",
@@ -61,6 +69,118 @@ const BUILTIN_SECRETS_REDACTION_PATTERNS = [
61
69
  "syn_key",
62
70
  "hex_token",
63
71
  ];
72
+ const GUARDIAN_CONFIG_SCHEMA = {
73
+ type: "object",
74
+ additionalProperties: false,
75
+ required: ["timeoutMs", "approvalRiskThreshold", "reviewToastDurationMs"],
76
+ properties: {
77
+ model: { type: "string", minLength: 1 },
78
+ variant: { type: "string", minLength: 1 },
79
+ timeoutMs: { type: "integer", minimum: 1 },
80
+ approvalRiskThreshold: { type: "integer", minimum: 0, maximum: 100 },
81
+ reviewToastDurationMs: { type: "integer", minimum: 1 },
82
+ },
83
+ };
84
+ const MEMORY_CONFIG_SCHEMA = {
85
+ type: "object",
86
+ additionalProperties: false,
87
+ required: ["enabled", "defaultSearchLimit"],
88
+ properties: {
89
+ enabled: { type: "boolean" },
90
+ defaultSearchLimit: { type: "integer", minimum: 1 },
91
+ reviewerModel: { type: "string", minLength: 1 },
92
+ reviewerVariant: { type: "string", minLength: 1 },
93
+ },
94
+ };
95
+ const SECRETS_REDACTION_CONFIG_SCHEMA = {
96
+ type: "object",
97
+ additionalProperties: false,
98
+ required: ["enabled", "secret", "ttlMs", "maxMappings", "patterns", "debug"],
99
+ properties: {
100
+ enabled: { type: "boolean" },
101
+ secret: { type: "string", minLength: 1 },
102
+ ttlMs: { type: "integer", minimum: 0 },
103
+ maxMappings: { type: "integer", minimum: 1 },
104
+ debug: { type: "boolean" },
105
+ patterns: {
106
+ type: "object",
107
+ additionalProperties: false,
108
+ required: ["keywords", "regex", "builtin", "exclude"],
109
+ properties: {
110
+ keywords: {
111
+ type: "array",
112
+ items: {
113
+ type: "object",
114
+ additionalProperties: false,
115
+ required: ["value"],
116
+ properties: {
117
+ value: { type: "string", minLength: 1 },
118
+ category: { type: "string", minLength: 1 },
119
+ },
120
+ },
121
+ },
122
+ regex: {
123
+ type: "array",
124
+ items: {
125
+ type: "object",
126
+ additionalProperties: false,
127
+ required: ["pattern", "category"],
128
+ properties: {
129
+ pattern: { type: "string", minLength: 1 },
130
+ category: { type: "string", minLength: 1 },
131
+ },
132
+ },
133
+ },
134
+ builtin: {
135
+ type: "array",
136
+ items: { type: "string", minLength: 1 },
137
+ },
138
+ exclude: {
139
+ type: "array",
140
+ items: { type: "string", minLength: 1 },
141
+ },
142
+ },
143
+ },
144
+ },
145
+ };
146
+ const VVOC_PRESET_AGENTS_SCHEMA = {
147
+ type: "object",
148
+ additionalProperties: false,
149
+ minProperties: 1,
150
+ properties: Object.fromEntries(SUPPORTED_MODEL_TARGET_NAMES.map((agentName) => [agentName, { type: "string", minLength: 1 }])),
151
+ };
152
+ const VVOC_PRESET_SCHEMA = {
153
+ type: "object",
154
+ additionalProperties: false,
155
+ required: ["agents"],
156
+ properties: {
157
+ description: { type: "string", minLength: 1 },
158
+ agents: VVOC_PRESET_AGENTS_SCHEMA,
159
+ },
160
+ };
161
+ const VVOC_CONFIG_V1_SCHEMA = {
162
+ $schema: JSON_SCHEMA_DRAFT_2020_12,
163
+ $id: VVOC_CONFIG_V1_SCHEMA_URL,
164
+ title: "vvoc config",
165
+ description: "Canonical vvoc configuration document (v1).",
166
+ type: "object",
167
+ additionalProperties: false,
168
+ required: ["$schema", "version", "guardian", "memory", "secretsRedaction"],
169
+ properties: {
170
+ $schema: {
171
+ type: "string",
172
+ minLength: 1,
173
+ description: "Hosted JSON Schema URL for vvoc.json.",
174
+ },
175
+ version: {
176
+ type: "integer",
177
+ const: 1,
178
+ },
179
+ guardian: GUARDIAN_CONFIG_SCHEMA,
180
+ memory: MEMORY_CONFIG_SCHEMA,
181
+ secretsRedaction: SECRETS_REDACTION_CONFIG_SCHEMA,
182
+ },
183
+ };
64
184
  export const VVOC_CONFIG_SCHEMA = {
65
185
  $schema: JSON_SCHEMA_DRAFT_2020_12,
66
186
  $id: VVOC_CONFIG_SCHEMA_URL,
@@ -68,7 +188,7 @@ export const VVOC_CONFIG_SCHEMA = {
68
188
  description: "Canonical vvoc configuration document.",
69
189
  type: "object",
70
190
  additionalProperties: false,
71
- required: ["$schema", "version", "guardian", "memory", "secretsRedaction"],
191
+ required: ["$schema", "version", "guardian", "memory", "secretsRedaction", "presets"],
72
192
  properties: {
73
193
  $schema: {
74
194
  type: "string",
@@ -79,83 +199,18 @@ export const VVOC_CONFIG_SCHEMA = {
79
199
  type: "integer",
80
200
  const: VVOC_CONFIG_VERSION,
81
201
  },
82
- guardian: {
83
- type: "object",
84
- additionalProperties: false,
85
- required: ["timeoutMs", "approvalRiskThreshold", "reviewToastDurationMs"],
86
- properties: {
87
- model: { type: "string", minLength: 1 },
88
- variant: { type: "string", minLength: 1 },
89
- timeoutMs: { type: "integer", minimum: 1 },
90
- approvalRiskThreshold: { type: "integer", minimum: 0, maximum: 100 },
91
- reviewToastDurationMs: { type: "integer", minimum: 1 },
92
- },
93
- },
94
- memory: {
202
+ guardian: GUARDIAN_CONFIG_SCHEMA,
203
+ memory: MEMORY_CONFIG_SCHEMA,
204
+ secretsRedaction: SECRETS_REDACTION_CONFIG_SCHEMA,
205
+ presets: {
95
206
  type: "object",
96
- additionalProperties: false,
97
- required: ["enabled", "defaultSearchLimit"],
98
- properties: {
99
- enabled: { type: "boolean" },
100
- defaultSearchLimit: { type: "integer", minimum: 1 },
101
- reviewerModel: { type: "string", minLength: 1 },
102
- reviewerVariant: { type: "string", minLength: 1 },
103
- },
104
- },
105
- secretsRedaction: {
106
- type: "object",
107
- additionalProperties: false,
108
- required: ["enabled", "secret", "ttlMs", "maxMappings", "patterns", "debug"],
109
- properties: {
110
- enabled: { type: "boolean" },
111
- secret: { type: "string", minLength: 1 },
112
- ttlMs: { type: "integer", minimum: 0 },
113
- maxMappings: { type: "integer", minimum: 1 },
114
- debug: { type: "boolean" },
115
- patterns: {
116
- type: "object",
117
- additionalProperties: false,
118
- required: ["keywords", "regex", "builtin", "exclude"],
119
- properties: {
120
- keywords: {
121
- type: "array",
122
- items: {
123
- type: "object",
124
- additionalProperties: false,
125
- required: ["value"],
126
- properties: {
127
- value: { type: "string", minLength: 1 },
128
- category: { type: "string", minLength: 1 },
129
- },
130
- },
131
- },
132
- regex: {
133
- type: "array",
134
- items: {
135
- type: "object",
136
- additionalProperties: false,
137
- required: ["pattern", "category"],
138
- properties: {
139
- pattern: { type: "string", minLength: 1 },
140
- category: { type: "string", minLength: 1 },
141
- },
142
- },
143
- },
144
- builtin: {
145
- type: "array",
146
- items: { type: "string", minLength: 1 },
147
- },
148
- exclude: {
149
- type: "array",
150
- items: { type: "string", minLength: 1 },
151
- },
152
- },
153
- },
154
- },
207
+ propertyNames: { minLength: 1 },
208
+ additionalProperties: VVOC_PRESET_SCHEMA,
155
209
  },
156
210
  },
157
211
  };
158
212
  const ajv = new Ajv2020({ allErrors: true, strict: false });
213
+ const validateWithV1Schema = ajv.compile(VVOC_CONFIG_V1_SCHEMA);
159
214
  const validateWithSchema = ajv.compile(VVOC_CONFIG_SCHEMA);
160
215
  // START_BLOCK_DEFAULT_CONFIG_BUILDERS
161
216
  export function createGuardianConfig(overrides = {}) {
@@ -191,6 +246,37 @@ export function createDefaultSecretsRedactionConfig() {
191
246
  debug: false,
192
247
  };
193
248
  }
249
+ export function createDefaultVvocPresets() {
250
+ return createVvocPresets({
251
+ openai: {
252
+ description: "Starter OpenAI overrides for common vvoc model targets.",
253
+ agents: {
254
+ default: "openai/gpt-5.4:xhigh",
255
+ "small-model": "openai/gpt-5.4-mini",
256
+ guardian: "openaig/gpt-5.4-mini",
257
+ explore: "openai/gpt-5.4-mini",
258
+ },
259
+ },
260
+ zai: {
261
+ description: "Starter ZAI overrides for common vvoc model targets.",
262
+ agents: {
263
+ default: "zai-coding-plan/glm-5.1",
264
+ "small-model": "zai-coding-plan/glm-4.7-flashx",
265
+ guardian: "zai-coding-plan/glm-4.7-flash-x",
266
+ explore: "zai-coding-plan/glm-4.7-flashx",
267
+ },
268
+ },
269
+ minimax: {
270
+ description: "Starter MiniMax overrides for common vvoc model targets.",
271
+ agents: {
272
+ default: "minimax-coding-plan/minimax-m2.7",
273
+ "small-model": "minimax-coding-plan/minimax-m2.1",
274
+ guardian: "minimax-coding-plan/minimax-m2.1",
275
+ explore: "minimax-coding-plan/minimax-m2.1",
276
+ },
277
+ },
278
+ });
279
+ }
194
280
  export function createDefaultVvocConfig() {
195
281
  return {
196
282
  $schema: VVOC_CONFIG_SCHEMA_URL,
@@ -198,6 +284,7 @@ export function createDefaultVvocConfig() {
198
284
  guardian: createGuardianConfig(),
199
285
  memory: createMemoryConfig(),
200
286
  secretsRedaction: createDefaultSecretsRedactionConfig(),
287
+ presets: createDefaultVvocPresets(),
201
288
  };
202
289
  }
203
290
  // END_BLOCK_DEFAULT_CONFIG_BUILDERS
@@ -258,7 +345,7 @@ export function renderMemoryConfig(overrides = {}) {
258
345
  }
259
346
  // END_BLOCK_SECTION_PARSE_AND_RENDER
260
347
  // START_BLOCK_CANONICAL_CONFIG_PARSE_RENDER
261
- export function parseVvocConfigText(text, label) {
348
+ export function parseVersionedVvocConfigText(text, label) {
262
349
  const value = parseStrictJson(text, label);
263
350
  const errors = validateVvocConfigDocument(value);
264
351
  if (errors.length > 0) {
@@ -266,6 +353,9 @@ export function parseVvocConfigText(text, label) {
266
353
  }
267
354
  return normalizeStrictVvocConfig(value);
268
355
  }
356
+ export function parseVvocConfigText(text, label) {
357
+ return parseVersionedVvocConfigText(text, label).config;
358
+ }
269
359
  export function loadLenientVvocConfigText(text, label, warnings) {
270
360
  let value;
271
361
  try {
@@ -279,12 +369,16 @@ export function loadLenientVvocConfigText(text, label, warnings) {
279
369
  warnings.push(`${label}: expected a top-level object`);
280
370
  return createDefaultVvocConfig();
281
371
  }
372
+ const sourceVersion = readLenientSupportedVersion(value.version, `${label}: version`, warnings);
282
373
  return {
283
- $schema: readLenientSchemaUrl(value.$schema, `${label}: $schema`, warnings),
284
- version: readLenientVersion(value.version, `${label}: version`, warnings),
374
+ $schema: VVOC_CONFIG_SCHEMA_URL,
375
+ version: VVOC_CONFIG_VERSION,
285
376
  guardian: loadLenientGuardianConfig(value.guardian, `${label}: guardian`, warnings),
286
377
  memory: loadLenientMemoryConfig(value.memory, `${label}: memory`, warnings),
287
378
  secretsRedaction: loadLenientSecretsRedactionConfig(value.secretsRedaction, `${label}: secretsRedaction`, warnings),
379
+ presets: sourceVersion === 2
380
+ ? loadLenientVvocPresets(value.presets, `${label}: presets`, warnings)
381
+ : createDefaultVvocPresets(),
288
382
  };
289
383
  }
290
384
  export function renderVvocConfig(config = createDefaultVvocConfig()) {
@@ -294,25 +388,44 @@ export function renderVvocConfig(config = createDefaultVvocConfig()) {
294
388
  guardian: createGuardianConfig(config.guardian),
295
389
  memory: createMemoryConfig(config.memory),
296
390
  secretsRedaction: createSecretsRedactionConfig(config.secretsRedaction),
391
+ presets: createVvocPresets(config.presets),
297
392
  });
298
393
  }
299
394
  // END_BLOCK_CANONICAL_CONFIG_PARSE_RENDER
300
395
  // START_BLOCK_SCHEMA_VALIDATION
301
396
  export function validateVvocConfigDocument(document) {
302
- if (validateWithSchema(document)) {
303
- return [];
397
+ const validator = isPlainObject(document) && document.version === 1 ? validateWithV1Schema : validateWithSchema;
398
+ if (!validator(document)) {
399
+ return (validator.errors ?? []).map(formatSchemaError);
304
400
  }
305
- return (validateWithSchema.errors ?? []).map(formatSchemaError);
401
+ if (isPlainObject(document) && document.version === VVOC_CONFIG_VERSION) {
402
+ return validatePresetSemantics(document);
403
+ }
404
+ return [];
306
405
  }
307
406
  // END_BLOCK_SCHEMA_VALIDATION
308
407
  function normalizeStrictVvocConfig(value) {
309
- return {
310
- $schema: readNonEmptyString(value.$schema, "$schema"),
311
- version: readExactVersion(value.version, "version"),
408
+ const sourceVersion = readSupportedVersion(value.version, "version");
409
+ const baseConfig = {
410
+ $schema: VVOC_CONFIG_SCHEMA_URL,
411
+ version: VVOC_CONFIG_VERSION,
312
412
  guardian: createGuardianConfig(value.guardian),
313
413
  memory: createMemoryConfig(value.memory),
314
414
  secretsRedaction: createSecretsRedactionConfig(value.secretsRedaction),
315
415
  };
416
+ return {
417
+ sourceSchema: readNonEmptyString(value.$schema, "$schema"),
418
+ sourceVersion,
419
+ config: sourceVersion === 1
420
+ ? {
421
+ ...baseConfig,
422
+ presets: createDefaultVvocPresets(),
423
+ }
424
+ : {
425
+ ...baseConfig,
426
+ presets: createVvocPresets(value.presets),
427
+ },
428
+ };
316
429
  }
317
430
  function createSecretsRedactionConfig(overrides = {}) {
318
431
  const defaults = createDefaultSecretsRedactionConfig();
@@ -331,6 +444,30 @@ function createSecretsRedactionConfig(overrides = {}) {
331
444
  debug: overrides.debug ?? defaults.debug,
332
445
  };
333
446
  }
447
+ function createVvocPresets(overrides = {}) {
448
+ const presets = {};
449
+ for (const [presetName, preset] of Object.entries(overrides)) {
450
+ presets[presetName] = createVvocPreset(preset);
451
+ }
452
+ return presets;
453
+ }
454
+ function createVvocPreset(overrides = {}) {
455
+ return compactObject({
456
+ description: normalizeOptionalString(overrides.description),
457
+ agents: createVvocPresetAgents(overrides.agents),
458
+ });
459
+ }
460
+ function createVvocPresetAgents(overrides = {}) {
461
+ const agents = {};
462
+ for (const agentName of SUPPORTED_MODEL_TARGET_NAMES) {
463
+ const value = overrides[agentName];
464
+ if (value === undefined) {
465
+ continue;
466
+ }
467
+ agents[agentName] = normalizeModelTargetOverride(agentName, value, `preset ${agentName}`);
468
+ }
469
+ return agents;
470
+ }
334
471
  function cloneKeywordRules(rules) {
335
472
  return rules.map((rule) => compactObject({ value: rule.value, category: rule.category }));
336
473
  }
@@ -534,18 +671,61 @@ function loadLenientStringArray(value, label, warnings, fallback) {
534
671
  .filter(Boolean);
535
672
  return entries;
536
673
  }
537
- function readLenientSchemaUrl(value, label, warnings) {
538
- const normalized = readLenientOptionalString(value, label, warnings);
539
- return normalized ?? VVOC_CONFIG_SCHEMA_URL;
674
+ function loadLenientVvocPresets(value, label, warnings) {
675
+ if (!isPlainObject(value)) {
676
+ warnings.push(`${label}: expected an object`);
677
+ return createDefaultVvocPresets();
678
+ }
679
+ const presets = {};
680
+ for (const [presetName, presetValue] of Object.entries(value)) {
681
+ if (!presetName.trim()) {
682
+ warnings.push(`${label}: preset names must be non-empty strings`);
683
+ continue;
684
+ }
685
+ if (!isPlainObject(presetValue)) {
686
+ warnings.push(`${label}.${presetName}: expected an object`);
687
+ continue;
688
+ }
689
+ const description = Object.hasOwn(presetValue, "description")
690
+ ? readLenientOptionalString(presetValue.description, `${label}.${presetName}.description`, warnings)
691
+ : undefined;
692
+ const agents = loadLenientVvocPresetAgents(presetValue.agents, `${label}.${presetName}.agents`, warnings);
693
+ if (Object.keys(agents).length === 0) {
694
+ warnings.push(`${label}.${presetName}.agents: expected at least one supported target`);
695
+ continue;
696
+ }
697
+ presets[presetName] = createVvocPreset({ description, agents });
698
+ }
699
+ return Object.keys(presets).length > 0 ? presets : createDefaultVvocPresets();
540
700
  }
541
- function readLenientVersion(value, label, warnings) {
701
+ function loadLenientVvocPresetAgents(value, label, warnings) {
702
+ if (!isPlainObject(value)) {
703
+ warnings.push(`${label}: expected an object`);
704
+ return {};
705
+ }
706
+ const agents = {};
707
+ for (const [agentName, modelValue] of Object.entries(value)) {
708
+ if (!SUPPORTED_MODEL_TARGET_NAMES.includes(agentName)) {
709
+ warnings.push(`${label}: unsupported target "${agentName}"`);
710
+ continue;
711
+ }
712
+ try {
713
+ agents[agentName] = normalizeModelTargetOverride(agentName, modelValue, `${label}.${agentName}`);
714
+ }
715
+ catch (error) {
716
+ warnings.push(`${label}.${agentName}: ${error instanceof Error ? error.message : String(error)}`);
717
+ }
718
+ }
719
+ return agents;
720
+ }
721
+ function readLenientSupportedVersion(value, label, warnings) {
542
722
  if (value === undefined) {
543
723
  return VVOC_CONFIG_VERSION;
544
724
  }
545
- if (value === VVOC_CONFIG_VERSION) {
546
- return VVOC_CONFIG_VERSION;
725
+ if (value === 1 || value === VVOC_CONFIG_VERSION) {
726
+ return value;
547
727
  }
548
- warnings.push(`${label}: expected ${VVOC_CONFIG_VERSION}`);
728
+ warnings.push(`${label}: expected 1 or ${VVOC_CONFIG_VERSION}`);
549
729
  return VVOC_CONFIG_VERSION;
550
730
  }
551
731
  function readLenientOptionalString(value, label, warnings) {
@@ -580,11 +760,11 @@ function parseStrictJson(text, label) {
580
760
  throw new Error(`${label}: failed to parse JSON (${error instanceof Error ? error.message : String(error)})`);
581
761
  }
582
762
  }
583
- function readExactVersion(value, label) {
584
- if (value !== VVOC_CONFIG_VERSION) {
585
- throw new Error(`${label}: expected ${VVOC_CONFIG_VERSION}`);
763
+ function readSupportedVersion(value, label) {
764
+ if (value === 1 || value === VVOC_CONFIG_VERSION) {
765
+ return value;
586
766
  }
587
- return VVOC_CONFIG_VERSION;
767
+ throw new Error(`${label}: expected 1 or ${VVOC_CONFIG_VERSION}`);
588
768
  }
589
769
  function readNonEmptyString(value, label) {
590
770
  if (typeof value !== "string" || !value.trim()) {
@@ -629,6 +809,28 @@ function compactObject(value) {
629
809
  function renderJson(value) {
630
810
  return `${JSON.stringify(value, null, 2)}\n`;
631
811
  }
812
+ function validatePresetSemantics(document) {
813
+ const presetsValue = document.presets;
814
+ if (!isPlainObject(presetsValue)) {
815
+ return [];
816
+ }
817
+ const errors = [];
818
+ for (const [presetName, presetValue] of Object.entries(presetsValue)) {
819
+ if (!isPlainObject(presetValue) || !isPlainObject(presetValue.agents)) {
820
+ continue;
821
+ }
822
+ for (const [agentName, modelValue] of Object.entries(presetValue.agents)) {
823
+ const location = `/presets/${presetName}/agents/${agentName}`;
824
+ try {
825
+ normalizeModelTargetOverride(agentName, modelValue, location);
826
+ }
827
+ catch (error) {
828
+ errors.push(`${location} ${error instanceof Error ? error.message : String(error)}`);
829
+ }
830
+ }
831
+ }
832
+ return errors;
833
+ }
632
834
  function formatSchemaError(error) {
633
835
  const path = error.instancePath || "/";
634
836
  if (error.keyword === "required") {