@workbench-ai/workbench 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 (39) hide show
  1. package/dist/adapter-project.d.ts +29 -0
  2. package/dist/adapter-project.d.ts.map +1 -0
  3. package/dist/adapter-project.js +363 -0
  4. package/dist/benchmark-fingerprint.d.ts +6 -0
  5. package/dist/benchmark-fingerprint.d.ts.map +1 -0
  6. package/dist/benchmark-fingerprint.js +101 -0
  7. package/dist/command-model.d.ts +5 -0
  8. package/dist/command-model.d.ts.map +1 -0
  9. package/dist/command-model.js +558 -0
  10. package/dist/dev-open/client.css +8157 -0
  11. package/dist/dev-open/client.js +252596 -0
  12. package/dist/dev-open/fonts/geist-cyrillic-wght-normal.woff2 +0 -0
  13. package/dist/dev-open/fonts/geist-latin-ext-wght-normal.woff2 +0 -0
  14. package/dist/dev-open/fonts/geist-latin-wght-normal.woff2 +0 -0
  15. package/dist/dev-open-server.d.ts +57 -0
  16. package/dist/dev-open-server.d.ts.map +1 -0
  17. package/dist/dev-open-server.js +496 -0
  18. package/dist/index.d.ts +10 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +3943 -0
  21. package/dist/init-scaffold.d.ts +22 -0
  22. package/dist/init-scaffold.d.ts.map +1 -0
  23. package/dist/init-scaffold.js +30 -0
  24. package/dist/init-template-pack.d.ts +19 -0
  25. package/dist/init-template-pack.d.ts.map +1 -0
  26. package/dist/init-template-pack.js +250 -0
  27. package/dist/local-archive.d.ts +23 -0
  28. package/dist/local-archive.d.ts.map +1 -0
  29. package/dist/local-archive.js +741 -0
  30. package/dist/project-source.d.ts +51 -0
  31. package/dist/project-source.d.ts.map +1 -0
  32. package/dist/project-source.js +700 -0
  33. package/dist/workbench.d.ts +3 -0
  34. package/dist/workbench.d.ts.map +1 -0
  35. package/dist/workbench.js +4 -0
  36. package/dist/workspace-snapshot.d.ts +10 -0
  37. package/dist/workspace-snapshot.d.ts.map +1 -0
  38. package/dist/workspace-snapshot.js +81 -0
  39. package/package.json +45 -0
