@workbench-ai/workbench-core 0.0.68 → 0.0.70

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,96 +1,9 @@
1
- import { createHash } from "node:crypto";
2
- import { isWorkbenchExecutionNetworkEgress, } from "@workbench-ai/workbench-contract";
3
- import YAML from "yaml";
4
- export const EVAL_SPEC_FILE = "eval.yaml";
5
- export const SKILL_SPEC_FILE = "skill.yaml";
6
1
  export const DEFAULT_EXECUTION_RESOURCES = {
7
2
  cpu: 2,
8
3
  memoryGb: 4,
9
4
  diskGb: 10,
10
5
  timeoutMinutes: 20,
11
6
  };
12
- export function validateWorkbenchResolvedSourceYaml(source) {
13
- const errors = [];
14
- const warnings = [];
15
- const trimmed = source.trim();
16
- if (!trimmed) {
17
- errors.push("Resolved Workbench spec cannot be empty.");
18
- }
19
- if (trimmed) {
20
- try {
21
- resolveWorkbenchResolvedSourceYaml(source);
22
- }
23
- catch (error) {
24
- errors.push(...splitErrorMessage(error));
25
- }
26
- }
27
- return {
28
- ok: errors.length === 0,
29
- errors,
30
- warnings,
31
- };
32
- }
33
- export function resolveWorkbenchResolvedSourceYaml(source) {
34
- const parsed = parseYamlRecord(source, "resolved Workbench spec");
35
- const errors = [];
36
- rejectUnknownKeys(parsed, "resolved Workbench spec", [
37
- "version",
38
- "eval",
39
- "skill",
40
- ], errors);
41
- if (parsed.version !== 4) {
42
- throw new Error("Resolved Workbench spec version must be 4.");
43
- }
44
- const evalSpec = normalizeEvalRecord(readRequiredRecord(parsed.eval, EVAL_SPEC_FILE, errors), EVAL_SPEC_FILE, "resolved", errors);
45
- const skill = normalizeSkillRecord(readRequiredRecord(parsed.skill, "resolved Workbench spec.skill", errors), "resolved Workbench spec.skill", "resolved", errors);
46
- if (errors.length > 0) {
47
- throw new Error(errors.join("\n"));
48
- }
49
- return genericSpecFromAuthoredBundle({
50
- version: 4,
51
- eval: evalSpec,
52
- skill: skill,
53
- });
54
- }
55
- export function engineResolveBindingForSourceYaml(source) {
56
- return engineResolveBindingForSpec(resolveWorkbenchResolvedSourceYaml(source));
57
- }
58
- export function engineResolveBindingForSpec(spec) {
59
- const resolver = engineResolveInvocationForSpec(spec);
60
- return {
61
- engine: spec.eval.engine.use,
62
- resolver: {
63
- use: resolver.use,
64
- withFingerprint: fingerprintJson(resolver.with ?? {}),
65
- },
66
- };
67
- }
68
- export function resolveWorkbenchSourceFiles(args) {
69
- return genericSpecFromAuthoredBundle(parseWorkbenchSourceFiles({
70
- evalSource: args.evalSource,
71
- skillSource: args.skillSource,
72
- selectedAgentId: args.selectedAgentId,
73
- }));
74
- }
75
- export function parseWorkbenchSourceFiles(args) {
76
- const errors = [];
77
- const evalSpec = normalizeEvalRecord(parseYamlRecord(args.evalSource, EVAL_SPEC_FILE), EVAL_SPEC_FILE, "authored", errors);
78
- const skill = normalizeSkillRecord(parseYamlRecord(args.skillSource ?? "", "skill YAML"), "skill YAML", "authored", errors, args.selectedAgentId ?? undefined);
79
- if (errors.length > 0) {
80
- throw new Error(errors.join("\n"));
81
- }
82
- return {
83
- version: 4,
84
- eval: evalSpec,
85
- skill: skill,
86
- };
87
- }
88
- export function serializeWorkbenchResolvedSourceYaml(source) {
89
- return YAML.stringify(source).trimEnd() + "\n";
90
- }
91
- export function isWorkbenchSkillManifestPath(filePath) {
92
- return /^skills\/[^/]+\/skill\.ya?ml$/iu.test(filePath.replace(/\\/gu, "/").replace(/^\/+/u, "").replace(/^(?:\.\/)+/u, ""));
93
- }
94
7
  export function resolveEngineCaseExecutionConfig(args) {
95
8
  return {
96
9
  prompt: args.engineCase.prompt,
@@ -98,9 +11,6 @@ export function resolveEngineCaseExecutionConfig(args) {
98
11
  run: args.spec.run,
99
12
  };
100
13
  }
101
- export function engineResolveInvocationForSpec(spec) {
102
- return spec.engineResolve;
103
- }
104
14
  export function engineCaseFilesForRuntimeInput(args) {
105
15
  void args.spec;
106
16
  return engineCasePublicFiles(args.engineCase);
@@ -110,11 +20,6 @@ export function engineCasePublicFiles(engineCase) {
110
20
  .map((file) => ({ ...file }))
111
21
  .sort((left, right) => left.path.localeCompare(right.path));
112
22
  }
113
- export function engineCasePrivateFiles(engineCase) {
114
- return (engineCase.files.private ?? [])
115
- .map((file) => ({ ...file }))
116
- .sort((left, right) => left.path.localeCompare(right.path));
117
- }
118
23
  export function runtimeResources(runtime) {
119
24
  const resources = runtime.resources ?? {};
120
25
  return {
@@ -130,394 +35,6 @@ export function runtimeNetwork(runtime) {
130
35
  export function runtimeSandboxRef(runtime) {
131
36
  return `dockerfile://${runtime.dockerfile}`;
132
37
  }
133
- function genericSpecFromAuthoredBundle(source) {
134
- const engineRuntime = engineRuntimeFromConfig(source.eval.engine);
135
- const engineRun = cloneEngineInvocation(source.eval.engine);
136
- const engineResolve = cloneEngineInvocation(source.eval.engine);
137
- const skill = source.skill;
138
- const selectedAgent = skill.agents[skill.selectedAgentId];
139
- if (!selectedAgent) {
140
- throw new Error(`Skill agent not found: ${skill.selectedAgentId}`);
141
- }
142
- return {
143
- version: 4,
144
- name: source.eval.name,
145
- description: source.eval.description,
146
- eval: {
147
- name: source.eval.name,
148
- description: source.eval.description,
149
- engine: cloneJson(source.eval.engine),
150
- },
151
- skill: {
152
- name: skill.name,
153
- ...(skill.description ? { description: skill.description } : {}),
154
- files: cloneJson(skill.files),
155
- ...(skill.prepare ? { prepare: cloneJson(skill.prepare) } : {}),
156
- defaultAgent: skill.defaultAgent ?? skill.selectedAgentId,
157
- selectedAgentId: skill.selectedAgentId,
158
- selectedAgentName: selectedAgent.name,
159
- agents: cloneJson(skill.agents),
160
- ...(skill.improve
161
- ? {
162
- improve: {
163
- edits: [...skill.improve.edits],
164
- ...(skill.improve.optimizeOn ? { optimizeOn: cloneJson(skill.improve.optimizeOn) } : {}),
165
- ...(skill.improve.selectBy ? { selectBy: cloneJson(skill.improve.selectBy) } : {}),
166
- },
167
- }
168
- : {}),
169
- },
170
- environment: cloneJson(engineRuntime),
171
- adapters: [
172
- ...new Set([
173
- ...source.eval.adapters,
174
- ...skill.adapters,
175
- ]),
176
- ],
177
- engine: cloneJson(source.eval.engine),
178
- engineResolve: cloneJson(engineResolve),
179
- ...(skill.improve ? { improve: clonePhaseAdapter(skill.improve) } : {}),
180
- run: clonePhaseAdapter(selectedAgent),
181
- engineRun: cloneJson(engineRun),
182
- };
183
- }
184
- function normalizeEvalRecord(record, label, mode, errors) {
185
- if (!record) {
186
- return null;
187
- }
188
- rejectUnknownKeys(record, label, [
189
- "version",
190
- "name",
191
- "description",
192
- "adapters",
193
- "engine",
194
- ], errors);
195
- requireSpecVersion(record.version, label, mode === "authored" ? 1 : 4, errors);
196
- const name = readRequiredString(record.name, `${label}.name`, errors);
197
- const description = readRequiredString(record.description, `${label}.description`, errors);
198
- const adapters = normalizeAdapterSources(record.adapters, `${label}.adapters`, errors);
199
- const engine = normalizePhaseAdapter(record.engine, `${label}.engine`, errors);
200
- if (engine) {
201
- normalizeEngineRuntimeConfig(engine, `${label}.engine.with`, errors);
202
- }
203
- return name && description && engine
204
- ? {
205
- version: 4,
206
- name,
207
- description,
208
- adapters,
209
- engine,
210
- }
211
- : null;
212
- }
213
- function normalizeEngineRuntimeConfig(engine, label, errors) {
214
- const config = engine.with && typeof engine.with === "object" && !Array.isArray(engine.with)
215
- ? engine.with
216
- : {};
217
- if (config.environment !== undefined) {
218
- const environment = normalizeRuntime(config.environment, `${label}.environment`, errors);
219
- if (environment) {
220
- config.environment = environment;
221
- engine.with = config;
222
- }
223
- }
224
- }
225
- function normalizeSkillRecord(record, label, mode, errors, selectedAgentId) {
226
- if (!record) {
227
- return null;
228
- }
229
- rejectUnknownKeys(record, label, [
230
- "version",
231
- "name",
232
- "description",
233
- "files",
234
- "prepare",
235
- "adapters",
236
- "defaultAgent",
237
- "agents",
238
- ...(mode === "resolved" ? ["selectedAgentId"] : []),
239
- "improve",
240
- ], errors);
241
- requireSpecVersion(record.version, label, mode === "authored" ? 1 : 4, errors);
242
- const name = readRequiredString(record.name, `${label}.name`, errors);
243
- const description = readOptionalString(record.description, `${label}.description`, errors);
244
- const files = normalizePathRef(record.files, `${label}.files`, errors);
245
- const prepare = normalizeSkillPrepare(record.prepare, `${label}.prepare`, errors);
246
- const adapters = normalizeAdapterSources(record.adapters, `${label}.adapters`, errors);
247
- const agents = normalizeSkillAgents(record.agents, `${label}.agents`, errors);
248
- const defaultAgent = readOptionalString(record.defaultAgent, `${label}.defaultAgent`, errors);
249
- const embeddedSelectedAgent = mode === "resolved"
250
- ? readOptionalString(record.selectedAgentId, `${label}.selectedAgentId`, errors)
251
- : undefined;
252
- const selected = selectedAgentId ?? embeddedSelectedAgent ?? defaultAgent ?? Object.keys(agents).sort()[0];
253
- if (selected && !agents[selected]) {
254
- errors.push(`${label}.${mode === "authored" ? "defaultAgent" : "selectedAgentId"} references unknown agent ${selected}.`);
255
- }
256
- const improve = normalizeSkillImprove(record.improve, `${label}.improve`, errors);
257
- return name && files && selected && Object.keys(agents).length > 0
258
- ? {
259
- version: 4,
260
- name,
261
- ...(description ? { description } : {}),
262
- files,
263
- ...(prepare ? { prepare } : {}),
264
- adapters,
265
- ...(defaultAgent ? { defaultAgent } : {}),
266
- agents,
267
- ...(improve ? { improve } : {}),
268
- selectedAgentId: selected,
269
- }
270
- : null;
271
- }
272
- function normalizeSkillPrepare(value, label, errors) {
273
- if (value === undefined) {
274
- return undefined;
275
- }
276
- const record = readRequiredRecord(value, label, errors);
277
- if (!record) {
278
- return undefined;
279
- }
280
- rejectUnknownKeys(record, label, ["command"], errors);
281
- const command = readRequiredString(record.command, `${label}.command`, errors);
282
- return command ? { command } : undefined;
283
- }
284
- function normalizeSkillAgents(value, label, errors) {
285
- const record = readRequiredRecord(value, label, errors);
286
- if (!record) {
287
- return {};
288
- }
289
- const agents = {};
290
- for (const [agentId, agentValue] of Object.entries(record).sort(([left], [right]) => left.localeCompare(right))) {
291
- if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/u.test(agentId)) {
292
- errors.push(`${label}.${agentId} must use letters, numbers, dots, underscores, or dashes.`);
293
- continue;
294
- }
295
- const agentRecord = readRequiredRecord(agentValue, `${label}.${agentId}`, errors);
296
- if (!agentRecord) {
297
- continue;
298
- }
299
- rejectUnknownKeys(agentRecord, `${label}.${agentId}`, ["name", "use", "with", "auth"], errors);
300
- const name = readRequiredString(agentRecord.name, `${label}.${agentId}.name`, errors);
301
- const invocation = normalizePhaseAdapter(adapterRecordFrom(agentRecord), `${label}.${agentId}`, errors);
302
- if (name && invocation) {
303
- agents[agentId] = {
304
- name,
305
- ...invocation,
306
- };
307
- }
308
- }
309
- if (Object.keys(agents).length === 0) {
310
- errors.push(`${label} must declare at least one agent.`);
311
- }
312
- return agents;
313
- }
314
- function normalizeSkillImprove(value, label, errors) {
315
- if (value === undefined) {
316
- return undefined;
317
- }
318
- const record = readRequiredRecord(value, label, errors);
319
- if (!record) {
320
- return undefined;
321
- }
322
- rejectUnknownKeys(record, label, ["edits", "use", "with", "auth", "optimizeOn", "selectBy"], errors);
323
- const edits = normalizeRelativePathList(record.edits, `${label}.edits`, errors);
324
- const invocation = normalizePhaseAdapter(adapterRecordFrom(record), label, errors);
325
- const optimizeOn = normalizeCaseSelector(record.optimizeOn, `${label}.optimizeOn`, errors);
326
- const selectBy = normalizeSelectionSpec(record.selectBy, `${label}.selectBy`, errors);
327
- return edits.length > 0 && invocation
328
- ? {
329
- ...invocation,
330
- edits,
331
- ...(optimizeOn ? { optimizeOn } : {}),
332
- ...(selectBy ? { selectBy } : {}),
333
- }
334
- : undefined;
335
- }
336
- function normalizeSelectionSpec(value, label, errors) {
337
- if (value === undefined) {
338
- return undefined;
339
- }
340
- const record = readRequiredRecord(value, label, errors);
341
- if (!record) {
342
- return undefined;
343
- }
344
- rejectUnknownKeys(record, label, ["metric", "cases"], errors);
345
- const metric = readRequiredString(record.metric, `${label}.metric`, errors);
346
- const cases = normalizeCaseSelector(record.cases, `${label}.cases`, errors);
347
- return metric
348
- ? {
349
- metric,
350
- ...(cases ? { cases } : {}),
351
- }
352
- : undefined;
353
- }
354
- function normalizeCaseSelector(value, label, errors) {
355
- if (value === undefined) {
356
- return undefined;
357
- }
358
- const record = readRequiredRecord(value, label, errors);
359
- if (!record) {
360
- return undefined;
361
- }
362
- rejectUnknownKeys(record, label, ["all", "split"], errors);
363
- const hasAll = Object.prototype.hasOwnProperty.call(record, "all");
364
- const hasSplit = Object.prototype.hasOwnProperty.call(record, "split");
365
- if (hasAll && hasSplit) {
366
- errors.push(`${label} must specify either all or split, not both.`);
367
- return undefined;
368
- }
369
- if (!hasAll && !hasSplit) {
370
- errors.push(`${label} must specify all: true or split.`);
371
- return undefined;
372
- }
373
- if (hasAll) {
374
- if (record.all !== true) {
375
- errors.push(`${label}.all must be true when provided.`);
376
- return undefined;
377
- }
378
- return { all: true };
379
- }
380
- const split = readRequiredString(record.split, `${label}.split`, errors);
381
- return split ? { split } : undefined;
382
- }
383
- function adapterRecordFrom(record) {
384
- return {
385
- use: record.use,
386
- ...(record.with !== undefined ? { with: record.with } : {}),
387
- ...(record.auth !== undefined ? { auth: record.auth } : {}),
388
- };
389
- }
390
- function requireSpecVersion(value, label, version, errors) {
391
- if (value !== version) {
392
- errors.push(`${label}.version must be ${version}.`);
393
- }
394
- }
395
- function normalizeRuntime(value, label, errors) {
396
- const record = readRequiredRecord(value, label, errors);
397
- if (!record) {
398
- return null;
399
- }
400
- rejectUnknownKeys(record, label, ["dockerfile", "workdir", "resources", "network"], errors);
401
- const dockerfile = normalizeWorkspaceLiteralPath(record.dockerfile, `${label}.dockerfile`, errors);
402
- const runtime = {
403
- dockerfile: dockerfile ?? "environment/Dockerfile",
404
- };
405
- const workdir = readOptionalString(record.workdir, `${label}.workdir`, errors);
406
- if (workdir) {
407
- runtime.workdir = workdir;
408
- }
409
- if (record.resources !== undefined) {
410
- const resources = readRequiredRecord(record.resources, `${label}.resources`, errors);
411
- if (resources) {
412
- rejectUnknownKeys(resources, `${label}.resources`, [
413
- "cpu",
414
- "memoryGb",
415
- "diskGb",
416
- "timeoutMinutes",
417
- ], errors);
418
- readOptionalPositiveNumber(resources.cpu, `${label}.resources.cpu`, errors);
419
- readOptionalPositiveNumber(resources.memoryGb, `${label}.resources.memoryGb`, errors);
420
- readOptionalPositiveNumber(resources.diskGb, `${label}.resources.diskGb`, errors);
421
- readOptionalPositiveNumber(resources.timeoutMinutes, `${label}.resources.timeoutMinutes`, errors);
422
- runtime.resources = resources;
423
- }
424
- }
425
- if (record.network !== undefined) {
426
- const network = readRequiredRecord(record.network, `${label}.network`, errors);
427
- if (network) {
428
- const normalized = normalizeNetworkConfig(network, `${label}.network`, errors);
429
- if (normalized) {
430
- runtime.network = normalized;
431
- }
432
- }
433
- }
434
- return runtime;
435
- }
436
- function normalizeRuntimeOverride(value, label, errors) {
437
- const record = readRequiredRecord(value, label, errors);
438
- if (!record) {
439
- return null;
440
- }
441
- rejectUnknownKeys(record, label, ["dockerfile", "workdir", "resources", "network"], errors);
442
- const runtime = {};
443
- if (record.dockerfile !== undefined) {
444
- const dockerfile = normalizeWorkspaceLiteralPath(record.dockerfile, `${label}.dockerfile`, errors);
445
- if (dockerfile) {
446
- runtime.dockerfile = dockerfile;
447
- }
448
- }
449
- const workdir = readOptionalString(record.workdir, `${label}.workdir`, errors);
450
- if (workdir) {
451
- runtime.workdir = workdir;
452
- }
453
- if (record.resources !== undefined) {
454
- const resources = readRequiredRecord(record.resources, `${label}.resources`, errors);
455
- if (resources) {
456
- rejectUnknownKeys(resources, `${label}.resources`, [
457
- "cpu",
458
- "memoryGb",
459
- "diskGb",
460
- "timeoutMinutes",
461
- ], errors);
462
- readOptionalPositiveNumber(resources.cpu, `${label}.resources.cpu`, errors);
463
- readOptionalPositiveNumber(resources.memoryGb, `${label}.resources.memoryGb`, errors);
464
- readOptionalPositiveNumber(resources.diskGb, `${label}.resources.diskGb`, errors);
465
- readOptionalPositiveNumber(resources.timeoutMinutes, `${label}.resources.timeoutMinutes`, errors);
466
- runtime.resources = resources;
467
- }
468
- }
469
- if (record.network !== undefined) {
470
- const network = readRequiredRecord(record.network, `${label}.network`, errors);
471
- if (network) {
472
- const normalized = normalizeNetworkConfig(network, `${label}.network`, errors);
473
- if (normalized) {
474
- runtime.network = normalized;
475
- }
476
- }
477
- }
478
- return Object.keys(runtime).length > 0 ? runtime : null;
479
- }
480
- function engineRuntimeFromConfig(engine) {
481
- const config = engine.with && typeof engine.with === "object" && !Array.isArray(engine.with)
482
- ? engine.with
483
- : {};
484
- const environment = config.environment;
485
- if (environment && typeof environment === "object" && !Array.isArray(environment)) {
486
- const record = environment;
487
- return {
488
- dockerfile: typeof record.dockerfile === "string" && record.dockerfile.trim()
489
- ? record.dockerfile
490
- : "environment/Dockerfile",
491
- ...(typeof record.workdir === "string" && record.workdir.trim() ? { workdir: record.workdir } : {}),
492
- ...(record.resources && typeof record.resources === "object" && !Array.isArray(record.resources)
493
- ? { resources: record.resources }
494
- : {}),
495
- ...(record.network && typeof record.network === "object" && !Array.isArray(record.network)
496
- ? { network: record.network }
497
- : {}),
498
- };
499
- }
500
- return {
501
- dockerfile: "environment/Dockerfile",
502
- network: { egress: "open" },
503
- resources: {
504
- cpu: DEFAULT_EXECUTION_RESOURCES.cpu,
505
- memoryGb: DEFAULT_EXECUTION_RESOURCES.memoryGb,
506
- diskGb: DEFAULT_EXECUTION_RESOURCES.diskGb,
507
- timeoutMinutes: DEFAULT_EXECUTION_RESOURCES.timeoutMinutes,
508
- },
509
- };
510
- }
511
- function cloneEngineInvocation(engine) {
512
- return clonePhaseAdapter(engine);
513
- }
514
- function clonePhaseAdapter(adapter) {
515
- return {
516
- use: adapter.use,
517
- with: cloneJson(adapter.with ?? {}),
518
- ...(adapter.auth !== undefined ? { auth: cloneJson(adapter.auth) } : {}),
519
- };
520
- }
521
38
  function mergeRuntime(base, override) {
522
39
  if (!override) {
523
40
  return cloneJson(base);
@@ -532,209 +49,11 @@ function mergeRuntime(base, override) {
532
49
  network: override.network ?? base.network,
533
50
  };
534
51
  }
535
- function normalizeAdapterSources(value, label, errors) {
536
- if (value === undefined) {
537
- return [];
538
- }
539
- if (!Array.isArray(value)) {
540
- errors.push(`${label} must be an array of adapter sources.`);
541
- return [];
542
- }
543
- const sources = value.flatMap((entry, index) => {
544
- if (typeof entry !== "string" || entry.trim().length === 0) {
545
- errors.push(`${label}[${index}] must be a non-empty string.`);
546
- return [];
547
- }
548
- return [entry.trim()];
549
- });
550
- return [...new Set(sources)];
551
- }
552
- function normalizeNetworkConfig(network, label, errors) {
553
- rejectUnknownKeys(network, label, ["egress"], errors);
554
- const egress = readOptionalString(network.egress, `${label}.egress`, errors) ?? "open";
555
- if (!isWorkbenchExecutionNetworkEgress(egress)) {
556
- errors.push(`${label}.egress must be none or open.`);
557
- return null;
558
- }
559
- return { egress };
560
- }
561
- function normalizePhaseAdapter(value, label, errors) {
562
- const spec = readAdapterRecord(value, label, errors);
563
- if (!spec) {
564
- return null;
565
- }
566
- return {
567
- use: spec.use,
568
- with: readJsonRecord(spec.with ?? {}),
569
- ...(spec.auth !== undefined ? { auth: spec.auth } : {}),
570
- };
571
- }
572
- function normalizeWorkspaceLiteralPath(value, label, errors) {
573
- const raw = readRequiredString(value, label, errors);
574
- if (!raw) {
575
- return null;
576
- }
577
- return normalizeLiteralPathString(raw, label, errors);
578
- }
579
- function normalizePathRef(value, label, errors) {
580
- const record = readRequiredRecord(value, label, errors);
581
- if (!record) {
582
- return null;
583
- }
584
- rejectUnknownKeys(record, label, ["path"], errors);
585
- const refPath = normalizeWorkspaceLiteralPath(record.path, `${label}.path`, errors);
586
- return refPath ? { path: refPath } : null;
587
- }
588
- function normalizeRelativePathList(value, label, errors) {
589
- if (!Array.isArray(value) || value.length === 0) {
590
- errors.push(`${label} must include at least one path.`);
591
- return [];
592
- }
593
- const paths = value.flatMap((entry, index) => {
594
- if (typeof entry !== "string" || entry.trim().length === 0) {
595
- errors.push(`${label}[${index}] must be a non-empty string.`);
596
- return [];
597
- }
598
- const normalized = normalizeLiteralPathString(entry, `${label}[${index}]`, errors);
599
- return normalized ? [normalized] : [];
600
- });
601
- return [...new Set(paths)];
602
- }
603
- function normalizeLiteralPathString(value, label, errors) {
604
- const trimmed = value.trim();
605
- if (/^(?:\/|[A-Za-z]:[\\/])/u.test(trimmed)) {
606
- errors.push(`${label} must be a relative path, not an absolute path.`);
607
- return null;
608
- }
609
- const normalized = trimmed.replace(/\\/gu, "/");
610
- if (!normalized || normalized.includes("\0")) {
611
- errors.push(`${label} must be a non-empty relative path.`);
612
- return null;
613
- }
614
- if (/[*?]/u.test(normalized)) {
615
- errors.push(`${label} must be a literal path, not a glob.`);
616
- return null;
617
- }
618
- const parts = normalized.split("/");
619
- if (parts.some((part) => part === ".." || part === "." || part === "")) {
620
- errors.push(`${label} must not contain empty, '.', or '..' path segments.`);
621
- return null;
622
- }
623
- return normalized;
624
- }
625
- function parseYamlRecord(source, label = "YAML") {
626
- const document = YAML.parseDocument(source, { prettyErrors: true });
627
- if (document.errors.length > 0) {
628
- throw new Error(document.errors.map((error) => `${label}: ${error.message}`).join("\n"));
629
- }
630
- const parsed = document.toJS();
631
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
632
- throw new Error(`${label} must be a YAML object.`);
633
- }
634
- return parsed;
635
- }
636
- function readAdapterRecord(value, label, errors) {
637
- const record = readRequiredRecord(value, label, errors);
638
- if (!record) {
639
- return undefined;
640
- }
641
- rejectUnknownKeys(record, label, ["use", "with", "auth"], errors);
642
- const use = readRequiredString(record.use, `${label}.use`, errors);
643
- const config = record.with === undefined
644
- ? {}
645
- : readRequiredRecord(record.with, `${label}.with`, errors);
646
- return use
647
- ? {
648
- use,
649
- ...(config ? { with: config } : {}),
650
- ...(record.auth !== undefined && isJson(record.auth) ? { auth: record.auth } : {}),
651
- }
652
- : undefined;
653
- }
654
- function rejectUnknownKeys(record, label, allowed, errors) {
655
- const extras = Object.keys(record).filter((key) => !allowed.includes(key));
656
- if (extras.length > 0) {
657
- errors.push(`${label} includes unsupported ${extras.length === 1 ? "field" : "fields"}: ${extras.join(", ")}.`);
658
- }
659
- }
660
- function readRequiredRecord(value, label, errors) {
661
- if (!value || typeof value !== "object" || Array.isArray(value)) {
662
- errors.push(`${label} must be an object.`);
663
- return null;
664
- }
665
- return value;
666
- }
667
- function readRequiredString(value, label, errors) {
668
- if (typeof value !== "string" || value.trim().length === 0) {
669
- errors.push(`${label} must be a non-empty string.`);
670
- return null;
671
- }
672
- return value.trim();
673
- }
674
- function readOptionalString(value, label, errors) {
675
- if (value === undefined) {
676
- return undefined;
677
- }
678
- if (typeof value !== "string" || value.trim().length === 0) {
679
- errors.push(`${label} must be a non-empty string when provided.`);
680
- return undefined;
681
- }
682
- return value.trim();
683
- }
684
- function readOptionalPositiveNumber(value, label, errors) {
685
- if (value === undefined) {
686
- return;
687
- }
688
- if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
689
- errors.push(`${label} must be a positive number.`);
690
- }
691
- }
692
52
  function readPositiveNumber(value, fallback) {
693
53
  return typeof value === "number" && Number.isFinite(value) && value > 0
694
54
  ? value
695
55
  : fallback;
696
56
  }
697
- function readJsonRecord(value) {
698
- if (!isJson(value)) {
699
- return {};
700
- }
701
- return value;
702
- }
703
- function isJson(value) {
704
- if (value === null) {
705
- return true;
706
- }
707
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
708
- return Number.isFinite(value) || typeof value !== "number";
709
- }
710
- if (Array.isArray(value)) {
711
- return value.every(isJson);
712
- }
713
- if (typeof value === "object") {
714
- return Object.values(value).every(isJson);
715
- }
716
- return false;
717
- }
718
- function fingerprintJson(value) {
719
- return createHash("sha256")
720
- .update(JSON.stringify(canonicalJson(value)))
721
- .digest("hex");
722
- }
723
- function canonicalJson(value) {
724
- if (Array.isArray(value)) {
725
- return value.map(canonicalJson);
726
- }
727
- if (value && typeof value === "object") {
728
- return Object.fromEntries(Object.entries(value)
729
- .sort(([left], [right]) => left.localeCompare(right))
730
- .map(([key, entry]) => [key, canonicalJson(entry)]));
731
- }
732
- return value;
733
- }
734
- function splitErrorMessage(error) {
735
- const message = error instanceof Error ? error.message : String(error);
736
- return message.split(/\n+/u).map((entry) => entry.trim()).filter(Boolean);
737
- }
738
57
  function cloneJson(value) {
739
58
  return JSON.parse(JSON.stringify(value));
740
59
  }