@workbench-ai/workbench-core 0.0.46

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 (72) hide show
  1. package/dist/adapter-auth.d.ts +63 -0
  2. package/dist/adapter-auth.d.ts.map +1 -0
  3. package/dist/adapter-auth.js +244 -0
  4. package/dist/execution-events.d.ts +53 -0
  5. package/dist/execution-events.d.ts.map +1 -0
  6. package/dist/execution-events.js +195 -0
  7. package/dist/execution-graph.d.ts +27 -0
  8. package/dist/execution-graph.d.ts.map +1 -0
  9. package/dist/execution-graph.js +126 -0
  10. package/dist/execution-jobs.d.ts +70 -0
  11. package/dist/execution-jobs.d.ts.map +1 -0
  12. package/dist/execution-jobs.js +229 -0
  13. package/dist/execution-outputs.d.ts +9 -0
  14. package/dist/execution-outputs.d.ts.map +1 -0
  15. package/dist/execution-outputs.js +393 -0
  16. package/dist/execution-phases.d.ts +21 -0
  17. package/dist/execution-phases.d.ts.map +1 -0
  18. package/dist/execution-phases.js +262 -0
  19. package/dist/execution-runtime-types.d.ts +35 -0
  20. package/dist/execution-runtime-types.d.ts.map +1 -0
  21. package/dist/execution-runtime-types.js +1 -0
  22. package/dist/execution-scheduler.d.ts +31 -0
  23. package/dist/execution-scheduler.d.ts.map +1 -0
  24. package/dist/execution-scheduler.js +241 -0
  25. package/dist/execution-traces.d.ts +16 -0
  26. package/dist/execution-traces.d.ts.map +1 -0
  27. package/dist/execution-traces.js +164 -0
  28. package/dist/execution-usage.d.ts +12 -0
  29. package/dist/execution-usage.d.ts.map +1 -0
  30. package/dist/execution-usage.js +433 -0
  31. package/dist/generic-spec.d.ts +113 -0
  32. package/dist/generic-spec.d.ts.map +1 -0
  33. package/dist/generic-spec.js +656 -0
  34. package/dist/index.d.ts +160 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +2858 -0
  37. package/dist/model-prices-litellm.d.ts +9674 -0
  38. package/dist/model-prices-litellm.d.ts.map +1 -0
  39. package/dist/model-prices-litellm.js +9668 -0
  40. package/dist/runtime-utils.d.ts +18 -0
  41. package/dist/runtime-utils.d.ts.map +1 -0
  42. package/dist/runtime-utils.js +108 -0
  43. package/dist/sandbox-backends/docker.d.ts +5 -0
  44. package/dist/sandbox-backends/docker.d.ts.map +1 -0
  45. package/dist/sandbox-backends/docker.js +568 -0
  46. package/dist/sandbox-backends/index.d.ts +37 -0
  47. package/dist/sandbox-backends/index.d.ts.map +1 -0
  48. package/dist/sandbox-backends/index.js +79 -0
  49. package/dist/sandbox-backends/names.d.ts +6 -0
  50. package/dist/sandbox-backends/names.d.ts.map +1 -0
  51. package/dist/sandbox-backends/names.js +14 -0
  52. package/dist/sandbox-backends/template-images.d.ts +4 -0
  53. package/dist/sandbox-backends/template-images.d.ts.map +1 -0
  54. package/dist/sandbox-backends/template-images.js +48 -0
  55. package/dist/sandbox-inputs.d.ts +27 -0
  56. package/dist/sandbox-inputs.d.ts.map +1 -0
  57. package/dist/sandbox-inputs.js +220 -0
  58. package/dist/sandbox-plane.d.ts +89 -0
  59. package/dist/sandbox-plane.d.ts.map +1 -0
  60. package/dist/sandbox-plane.js +327 -0
  61. package/dist/subject-patch.d.ts +8 -0
  62. package/dist/subject-patch.d.ts.map +1 -0
  63. package/dist/subject-patch.js +63 -0
  64. package/dist/trace-files.d.ts +18 -0
  65. package/dist/trace-files.d.ts.map +1 -0
  66. package/dist/trace-files.js +94 -0
  67. package/environments/libreoffice-agent/Dockerfile +13 -0
  68. package/environments/libreoffice-python/Dockerfile +11 -0
  69. package/environments/node-22/Dockerfile +3 -0
  70. package/environments/python-3.12/Dockerfile +8 -0
  71. package/package.json +42 -0
  72. package/worker/sandbox-adapter-runner.cjs +275 -0