@@ -0,0 +1,700 @@
1
+ import { promises as fs } from "node:fs";
2
+ import { createHash, randomUUID } from "node:crypto";
3
+ import { spawn } from "node:child_process";
4
+ import path from "node:path";
5
+ import { BENCHMARK_SPEC_FILE, buildWorkbenchProjectSourceFiles, engineResolveInvocationForSpec, normalizeSurfaceFiles, parseWorkbenchSourceFiles, resolveWorkbenchResolvedSourceYaml, serializeWorkbenchResolvedSourceYaml, validateWorkbenchResolvedSourceYaml, } from "@workbench-ai/workbench-core";
6
+ import { assertWorkbenchAdapterOperationSupport, assertWorkbenchAdapterOperationResultOk, collectWorkbenchAdapterInvocations, readWorkbenchAdapterOperationResult, workbenchAdapterOperationCommand, workbenchAdapterOperationResultPath, } from "@workbench-ai/workbench-protocol";
7
+ import { readSnapshotFiles, WorkspaceSnapshotError, } from "./workspace-snapshot.js";
8
+ import { defaultAdapterManifests, composeRuntimeDockerfileWithAdapters, resolveDefaultWorkbenchAdapter, resolveProjectAdapterSource, resolveWorkbenchAdaptersForProject, } from "./adapter-project.js";
9
+ import YAML from "yaml";
10
+ export const WORKBENCH_BENCHMARK_FILE = BENCHMARK_SPEC_FILE;
11
+ export const WORKBENCH_SUBJECTS_DIR = "subjects";
12
+ export const WORKBENCH_OPTIMIZERS_DIR = "optimizers";
13
+ export const WORKBENCH_SUBJECT_FILE = "subject.yaml";
14
+ function rootAdapterInvocations(spec) {
15
+ return [
16
+ engineResolveInvocationForSpec(spec),
17
+ spec.engineRun,
18
+ spec.run,
19
+ ...(spec.improve ? [spec.improve] : []),
20
+ ];
21
+ }
22
+ export async function readLocalProjectSource(source, options = {}) {
23
+ const paths = await resolveLocalProjectSourcePaths(source, options);
24
+ const { dir, benchmarkPath, subjectSpecPath, subjectDir, optimizerPath, } = paths;
25
+ const benchmarkSource = await readRequiredTextFile(benchmarkPath, WORKBENCH_BENCHMARK_FILE);
26
+ const subjectSource = await readRequiredTextFile(subjectSpecPath, "subject YAML");
27
+ const optimizerSource = optimizerPath
28
+ ? await readRequiredTextFile(optimizerPath, "optimizer YAML")
29
+ : undefined;
30
+ const normalizedSources = await normalizeSourceYamlForExecution({
31
+ dir,
32
+ benchmarkPath,
33
+ benchmarkSource,
34
+ subjectSpecPath,
35
+ subjectSource,
36
+ optimizerPath,
37
+ optimizerSource,
38
+ });
39
+ const resolvedSource = parseWorkbenchSourceFiles({
40
+ benchmarkSource: normalizedSources.benchmarkSource,
41
+ subjectSource: normalizedSources.subjectSource,
42
+ optimizerSource: normalizedSources.optimizerSource,
43
+ });
44
+ const specSource = serializeWorkbenchResolvedSourceYaml(resolvedSource);
45
+ const validation = validateWorkbenchResolvedSourceYaml(specSource);
46
+ if (!validation.ok) {
47
+ throw new WorkspaceSnapshotError(`Benchmark source is invalid:\n${validation.errors.map((entry) => `- ${entry}`).join("\n")}`);
48
+ }
49
+ const spec = resolveWorkbenchResolvedSourceYaml(specSource);
50
+ const dockerfilePath = spec.environment.dockerfile;
51
+ const dockerfileSources = await readDockerfileSourcesForSpec(dir, spec, normalizedSources.engineCases);
52
+ const dockerfile = dockerfileSources.get(dockerfilePath);
53
+ if (dockerfile === undefined) {
54
+ throw new WorkspaceSnapshotError(`Dockerfile not found: ${resolveProjectPath(dir, dockerfilePath)}`);
55
+ }
56
+ const adapters = await resolveWorkbenchAdaptersForProject(dir, spec);
57
+ const benchmarkAdapterIds = [
58
+ ...new Set(collectWorkbenchAdapterInvocations(rootAdapterInvocations(spec), adapters.map((adapter) => adapter.manifest)).map((invocation) => invocation.use)),
59
+ ];
60
+ const composedDockerfile = await composeRuntimeDockerfileWithAdapters(dockerfile, adapters);
61
+ const adapterFiles = adapterSourceFiles(adapters);
62
+ const absoluteSubjectFilesPath = resolveProjectPath(dir, spec.subject.files.path);
63
+ const subjectFilesPath = absoluteSubjectFilesPath;
64
+ const subjectFiles = await directoryExists(absoluteSubjectFilesPath)
65
+ ? normalizeSurfaceFiles(await readSnapshotFiles(absoluteSubjectFilesPath))
66
+ : [];
67
+ const rawEngineResolveFiles = engineResolveFilesFromBundles(normalizedSources.engineCases);
68
+ const engineResolveFiles = toHostedFiles(rawEngineResolveFiles);
69
+ const engineCases = normalizedSources.engineCases;
70
+ if (engineCases.length === 0) {
71
+ throw new WorkspaceSnapshotError(`Engine resolver ${normalizedSources.engineResolve.use} did not emit any cases.`);
72
+ }
73
+ const caseIds = engineCases.map((bundle) => bundle.id);
74
+ return {
75
+ dir,
76
+ specPath: benchmarkPath,
77
+ specSource,
78
+ spec,
79
+ benchmarkPath,
80
+ benchmarkSource,
81
+ subjectName: path.basename(subjectDir),
82
+ subjectDir,
83
+ subjectFilesPath,
84
+ subjectSpecPath,
85
+ subjectSource,
86
+ ...(optimizerSource !== undefined && optimizerPath ? { optimizerPath, optimizerSource } : {}),
87
+ benchmarkAdapterSources: [...resolvedSource.benchmark.adapters],
88
+ benchmarkAdapterIds,
89
+ dockerfilePath,
90
+ dockerfile,
91
+ runtimeDockerfile: composedDockerfile,
92
+ dockerfileFiles: toHostedFiles(dockerfileSourceFiles(dockerfileSources)),
93
+ subjectFiles: toHostedFiles(subjectFiles),
94
+ engineResolveFiles,
95
+ adapters,
96
+ adapterFiles: toHostedFiles(adapterFiles),
97
+ caseIds,
98
+ engineCases,
99
+ engineResolve: normalizedSources.engineResolve,
100
+ engineResolveFingerprintPath: normalizedSources.engineResolveFingerprintPath,
101
+ ...(normalizedSources.engineResolveEnvironment
102
+ ? { engineResolveEnvironment: normalizedSources.engineResolveEnvironment }
103
+ : {}),
104
+ sourceFiles: buildWorkbenchProjectSourceFiles({
105
+ specFiles: [
106
+ textSourceFile(toRootRelativePath(dir, benchmarkPath), benchmarkSource),
107
+ textSourceFile(toRootRelativePath(dir, subjectSpecPath), subjectSource),
108
+ ...(optimizerSource !== undefined && optimizerPath
109
+ ? [textSourceFile(toRootRelativePath(dir, optimizerPath), optimizerSource)]
110
+ : []),
111
+ ],
112
+ subjectFilesPath: spec.subject.files.path,
113
+ subjectFiles,
114
+ engineResolveFilesPath: normalizedSources.engineResolveFingerprintPath,
115
+ engineResolveFiles: rawEngineResolveFiles,
116
+ adapterFiles,
117
+ dockerfiles: dockerfileSourceFiles(dockerfileSources),
118
+ }),
119
+ };
120
+ }
121
+ async function resolveLocalProjectSourcePaths(source, options) {
122
+ const resolved = path.resolve(source);
123
+ const stat = await fs.stat(resolved).catch(() => null);
124
+ if (stat?.isFile()) {
125
+ const sourceRecord = await readYamlRecordFile(resolved);
126
+ if (isSubjectSourceRecord(sourceRecord)) {
127
+ const subjectDir = path.dirname(resolved);
128
+ const dir = projectRootForSubjectDir(subjectDir);
129
+ return {
130
+ dir,
131
+ benchmarkPath: path.join(dir, WORKBENCH_BENCHMARK_FILE),
132
+ subjectDir,
133
+ subjectSpecPath: resolved,
134
+ optimizerPath: await resolveOptimizerPath(dir, options.optimizerPath, path.basename(subjectDir)),
135
+ };
136
+ }
137
+ if (isBenchmarkSourceRecord(sourceRecord)) {
138
+ const dir = path.dirname(resolved);
139
+ const subjectPaths = await resolveSubjectPaths(dir);
140
+ return {
141
+ dir,
142
+ benchmarkPath: resolved,
143
+ ...subjectPaths,
144
+ optimizerPath: await resolveOptimizerPath(dir, options.optimizerPath, path.basename(subjectPaths.subjectDir)),
145
+ };
146
+ }
147
+ if (isOptimizerSourceRecord(sourceRecord)) {
148
+ throw new WorkspaceSnapshotError(`Optimizer source must be passed with --optimizer; pass a source directory or subject YAML as SOURCE: ${resolved}`);
149
+ }
150
+ throw new WorkspaceSnapshotError(`Unsupported Workbench YAML source: ${resolved}`);
151
+ }
152
+ const dir = resolved;
153
+ const directorySubject = await subjectPathsForSubjectDirectory(dir);
154
+ if (directorySubject) {
155
+ return {
156
+ ...directorySubject,
157
+ optimizerPath: await resolveOptimizerPath(directorySubject.dir, options.optimizerPath, path.basename(directorySubject.subjectDir)),
158
+ };
159
+ }
160
+ const subjectPaths = await resolveSubjectPathsWithOptimizer(dir, options.optimizerPath);
161
+ return {
162
+ dir,
163
+ benchmarkPath: path.join(dir, WORKBENCH_BENCHMARK_FILE),
164
+ ...subjectPaths,
165
+ };
166
+ }
167
+ async function resolveSubjectPathsWithOptimizer(dir, explicitOptimizerPath) {
168
+ const subjectPaths = await resolveSubjectPaths(dir);
169
+ return {
170
+ ...subjectPaths,
171
+ optimizerPath: await resolveOptimizerPath(dir, explicitOptimizerPath, path.basename(subjectPaths.subjectDir)),
172
+ };
173
+ }
174
+ async function resolveSubjectPaths(dir) {
175
+ const subjectsDir = path.join(dir, WORKBENCH_SUBJECTS_DIR);
176
+ const subjects = await listSubjectManifestFiles(subjectsDir);
177
+ if (subjects.length === 1) {
178
+ const subjectSpecPath = subjects[0];
179
+ const subjectDir = path.dirname(subjectSpecPath);
180
+ return {
181
+ subjectDir,
182
+ subjectSpecPath,
183
+ };
184
+ }
185
+ if (subjects.length > 1) {
186
+ throw new WorkspaceSnapshotError(`Multiple subject directories found under ${subjectsDir}; pass subjects/NAME or subjects/NAME/subject.yaml explicitly.`);
187
+ }
188
+ throw new WorkspaceSnapshotError(`No subject directories found under ${subjectsDir}; create subjects/NAME/subject.yaml with files.path.`);
189
+ }
190
+ async function resolveOptimizerPath(dir, explicit, subjectName) {
191
+ if (explicit) {
192
+ return path.resolve(dir, explicit);
193
+ }
194
+ if (subjectName) {
195
+ const named = path.join(dir, WORKBENCH_OPTIMIZERS_DIR, `${subjectName}.yaml`);
196
+ if (await fileExists(named)) {
197
+ return named;
198
+ }
199
+ }
200
+ const optimizersDir = path.join(dir, WORKBENCH_OPTIMIZERS_DIR);
201
+ const optimizers = await listYamlFiles(optimizersDir);
202
+ if (optimizers.length === 1) {
203
+ return optimizers[0];
204
+ }
205
+ return undefined;
206
+ }
207
+ async function normalizeSourceYamlForExecution(args) {
208
+ const benchmark = parseYamlRecord(args.benchmarkSource, args.benchmarkPath);
209
+ const subject = parseYamlRecord(args.subjectSource, args.subjectSpecPath);
210
+ const optimizer = args.optimizerSource === undefined || args.optimizerPath === undefined
211
+ ? undefined
212
+ : parseYamlRecord(args.optimizerSource, args.optimizerPath);
213
+ const benchmarkDir = path.dirname(args.benchmarkPath);
214
+ const subjectDir = path.dirname(args.subjectSpecPath);
215
+ normalizeAdapterSourcePaths(args.dir, benchmark, benchmarkDir);
216
+ normalizeAdapterSourcePaths(args.dir, subject, subjectDir);
217
+ if (optimizer && args.optimizerPath) {
218
+ normalizeAdapterSourcePaths(args.dir, optimizer, path.dirname(args.optimizerPath));
219
+ }
220
+ const engine = yamlRecord(benchmark.engine);
221
+ if (!engine || typeof engine.use !== "string" || !engine.use.trim()) {
222
+ throw new WorkspaceSnapshotError("benchmark.yaml engine must declare an adapter invocation with use.");
223
+ }
224
+ normalizeEngineForExecution(args.dir, benchmarkDir, subjectDir, benchmark, subject);
225
+ const authoredEngineResolve = engineResolveInvocationFromRecord(engine);
226
+ const engineResolve = await resolveEngineResolveAdapter({
227
+ root: args.dir,
228
+ yamlDir: benchmarkDir,
229
+ benchmark,
230
+ value: authoredEngineResolve,
231
+ });
232
+ const engineResolveFingerprintPath = engineResolve.sourcePath
233
+ ?? engineResolvePathForInvocation(args.dir, benchmarkDir, authoredEngineResolve);
234
+ applyEngineResolveEnvironment(benchmark, engineResolve.environment);
235
+ return {
236
+ benchmarkSource: YAML.stringify(benchmark).trimEnd() + "\n",
237
+ subjectSource: YAML.stringify(subject).trimEnd() + "\n",
238
+ engineResolveFingerprintPath,
239
+ engineResolve: authoredEngineResolve,
240
+ ...(engineResolve.environment ? { engineResolveEnvironment: engineResolve.environment } : {}),
241
+ engineCases: engineResolve.engineCases,
242
+ ...(optimizer
243
+ ? { optimizerSource: YAML.stringify(optimizer).trimEnd() + "\n" }
244
+ : {}),
245
+ };
246
+ }
247
+ function normalizeEngineForExecution(root, benchmarkDir, subjectDir, benchmark, subject) {
248
+ const subjectFiles = yamlRecord(subject.files);
249
+ if (subjectFiles && typeof subjectFiles.path === "string") {
250
+ subjectFiles.path = toRootRelativePath(root, resolveYamlReference(subjectDir, subjectFiles.path));
251
+ subject.files = subjectFiles;
252
+ }
253
+ const engine = yamlRecord(benchmark.engine);
254
+ const engineConfig = yamlRecord(engine?.with) ?? {};
255
+ const environment = yamlRecord(engineConfig.environment);
256
+ if (environment && typeof environment.dockerfile === "string") {
257
+ environment.dockerfile = toRootRelativePath(root, resolveYamlReference(benchmarkDir, environment.dockerfile));
258
+ engineConfig.environment = environment;
259
+ }
260
+ if (engine) {
261
+ engine.with = engineConfig;
262
+ benchmark.engine = engine;
263
+ }
264
+ }
265
+ function applyEngineResolveEnvironment(benchmark, environment) {
266
+ if (!environment) {
267
+ return;
268
+ }
269
+ const engine = yamlRecord(benchmark.engine);
270
+ const engineConfig = yamlRecord(engine?.with) ?? {};
271
+ if (!yamlRecord(engineConfig.environment)) {
272
+ engineConfig.environment = environment;
273
+ if (engine) {
274
+ engine.with = engineConfig;
275
+ benchmark.engine = engine;
276
+ }
277
+ }
278
+ }
279
+ function engineResolveInvocationFromRecord(record) {
280
+ return {
281
+ use: record.use,
282
+ with: cloneJson((record.with ?? {})),
283
+ ...(record.auth !== undefined ? { auth: cloneJson(record.auth) } : {}),
284
+ };
285
+ }
286
+ function cloneJson(value) {
287
+ return JSON.parse(JSON.stringify(value));
288
+ }
289
+ function engineResolvePathForInvocation(root, yamlDir, declaration) {
290
+ void root;
291
+ void yamlDir;
292
+ return `engine-resolve/${safePathSegment(String(declaration.use))}`;
293
+ }
294
+ function normalizeAdapterSourcePaths(root, record, yamlDir) {
295
+ if (!Array.isArray(record.adapters)) {
296
+ return;
297
+ }
298
+ record.adapters = record.adapters.map((entry) => typeof entry === "string" && isPathAdapterSource(entry)
299
+ ? toRootRelativePath(root, resolveYamlReference(yamlDir, entry))
300
+ : entry);
301
+ }
302
+ async function resolveEngineResolveAdapter(args) {
303
+ const record = yamlRecord(args.value);
304
+ if (!record || typeof record.use !== "string") {
305
+ throw new WorkspaceSnapshotError("benchmark.yaml engine must be an adapter invocation.");
306
+ }
307
+ const adapter = await resolveEngineResolveAdapterReference({
308
+ root: args.root,
309
+ benchmark: args.benchmark,
310
+ adapterId: record.use,
311
+ });
312
+ await assertEngineResolveAdapterOperations({
313
+ root: args.root,
314
+ benchmark: args.benchmark,
315
+ invocation: engineResolveInvocationFromRecord(record),
316
+ });
317
+ const digest = createHash("sha256")
318
+ .update(JSON.stringify({
319
+ adapter: adapter.contentHash,
320
+ invocation: record,
321
+ cwd: path.resolve(args.yamlDir),
322
+ }))
323
+ .digest("hex")
324
+ .slice(0, 16);
325
+ const generatedRoot = path.join(args.root, ".workbench", "generated", "engine-resolves", safePathSegment(record.use), `${digest}-${randomUUID().replace(/-/gu, "").slice(0, 12)}`);
326
+ await fs.mkdir(generatedRoot, { recursive: true });
327
+ const requestPath = path.join(generatedRoot, ".workbench", "request.json");
328
+ await fs.mkdir(path.dirname(requestPath), { recursive: true });
329
+ await fs.writeFile(requestPath, `${JSON.stringify({
330
+ protocol: "workbench.adapter.v3",
331
+ id: `engine_resolve_${digest}`,
332
+ operation: "engine.resolve",
333
+ invocation: {
334
+ use: record.use,
335
+ with: record.with ?? {},
336
+ ...(record.auth !== undefined ? { auth: record.auth } : {}),
337
+ },
338
+ paths: {
339
+ workspace: args.root,
340
+ cwd: args.yamlDir,
341
+ output: generatedRoot,
342
+ result: workbenchAdapterOperationResultPath(generatedRoot),
343
+ },
344
+ }, null, 2)}\n`);
345
+ await executeEngineResolveAdapter({
346
+ adapter,
347
+ requestPath,
348
+ outputRoot: generatedRoot,
349
+ workspaceRoot: args.root,
350
+ });
351
+ const result = await readEngineResolveResult(generatedRoot, record.use);
352
+ const environment = normalizeEngineResolveEnvironment(args.root, result.value.environment);
353
+ const sourcePath = engineResolveSourcePathFromFeedback(args.root, args.yamlDir, result.feedback);
354
+ return {
355
+ engineCases: normalizeEngineCaseEnvironments(args.root, result.value.cases),
356
+ ...(sourcePath ? { sourcePath } : {}),
357
+ ...(environment ? { environment } : {}),
358
+ };
359
+ }
360
+ async function assertEngineResolveAdapterOperations(args) {
361
+ const manifests = await resolveBenchmarkAdapterManifests(args.root, args.benchmark);
362
+ try {
363
+ assertWorkbenchAdapterOperationSupport([{ invocation: args.invocation, operation: "engine.resolve" }], manifests);
364
+ }
365
+ catch (error) {
366
+ throw new WorkspaceSnapshotError(error instanceof Error ? error.message : String(error));
367
+ }
368
+ }
369
+ async function resolveBenchmarkAdapterManifests(root, benchmark) {
370
+ const manifests = new Map(defaultAdapterManifests().map((manifest) => [manifest.id, manifest]));
371
+ const sources = Array.isArray(benchmark.adapters)
372
+ ? benchmark.adapters.filter((entry) => typeof entry === "string")
373
+ : [];
374
+ for (const source of sources) {
375
+ const adapter = await resolveProjectAdapterSource(root, source);
376
+ manifests.set(adapter.manifest.id, adapter.manifest);
377
+ }
378
+ return [...manifests.values()];
379
+ }
380
+ async function resolveEngineResolveAdapterReference(args) {
381
+ const sources = Array.isArray(args.benchmark.adapters)
382
+ ? args.benchmark.adapters.filter((entry) => typeof entry === "string")
383
+ : [];
384
+ for (const source of sources) {
385
+ const adapter = await resolveProjectAdapterSource(args.root, source);
386
+ if (adapter.manifest.id === args.adapterId) {
387
+ return adapter;
388
+ }
389
+ }
390
+ const defaultAdapter = resolveDefaultWorkbenchAdapter(args.adapterId);
391
+ if (defaultAdapter) {
392
+ return defaultAdapter;
393
+ }
394
+ throw new WorkspaceSnapshotError(`Workbench engine resolver adapter ${args.adapterId} is not installed. Add its source under benchmark.yaml adapters.`);
395
+ }
396
+ async function executeEngineResolveAdapter(args) {
397
+ const cwd = args.adapter.root && await directoryExists(args.adapter.root)
398
+ ? args.adapter.root
399
+ : args.adapter.kind === "default"
400
+ ? args.workspaceRoot
401
+ : await materializeEngineResolveAdapterFiles(args.outputRoot, args.adapter);
402
+ await runEngineResolveAdapterCommand({
403
+ command: workbenchAdapterOperationCommand(args.adapter.manifest, "engine.resolve"),
404
+ cwd,
405
+ requestPath: args.requestPath,
406
+ outputRoot: args.outputRoot,
407
+ workspaceRoot: args.workspaceRoot,
408
+ });
409
+ }
410
+ async function materializeEngineResolveAdapterFiles(outputRoot, adapter) {
411
+ const adapterRoot = path.join(outputRoot, ".workbench", "adapter");
412
+ await fs.rm(adapterRoot, { recursive: true, force: true }).catch(() => undefined);
413
+ await fs.mkdir(adapterRoot, { recursive: true });
414
+ for (const file of adapter.files ?? []) {
415
+ const target = path.join(adapterRoot, file.path);
416
+ await fs.mkdir(path.dirname(target), { recursive: true });
417
+ await fs.writeFile(target, file.content);
418
+ if (file.executable) {
419
+ await fs.chmod(target, 0o755).catch(() => undefined);
420
+ }
421
+ }
422
+ return adapterRoot;
423
+ }
424
+ async function runEngineResolveAdapterCommand(args) {
425
+ await new Promise((resolve, reject) => {
426
+ const child = spawn("sh", ["-c", args.command], {
427
+ cwd: args.cwd,
428
+ env: {
429
+ ...process.env,
430
+ PATH: [
431
+ path.join(args.workspaceRoot, "node_modules/.bin"),
432
+ path.join(args.workspaceRoot, "products/workbench/node_modules/.bin"),
433
+ process.env.PATH,
434
+ ].filter(Boolean).join(":"),
435
+ WORKBENCH_ADAPTER_REQUEST: args.requestPath,
436
+ WORKBENCH_OUTPUT: args.outputRoot,
437
+ WORKBENCH_RESULT: workbenchAdapterOperationResultPath(args.outputRoot),
438
+ WORKBENCH_WORKSPACE_ROOT: args.workspaceRoot,
439
+ WORKBENCH_ADAPTER_ROOT: args.cwd,
440
+ },
441
+ stdio: ["ignore", "pipe", "pipe"],
442
+ });
443
+ const stdout = [];
444
+ const stderr = [];
445
+ child.stdout?.on("data", (chunk) => stdout.push(chunk));
446
+ child.stderr?.on("data", (chunk) => stderr.push(chunk));
447
+ child.on("error", reject);
448
+ child.on("exit", (code, signal) => {
449
+ if (code === 0) {
450
+ resolve();
451
+ return;
452
+ }
453
+ const detail = [
454
+ Buffer.concat(stderr).toString("utf8").trim(),
455
+ Buffer.concat(stdout).toString("utf8").trim(),
456
+ ].filter(Boolean).join("\n");
457
+ reject(new WorkspaceSnapshotError([
458
+ signal
459
+ ? `Workbench engine resolver adapter command exited from signal ${signal}.`
460
+ : `Workbench engine resolver adapter command exited with status ${code ?? "unknown"}.`,
461
+ detail,
462
+ ].filter(Boolean).join("\n")));
463
+ });
464
+ });
465
+ }
466
+ async function readEngineResolveResult(outputRoot, adapterId) {
467
+ return await readWorkbenchAdapterOperationResult(outputRoot, "engine.resolve").then((result) => {
468
+ assertWorkbenchAdapterOperationResultOk(result, `Adapter ${adapterId} engine.resolve`);
469
+ if (!result.value || typeof result.value !== "object" || Array.isArray(result.value)) {
470
+ throw new WorkspaceSnapshotError(`Adapter ${adapterId} engine.resolve did not return engine cases.`);
471
+ }
472
+ return {
473
+ value: result.value,
474
+ ...(result.feedback !== undefined ? { feedback: result.feedback } : {}),
475
+ };
476
+ }).catch((error) => {
477
+ if (error.code === "ENOENT") {
478
+ throw new WorkspaceSnapshotError(`Adapter ${adapterId} must write workbench-result.json for engine.resolve.`);
479
+ }
480
+ throw new WorkspaceSnapshotError(error instanceof Error ? error.message : String(error));
481
+ });
482
+ }
483
+ function engineResolveSourcePathFromFeedback(root, yamlDir, feedback) {
484
+ const record = yamlRecord(feedback);
485
+ if (!record || typeof record.path !== "string" || record.path.trim().length === 0) {
486
+ return undefined;
487
+ }
488
+ const absolute = resolveYamlReference(yamlDir, record.path);
489
+ const relative = path.relative(root, absolute);
490
+ if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) {
491
+ return undefined;
492
+ }
493
+ return normalizeSnapshotPath(relative);
494
+ }
495
+ function normalizeEngineResolveEnvironment(root, environment) {
496
+ if (!environment) {
497
+ return undefined;
498
+ }
499
+ return {
500
+ ...environment,
501
+ ...(environment.dockerfile
502
+ ? {
503
+ dockerfile: toRootRelativePath(root, resolveProjectPath(root, environment.dockerfile)),
504
+ }
505
+ : {}),
506
+ };
507
+ }
508
+ function normalizeEngineCaseEnvironments(root, engineCases) {
509
+ return engineCases.map((engineCase) => {
510
+ const environment = normalizeEngineResolveEnvironment(root, engineCase.case.environment);
511
+ return {
512
+ ...engineCase,
513
+ case: {
514
+ ...engineCase.case,
515
+ ...(environment ? { environment } : {}),
516
+ },
517
+ };
518
+ });
519
+ }
520
+ function engineResolveFilesFromBundles(engineCases) {
521
+ return normalizeSurfaceFiles(engineCases.flatMap((bundle) => {
522
+ const buckets = bundle.files;
523
+ const files = buckets.source?.length
524
+ ? buckets.source
525
+ : [...(buckets.subjectVisible ?? []), ...(buckets.enginePrivate ?? [])];
526
+ return files.map((file) => ({
527
+ ...file,
528
+ path: normalizeSnapshotPath(`${bundle.id}/${file.path}`),
529
+ }));
530
+ }));
531
+ }
532
+ function safePathSegment(value) {
533
+ return value.replace(/[^a-zA-Z0-9._-]+/gu, "_").replace(/^_+|_+$/gu, "") || "adapter";
534
+ }
535
+ function isPathAdapterSource(source) {
536
+ return !/^(?:npm|git):/iu.test(source.trim());
537
+ }
538
+ function isBenchmarkSourceRecord(record) {
539
+ return record.engine !== undefined;
540
+ }
541
+ function isSubjectSourceRecord(record) {
542
+ return record.run !== undefined;
543
+ }
544
+ function isOptimizerSourceRecord(record) {
545
+ return record.edits !== undefined && record.improve !== undefined;
546
+ }
547
+ async function readYamlRecordFile(filePath) {
548
+ return parseYamlRecord(await readRequiredTextFile(filePath, path.basename(filePath)), filePath);
549
+ }
550
+ async function subjectPathsForSubjectDirectory(sourceDir) {
551
+ const subjectSpecPath = path.join(sourceDir, WORKBENCH_SUBJECT_FILE);
552
+ if (!(await fileExists(subjectSpecPath))) {
553
+ return null;
554
+ }
555
+ const dir = projectRootForSubjectDir(sourceDir);
556
+ return {
557
+ dir,
558
+ benchmarkPath: path.join(dir, WORKBENCH_BENCHMARK_FILE),
559
+ subjectDir: sourceDir,
560
+ subjectSpecPath,
561
+ };
562
+ }
563
+ function projectRootForSubjectDir(subjectDir) {
564
+ const parent = path.basename(path.dirname(subjectDir));
565
+ if (parent !== WORKBENCH_SUBJECTS_DIR) {
566
+ throw new WorkspaceSnapshotError(`Subject directory must be under ${WORKBENCH_SUBJECTS_DIR}/NAME: ${subjectDir}`);
567
+ }
568
+ return path.dirname(path.dirname(subjectDir));
569
+ }
570
+ function parseYamlRecord(source, label) {
571
+ const parsed = YAML.parse(source);
572
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
573
+ throw new WorkspaceSnapshotError(`${label} must be a YAML object.`);
574
+ }
575
+ return parsed;
576
+ }
577
+ function yamlRecord(value) {
578
+ return value && typeof value === "object" && !Array.isArray(value)
579
+ ? value
580
+ : null;
581
+ }
582
+ function resolveYamlReference(baseDir, value) {
583
+ return path.isAbsolute(value) ? path.normalize(value) : path.resolve(baseDir, value);
584
+ }
585
+ function toRootRelativePath(root, absolutePath) {
586
+ const relative = path.relative(root, absolutePath);
587
+ if (!relative || relative === "") {
588
+ return ".";
589
+ }
590
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
591
+ return path.normalize(absolutePath);
592
+ }
593
+ return normalizeSnapshotPath(relative);
594
+ }
595
+ function normalizeSnapshotPath(filePath) {
596
+ return filePath.split(path.sep).join("/");
597
+ }
598
+ async function fileExists(filePath) {
599
+ return await fs.stat(filePath).then((stat) => stat.isFile(), () => false);
600
+ }
601
+ async function directoryExists(filePath) {
602
+ return await fs.stat(filePath).then((stat) => stat.isDirectory(), () => false);
603
+ }
604
+ async function listSubjectManifestFiles(dir) {
605
+ const entries = await fs.readdir(dir, { withFileTypes: true }).catch((error) => {
606
+ if (error.code === "ENOENT") {
607
+ return [];
608
+ }
609
+ throw error;
610
+ });
611
+ const manifests = await Promise.all(entries
612
+ .filter((entry) => entry.isDirectory())
613
+ .map(async (entry) => {
614
+ const manifestPath = path.join(dir, entry.name, WORKBENCH_SUBJECT_FILE);
615
+ return await fileExists(manifestPath) ? manifestPath : null;
616
+ }));
617
+ return manifests
618
+ .filter((entry) => Boolean(entry))
619
+ .sort((left, right) => left.localeCompare(right));
620
+ }
621
+ async function listYamlFiles(dir) {
622
+ const entries = await fs.readdir(dir, { withFileTypes: true }).catch((error) => {
623
+ if (error.code === "ENOENT") {
624
+ return [];
625
+ }
626
+ throw error;
627
+ });
628
+ return entries
629
+ .filter((entry) => entry.isFile() && /\.ya?ml$/iu.test(entry.name))
630
+ .map((entry) => path.join(dir, entry.name))
631
+ .sort((left, right) => left.localeCompare(right));
632
+ }
633
+ async function readRequiredTextFile(filePath, label) {
634
+ return await fs.readFile(filePath, "utf8").catch((error) => {
635
+ if (error.code === "ENOENT") {
636
+ throw new WorkspaceSnapshotError(`${label} not found: ${filePath}`);
637
+ }
638
+ throw error;
639
+ });
640
+ }
641
+ async function readDockerfileSourcesForSpec(dir, spec, engineCases = []) {
642
+ const dockerfilePaths = new Set([spec.environment.dockerfile]);
643
+ for (const engineCase of engineCases) {
644
+ const dockerfile = engineCase.case.environment?.dockerfile;
645
+ if (dockerfile) {
646
+ dockerfilePaths.add(dockerfile);
647
+ }
648
+ }
649
+ const sources = new Map();
650
+ for (const dockerfilePath of [...dockerfilePaths].sort()) {
651
+ const absoluteDockerfilePath = resolveProjectPath(dir, dockerfilePath);
652
+ const source = await fs.readFile(absoluteDockerfilePath, "utf8").catch((error) => {
653
+ if (error.code === "ENOENT") {
654
+ throw new WorkspaceSnapshotError(`Dockerfile not found: ${absoluteDockerfilePath}`);
655
+ }
656
+ throw error;
657
+ });
658
+ sources.set(dockerfilePath, source);
659
+ }
660
+ return sources;
661
+ }
662
+ function resolveProjectPath(root, filePath) {
663
+ return path.isAbsolute(filePath) ? filePath : path.join(root, filePath);
664
+ }
665
+ function dockerfileSourceFiles(sources) {
666
+ return [...sources.entries()].map(([filePath, content]) => textSourceFile(filePath, content));
667
+ }
668
+ function textSourceFile(filePath, content) {
669
+ return {
670
+ path: filePath,
671
+ kind: "text",
672
+ encoding: "utf8",
673
+ content,
674
+ executable: false,
675
+ };
676
+ }
677
+ function adapterSourceFiles(adapters) {
678
+ return adapters.flatMap((adapter) => adapter.kind === "default"
679
+ ? []
680
+ : (adapter.files ?? []).map((file) => ({
681
+ path: adapterSourceSnapshotPath(adapter, file.path),
682
+ kind: "text",
683
+ encoding: "utf8",
684
+ content: file.content,
685
+ executable: file.executable,
686
+ })));
687
+ }
688
+ function adapterSourceSnapshotPath(adapter, filePath) {
689
+ return adapter.kind === "path"
690
+ ? `${adapter.source}/${filePath}`
691
+ : `adapters/${adapter.manifest.id}/${filePath}`;
692
+ }
693
+ function toHostedFiles(files) {
694
+ return files.map((file) => ({
695
+ path: file.path,
696
+ content: file.content,
697
+ ...(file.encoding === "base64" ? { encoding: file.encoding } : {}),
698
+ ...(file.executable ? { executable: true } : {}),
699
+ }));
700
+ }