@workbench-ai/workbench-core 0.0.67 → 0.0.69

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