@@ -0,0 +1,656 @@
1
+ import { createHash } from "node:crypto";
2
+ import YAML from "yaml";
3
+ export const BENCHMARK_SPEC_FILE = "benchmark.yaml";
4
+ export const DEFAULT_EXECUTION_RESOURCES = {
5
+ cpu: 2,
6
+ memoryGb: 4,
7
+ diskGb: 10,
8
+ timeoutMinutes: 20,
9
+ };
10
+ export function validateWorkbenchResolvedSourceYaml(source) {
11
+ const errors = [];
12
+ const warnings = [];
13
+ const trimmed = source.trim();
14
+ if (!trimmed) {
15
+ errors.push("Resolved Workbench source cannot be empty.");
16
+ }
17
+ if (trimmed) {
18
+ try {
19
+ resolveWorkbenchResolvedSourceYaml(source);
20
+ }
21
+ catch (error) {
22
+ errors.push(...splitErrorMessage(error));
23
+ }
24
+ }
25
+ return {
26
+ ok: errors.length === 0,
27
+ errors,
28
+ warnings,
29
+ };
30
+ }
31
+ export function resolveWorkbenchResolvedSourceYaml(source) {
32
+ const parsed = parseYamlRecord(source, "resolved Workbench source");
33
+ const errors = [];
34
+ rejectUnknownKeys(parsed, "resolved Workbench source", [
35
+ "version",
36
+ "benchmark",
37
+ "subject",
38
+ "optimizer",
39
+ ], errors);
40
+ if (parsed.version !== 3) {
41
+ throw new Error("Resolved Workbench source version must be 3.");
42
+ }
43
+ const benchmark = normalizeBenchmarkRecord(readRequiredRecord(parsed.benchmark, "resolved Workbench source.benchmark", errors), "benchmark.yaml", errors);
44
+ const subject = normalizeSubjectRecord(readRequiredRecord(parsed.subject, "resolved Workbench source.subject", errors), "resolved Workbench source.subject", errors);
45
+ const optimizer = parsed.optimizer === undefined
46
+ ? undefined
47
+ : normalizeOptimizerRecord(readRequiredRecord(parsed.optimizer, "resolved Workbench source.optimizer", errors), "optimizer YAML", errors);
48
+ if (errors.length > 0) {
49
+ throw new Error(errors.join("\n"));
50
+ }
51
+ return genericSpecFromAuthoredBundle({
52
+ version: 3,
53
+ benchmark: benchmark,
54
+ subject: subject,
55
+ ...(optimizer ? { optimizer } : {}),
56
+ });
57
+ }
58
+ export function engineResolveBindingForSourceYaml(source) {
59
+ return engineResolveBindingForSpec(resolveWorkbenchResolvedSourceYaml(source));
60
+ }
61
+ export function engineResolveBindingForSpec(spec) {
62
+ const resolver = engineResolveInvocationForSpec(spec);
63
+ return {
64
+ engine: spec.benchmark.engine.use,
65
+ resolver: {
66
+ use: resolver.use,
67
+ withFingerprint: fingerprintJson(resolver.with ?? {}),
68
+ },
69
+ };
70
+ }
71
+ export function resolveWorkbenchSourceFiles(args) {
72
+ return genericSpecFromAuthoredBundle(parseWorkbenchSourceFiles({
73
+ benchmarkSource: args.benchmarkSource,
74
+ subjectSource: args.subjectSource,
75
+ optimizerSource: args.optimizerSource,
76
+ }));
77
+ }
78
+ export function parseWorkbenchSourceFiles(args) {
79
+ const errors = [];
80
+ const benchmark = normalizeBenchmarkRecord(parseYamlRecord(args.benchmarkSource, BENCHMARK_SPEC_FILE), BENCHMARK_SPEC_FILE, errors);
81
+ const subject = normalizeSubjectRecord(parseYamlRecord(args.subjectSource ?? "", "subject YAML"), "subject YAML", errors);
82
+ const optimizer = args.optimizerSource?.trim()
83
+ ? normalizeOptimizerRecord(parseYamlRecord(args.optimizerSource, "optimizer YAML"), "optimizer YAML", errors)
84
+ : undefined;
85
+ if (errors.length > 0) {
86
+ throw new Error(errors.join("\n"));
87
+ }
88
+ return {
89
+ version: 3,
90
+ benchmark: benchmark,
91
+ subject: subject,
92
+ ...(optimizer ? { optimizer } : {}),
93
+ };
94
+ }
95
+ export function serializeWorkbenchResolvedSourceYaml(source) {
96
+ return YAML.stringify(source).trimEnd() + "\n";
97
+ }
98
+ export function isWorkbenchSubjectManifestPath(filePath) {
99
+ return /^subjects\/[^/]+\/subject\.ya?ml$/iu.test(filePath.replace(/\\/gu, "/").replace(/^\/+/u, "").replace(/^(?:\.\/)+/u, ""));
100
+ }
101
+ export function resolveEngineCaseExecutionConfig(args) {
102
+ return {
103
+ prompt: args.engineCase.prompt,
104
+ environment: mergeRuntime(args.spec.environment, args.engineCase.environment),
105
+ run: args.spec.run,
106
+ };
107
+ }
108
+ export function engineResolveInvocationForSpec(spec) {
109
+ return spec.engineResolve;
110
+ }
111
+ export function engineCaseFilesForRuntimeInput(args) {
112
+ void args.spec;
113
+ return engineCaseSubjectVisibleFiles(args.engineCase);
114
+ }
115
+ export function engineCaseSubjectVisibleFiles(engineCase) {
116
+ return (engineCase.files.subjectVisible ?? [])
117
+ .map((file) => ({ ...file }))
118
+ .sort((left, right) => left.path.localeCompare(right.path));
119
+ }
120
+ export function engineCaseEnginePrivateFiles(engineCase) {
121
+ return (engineCase.files.enginePrivate ?? [])
122
+ .map((file) => ({ ...file }))
123
+ .sort((left, right) => left.path.localeCompare(right.path));
124
+ }
125
+ export function runtimeResources(runtime) {
126
+ const resources = runtime.resources ?? {};
127
+ return {
128
+ cpu: readPositiveNumber(resources.cpu, DEFAULT_EXECUTION_RESOURCES.cpu),
129
+ memoryGb: readPositiveNumber(resources.memoryGb, DEFAULT_EXECUTION_RESOURCES.memoryGb),
130
+ diskGb: readPositiveNumber(resources.diskGb, DEFAULT_EXECUTION_RESOURCES.diskGb),
131
+ timeoutMinutes: readPositiveNumber(resources.timeoutMinutes, DEFAULT_EXECUTION_RESOURCES.timeoutMinutes),
132
+ };
133
+ }
134
+ export function runtimeNetwork(runtime) {
135
+ return runtime.network ?? { egress: "open" };
136
+ }
137
+ export function runtimeSandboxRef(runtime) {
138
+ return `dockerfile://${runtime.dockerfile}`;
139
+ }
140
+ function genericSpecFromAuthoredBundle(source) {
141
+ const engineRuntime = engineRuntimeFromConfig(source.benchmark.engine);
142
+ const engineRun = cloneEngineInvocation(source.benchmark.engine);
143
+ const engineResolve = cloneEngineInvocation(source.benchmark.engine);
144
+ return {
145
+ version: 3,
146
+ name: source.benchmark.name,
147
+ description: source.benchmark.description,
148
+ benchmark: {
149
+ name: source.benchmark.name,
150
+ description: source.benchmark.description,
151
+ engine: cloneJson(source.benchmark.engine),
152
+ },
153
+ subject: {
154
+ name: source.subject.name,
155
+ ...(source.subject.description ? { description: source.subject.description } : {}),
156
+ files: cloneJson(source.subject.files),
157
+ },
158
+ ...(source.optimizer
159
+ ? {
160
+ optimizer: {
161
+ name: source.optimizer.name,
162
+ ...(source.optimizer.description ? { description: source.optimizer.description } : {}),
163
+ edits: [...source.optimizer.edits],
164
+ },
165
+ }
166
+ : {}),
167
+ environment: cloneJson(engineRuntime),
168
+ adapters: [
169
+ ...new Set([
170
+ ...source.benchmark.adapters,
171
+ ...source.subject.adapters,
172
+ ...(source.optimizer?.adapters ?? []),
173
+ ]),
174
+ ],
175
+ engine: cloneJson(source.benchmark.engine),
176
+ engineResolve: cloneJson(engineResolve),
177
+ ...(source.optimizer ? { improve: cloneJson(source.optimizer.improve) } : {}),
178
+ run: cloneJson(source.subject.run),
179
+ engineRun: cloneJson(engineRun),
180
+ };
181
+ }
182
+ function normalizeBenchmarkRecord(record, label, errors) {
183
+ if (!record) {
184
+ return null;
185
+ }
186
+ rejectUnknownKeys(record, label, [
187
+ "version",
188
+ "name",
189
+ "description",
190
+ "adapters",
191
+ "engine",
192
+ ], errors);
193
+ requireVersionThree(record.version, label, errors);
194
+ const name = readRequiredString(record.name, `${label}.name`, errors);
195
+ const description = readRequiredString(record.description, `${label}.description`, errors);
196
+ const adapters = normalizeAdapterSources(record.adapters, `${label}.adapters`, errors);
197
+ const engine = normalizePhaseAdapter(record.engine, `${label}.engine`, errors);
198
+ if (engine) {
199
+ normalizeEngineRuntimeConfig(engine, `${label}.engine.with`, errors);
200
+ }
201
+ return name && description && engine
202
+ ? {
203
+ version: 3,
204
+ name,
205
+ description,
206
+ adapters,
207
+ engine,
208
+ }
209
+ : null;
210
+ }
211
+ function normalizeEngineRuntimeConfig(engine, label, errors) {
212
+ const config = engine.with && typeof engine.with === "object" && !Array.isArray(engine.with)
213
+ ? engine.with
214
+ : {};
215
+ if (config.environment !== undefined) {
216
+ const environment = normalizeRuntime(config.environment, `${label}.environment`, errors);
217
+ if (environment) {
218
+ config.environment = environment;
219
+ engine.with = config;
220
+ }
221
+ }
222
+ }
223
+ function normalizeSubjectRecord(record, label, errors) {
224
+ if (!record) {
225
+ return null;
226
+ }
227
+ rejectUnknownKeys(record, label, [
228
+ "version",
229
+ "name",
230
+ "description",
231
+ "files",
232
+ "adapters",
233
+ "run",
234
+ ], errors);
235
+ requireVersionThree(record.version, label, errors);
236
+ const name = readRequiredString(record.name, `${label}.name`, errors);
237
+ const description = readOptionalString(record.description, `${label}.description`, errors);
238
+ const files = normalizePathRef(record.files, `${label}.files`, errors);
239
+ const adapters = normalizeAdapterSources(record.adapters, `${label}.adapters`, errors);
240
+ const run = normalizePhaseAdapter(record.run, `${label}.run`, errors);
241
+ return name && files && run
242
+ ? {
243
+ version: 3,
244
+ name,
245
+ ...(description ? { description } : {}),
246
+ files,
247
+ adapters,
248
+ run,
249
+ }
250
+ : null;
251
+ }
252
+ function normalizeOptimizerRecord(record, label, errors) {
253
+ if (!record) {
254
+ return null;
255
+ }
256
+ rejectUnknownKeys(record, label, [
257
+ "version",
258
+ "name",
259
+ "description",
260
+ "edits",
261
+ "adapters",
262
+ "improve",
263
+ ], errors);
264
+ requireVersionThree(record.version, label, errors);
265
+ const name = readRequiredString(record.name, `${label}.name`, errors);
266
+ const description = readOptionalString(record.description, `${label}.description`, errors);
267
+ const edits = normalizeRelativePathList(record.edits, `${label}.edits`, errors);
268
+ const adapters = normalizeAdapterSources(record.adapters, `${label}.adapters`, errors);
269
+ const improve = normalizePhaseAdapter(record.improve, `${label}.improve`, errors);
270
+ return name && edits.length > 0 && improve
271
+ ? {
272
+ version: 3,
273
+ name,
274
+ ...(description ? { description } : {}),
275
+ edits,
276
+ adapters,
277
+ improve,
278
+ }
279
+ : null;
280
+ }
281
+ function requireVersionThree(value, label, errors) {
282
+ if (value !== 3) {
283
+ errors.push(`${label}.version must be 3.`);
284
+ }
285
+ }
286
+ function normalizeRuntime(value, label, errors) {
287
+ const record = readRequiredRecord(value, label, errors);
288
+ if (!record) {
289
+ return null;
290
+ }
291
+ rejectUnknownKeys(record, label, ["dockerfile", "workdir", "resources", "network"], errors);
292
+ const dockerfile = normalizeWorkspaceLiteralPath(record.dockerfile, `${label}.dockerfile`, errors);
293
+ const runtime = {
294
+ dockerfile: dockerfile ?? "environment/Dockerfile",
295
+ };
296
+ const workdir = readOptionalString(record.workdir, `${label}.workdir`, errors);
297
+ if (workdir) {
298
+ runtime.workdir = workdir;
299
+ }
300
+ if (record.resources !== undefined) {
301
+ const resources = readRequiredRecord(record.resources, `${label}.resources`, errors);
302
+ if (resources) {
303
+ rejectUnknownKeys(resources, `${label}.resources`, [
304
+ "cpu",
305
+ "memoryGb",
306
+ "diskGb",
307
+ "timeoutMinutes",
308
+ ], errors);
309
+ readOptionalPositiveNumber(resources.cpu, `${label}.resources.cpu`, errors);
310
+ readOptionalPositiveNumber(resources.memoryGb, `${label}.resources.memoryGb`, errors);
311
+ readOptionalPositiveNumber(resources.diskGb, `${label}.resources.diskGb`, errors);
312
+ readOptionalPositiveNumber(resources.timeoutMinutes, `${label}.resources.timeoutMinutes`, errors);
313
+ runtime.resources = resources;
314
+ }
315
+ }
316
+ if (record.network !== undefined) {
317
+ const network = readRequiredRecord(record.network, `${label}.network`, errors);
318
+ if (network) {
319
+ const normalized = normalizeNetworkConfig(network, `${label}.network`, errors);
320
+ if (normalized) {
321
+ runtime.network = normalized;
322
+ }
323
+ }
324
+ }
325
+ return runtime;
326
+ }
327
+ function normalizeRuntimeOverride(value, label, errors) {
328
+ const record = readRequiredRecord(value, label, errors);
329
+ if (!record) {
330
+ return null;
331
+ }
332
+ rejectUnknownKeys(record, label, ["dockerfile", "workdir", "resources", "network"], errors);
333
+ const runtime = {};
334
+ if (record.dockerfile !== undefined) {
335
+ const dockerfile = normalizeWorkspaceLiteralPath(record.dockerfile, `${label}.dockerfile`, errors);
336
+ if (dockerfile) {
337
+ runtime.dockerfile = dockerfile;
338
+ }
339
+ }
340
+ const workdir = readOptionalString(record.workdir, `${label}.workdir`, errors);
341
+ if (workdir) {
342
+ runtime.workdir = workdir;
343
+ }
344
+ if (record.resources !== undefined) {
345
+ const resources = readRequiredRecord(record.resources, `${label}.resources`, errors);
346
+ if (resources) {
347
+ rejectUnknownKeys(resources, `${label}.resources`, [
348
+ "cpu",
349
+ "memoryGb",
350
+ "diskGb",
351
+ "timeoutMinutes",
352
+ ], errors);
353
+ readOptionalPositiveNumber(resources.cpu, `${label}.resources.cpu`, errors);
354
+ readOptionalPositiveNumber(resources.memoryGb, `${label}.resources.memoryGb`, errors);
355
+ readOptionalPositiveNumber(resources.diskGb, `${label}.resources.diskGb`, errors);
356
+ readOptionalPositiveNumber(resources.timeoutMinutes, `${label}.resources.timeoutMinutes`, errors);
357
+ runtime.resources = resources;
358
+ }
359
+ }
360
+ if (record.network !== undefined) {
361
+ const network = readRequiredRecord(record.network, `${label}.network`, errors);
362
+ if (network) {
363
+ const normalized = normalizeNetworkConfig(network, `${label}.network`, errors);
364
+ if (normalized) {
365
+ runtime.network = normalized;
366
+ }
367
+ }
368
+ }
369
+ return Object.keys(runtime).length > 0 ? runtime : null;
370
+ }
371
+ function engineRuntimeFromConfig(engine) {
372
+ const config = engine.with && typeof engine.with === "object" && !Array.isArray(engine.with)
373
+ ? engine.with
374
+ : {};
375
+ const environment = config.environment;
376
+ if (environment && typeof environment === "object" && !Array.isArray(environment)) {
377
+ const record = environment;
378
+ return {
379
+ dockerfile: typeof record.dockerfile === "string" && record.dockerfile.trim()
380
+ ? record.dockerfile
381
+ : "environment/Dockerfile",
382
+ ...(typeof record.workdir === "string" && record.workdir.trim() ? { workdir: record.workdir } : {}),
383
+ ...(record.resources && typeof record.resources === "object" && !Array.isArray(record.resources)
384
+ ? { resources: record.resources }
385
+ : {}),
386
+ ...(record.network && typeof record.network === "object" && !Array.isArray(record.network)
387
+ ? { network: record.network }
388
+ : {}),
389
+ };
390
+ }
391
+ return {
392
+ dockerfile: "environment/Dockerfile",
393
+ network: { egress: "open" },
394
+ resources: {
395
+ cpu: DEFAULT_EXECUTION_RESOURCES.cpu,
396
+ memoryGb: DEFAULT_EXECUTION_RESOURCES.memoryGb,
397
+ diskGb: DEFAULT_EXECUTION_RESOURCES.diskGb,
398
+ timeoutMinutes: DEFAULT_EXECUTION_RESOURCES.timeoutMinutes,
399
+ },
400
+ };
401
+ }
402
+ function cloneEngineInvocation(engine) {
403
+ return {
404
+ use: engine.use,
405
+ with: cloneJson(engine.with ?? {}),
406
+ ...(engine.auth !== undefined ? { auth: cloneJson(engine.auth) } : {}),
407
+ };
408
+ }
409
+ function mergeRuntime(base, override) {
410
+ if (!override) {
411
+ return cloneJson(base);
412
+ }
413
+ return {
414
+ ...cloneJson(base),
415
+ ...cloneJson(override),
416
+ resources: {
417
+ ...(base.resources ?? {}),
418
+ ...(override.resources ?? {}),
419
+ },
420
+ network: override.network ?? base.network,
421
+ };
422
+ }
423
+ function normalizeAdapterSources(value, label, errors) {
424
+ if (value === undefined) {
425
+ return [];
426
+ }
427
+ if (!Array.isArray(value)) {
428
+ errors.push(`${label} must be an array of adapter sources.`);
429
+ return [];
430
+ }
431
+ const sources = value.flatMap((entry, index) => {
432
+ if (typeof entry !== "string" || entry.trim().length === 0) {
433
+ errors.push(`${label}[${index}] must be a non-empty string.`);
434
+ return [];
435
+ }
436
+ return [entry.trim()];
437
+ });
438
+ return [...new Set(sources)];
439
+ }
440
+ function normalizeNetworkConfig(network, label, errors) {
441
+ rejectUnknownKeys(network, label, ["egress", "allow"], errors);
442
+ const egress = readOptionalString(network.egress, `${label}.egress`, errors) ?? "open";
443
+ if (egress !== "none" && egress !== "open" && egress !== "allowlist") {
444
+ errors.push(`${label}.egress must be none, open, or allowlist.`);
445
+ return null;
446
+ }
447
+ const allow = network.allow === undefined
448
+ ? undefined
449
+ : normalizeNetworkAllowList(network.allow, `${label}.allow`, errors);
450
+ if (egress !== "allowlist") {
451
+ if (network.allow !== undefined) {
452
+ errors.push(`${label}.allow is only supported when ${label}.egress is allowlist.`);
453
+ }
454
+ return { egress };
455
+ }
456
+ if (!allow || allow.length === 0) {
457
+ errors.push(`${label}.allow must contain at least one host when ${label}.egress is allowlist.`);
458
+ }
459
+ return {
460
+ egress,
461
+ ...(allow && allow.length > 0 ? { allow } : {}),
462
+ };
463
+ }
464
+ function normalizeNetworkAllowList(value, label, errors) {
465
+ if (!Array.isArray(value)) {
466
+ errors.push(`${label} must be an array of hosts.`);
467
+ return [];
468
+ }
469
+ return value.flatMap((entry, index) => {
470
+ if (typeof entry !== "string" || entry.trim().length === 0) {
471
+ errors.push(`${label}[${index}] must be a non-empty string.`);
472
+ return [];
473
+ }
474
+ return [entry.trim()];
475
+ });
476
+ }
477
+ function normalizePhaseAdapter(value, label, errors) {
478
+ const spec = readAdapterRecord(value, label, errors);
479
+ if (!spec) {
480
+ return null;
481
+ }
482
+ return {
483
+ use: spec.use,
484
+ with: readJsonRecord(spec.with ?? {}),
485
+ ...(spec.auth !== undefined ? { auth: spec.auth } : {}),
486
+ };
487
+ }
488
+ function normalizeWorkspaceLiteralPath(value, label, errors) {
489
+ const raw = readRequiredString(value, label, errors);
490
+ if (!raw) {
491
+ return null;
492
+ }
493
+ return normalizeLiteralPathString(raw, label, errors);
494
+ }
495
+ function normalizePathRef(value, label, errors) {
496
+ const record = readRequiredRecord(value, label, errors);
497
+ if (!record) {
498
+ return null;
499
+ }
500
+ rejectUnknownKeys(record, label, ["path"], errors);
501
+ const refPath = normalizeWorkspaceLiteralPath(record.path, `${label}.path`, errors);
502
+ return refPath ? { path: refPath } : null;
503
+ }
504
+ function normalizeRelativePathList(value, label, errors) {
505
+ if (!Array.isArray(value) || value.length === 0) {
506
+ errors.push(`${label} must include at least one path.`);
507
+ return [];
508
+ }
509
+ const paths = value.flatMap((entry, index) => {
510
+ if (typeof entry !== "string" || entry.trim().length === 0) {
511
+ errors.push(`${label}[${index}] must be a non-empty string.`);
512
+ return [];
513
+ }
514
+ const normalized = normalizeLiteralPathString(entry, `${label}[${index}]`, errors);
515
+ return normalized ? [normalized] : [];
516
+ });
517
+ return [...new Set(paths)];
518
+ }
519
+ function normalizeLiteralPathString(value, label, errors) {
520
+ const trimmed = value.trim();
521
+ if (/^(?:\/|[A-Za-z]:[\\/])/u.test(trimmed)) {
522
+ errors.push(`${label} must be a relative path, not an absolute path.`);
523
+ return null;
524
+ }
525
+ const normalized = trimmed.replace(/\\/gu, "/");
526
+ if (!normalized || normalized.includes("\0")) {
527
+ errors.push(`${label} must be a non-empty relative path.`);
528
+ return null;
529
+ }
530
+ if (/[*?]/u.test(normalized)) {
531
+ errors.push(`${label} must be a literal path, not a glob.`);
532
+ return null;
533
+ }
534
+ const parts = normalized.split("/");
535
+ if (parts.some((part) => part === ".." || part === "." || part === "")) {
536
+ errors.push(`${label} must not contain empty, '.', or '..' path segments.`);
537
+ return null;
538
+ }
539
+ return normalized;
540
+ }
541
+ function parseYamlRecord(source, label = "YAML") {
542
+ const document = YAML.parseDocument(source, { prettyErrors: true });
543
+ if (document.errors.length > 0) {
544
+ throw new Error(document.errors.map((error) => `${label}: ${error.message}`).join("\n"));
545
+ }
546
+ const parsed = document.toJS();
547
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
548
+ throw new Error(`${label} must be a YAML object.`);
549
+ }
550
+ return parsed;
551
+ }
552
+ function readAdapterRecord(value, label, errors) {
553
+ const record = readRequiredRecord(value, label, errors);
554
+ if (!record) {
555
+ return undefined;
556
+ }
557
+ rejectUnknownKeys(record, label, ["use", "with", "auth"], errors);
558
+ const use = readRequiredString(record.use, `${label}.use`, errors);
559
+ const config = record.with === undefined
560
+ ? {}
561
+ : readRequiredRecord(record.with, `${label}.with`, errors);
562
+ return use
563
+ ? {
564
+ use,
565
+ ...(config ? { with: config } : {}),
566
+ ...(record.auth !== undefined && isJson(record.auth) ? { auth: record.auth } : {}),
567
+ }
568
+ : undefined;
569
+ }
570
+ function rejectUnknownKeys(record, label, allowed, errors) {
571
+ const extras = Object.keys(record).filter((key) => !allowed.includes(key));
572
+ if (extras.length > 0) {
573
+ errors.push(`${label} includes unsupported ${extras.length === 1 ? "field" : "fields"}: ${extras.join(", ")}.`);
574
+ }
575
+ }
576
+ function readRequiredRecord(value, label, errors) {
577
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
578
+ errors.push(`${label} must be an object.`);
579
+ return null;
580
+ }
581
+ return value;
582
+ }
583
+ function readRequiredString(value, label, errors) {
584
+ if (typeof value !== "string" || value.trim().length === 0) {
585
+ errors.push(`${label} must be a non-empty string.`);
586
+ return null;
587
+ }
588
+ return value.trim();
589
+ }
590
+ function readOptionalString(value, label, errors) {
591
+ if (value === undefined) {
592
+ return undefined;
593
+ }
594
+ if (typeof value !== "string" || value.trim().length === 0) {
595
+ errors.push(`${label} must be a non-empty string when provided.`);
596
+ return undefined;
597
+ }
598
+ return value.trim();
599
+ }
600
+ function readOptionalPositiveNumber(value, label, errors) {
601
+ if (value === undefined) {
602
+ return;
603
+ }
604
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
605
+ errors.push(`${label} must be a positive number.`);
606
+ }
607
+ }
608
+ function readPositiveNumber(value, fallback) {
609
+ return typeof value === "number" && Number.isFinite(value) && value > 0
610
+ ? value
611
+ : fallback;
612
+ }
613
+ function readJsonRecord(value) {
614
+ if (!isJson(value)) {
615
+ return {};
616
+ }
617
+ return value;
618
+ }
619
+ function isJson(value) {
620
+ if (value === null) {
621
+ return true;
622
+ }
623
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
624
+ return Number.isFinite(value) || typeof value !== "number";
625
+ }
626
+ if (Array.isArray(value)) {
627
+ return value.every(isJson);
628
+ }
629
+ if (typeof value === "object") {
630
+ return Object.values(value).every(isJson);
631
+ }
632
+ return false;
633
+ }
634
+ function fingerprintJson(value) {
635
+ return createHash("sha256")
636
+ .update(JSON.stringify(canonicalJson(value)))
637
+ .digest("hex");
638
+ }
639
+ function canonicalJson(value) {
640
+ if (Array.isArray(value)) {
641
+ return value.map(canonicalJson);
642
+ }
643
+ if (value && typeof value === "object") {
644
+ return Object.fromEntries(Object.entries(value)
645
+ .sort(([left], [right]) => left.localeCompare(right))
646
+ .map(([key, entry]) => [key, canonicalJson(entry)]));
647
+ }
648
+ return value;
649
+ }
650
+ function splitErrorMessage(error) {
651
+ const message = error instanceof Error ? error.message : String(error);
652
+ return message.split(/\n+/u).map((entry) => entry.trim()).filter(Boolean);
653
+ }
654
+ function cloneJson(value) {
655
+ return JSON.parse(JSON.stringify(value));
656
+ }