@workbench-ai/workbench-core 0.0.64 → 0.0.66

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.
@@ -0,0 +1,217 @@
1
+ import { buildCandidateLineage, buildWorkbenchEvaluationComparison, } from "@workbench-ai/workbench-contract";
2
+ import { buildCandidateCaseExecutionRefs, buildWorkbenchExecutionEvidence, } from "./execution-evidence.js";
3
+ import { candidateRecordWithoutDerivedFields, createCandidateFilePreview, createCaseReview, summarizeCandidateFiles, } from "./index.js";
4
+ export class WorkbenchInspectionError extends Error {
5
+ status;
6
+ statusCode;
7
+ constructor(message, options = {}) {
8
+ super(message);
9
+ this.name = "WorkbenchInspectionError";
10
+ this.status = options.status ?? 400;
11
+ this.statusCode = this.status;
12
+ }
13
+ }
14
+ export function createWorkbenchInspection(backend) {
15
+ return {
16
+ snapshot: () => backend.snapshot(),
17
+ spec: (input = {}) => backend.spec(input),
18
+ sourceFiles: async (input = {}) => {
19
+ const files = await backend.sourceFiles(input);
20
+ return summarizeCandidateFiles(files, files.map((file) => file.path));
21
+ },
22
+ sourcePreview: async (input) => createCandidateFilePreview({
23
+ files: await backend.sourceFiles(input),
24
+ path: input.path,
25
+ view: input.view,
26
+ }),
27
+ candidate: async (input) => candidateRecordWithoutDerivedFields(await backend.candidate(input)),
28
+ candidateFiles: async (input) => {
29
+ const result = await backend.candidateFiles(input);
30
+ return summarizeCandidateFiles(result.files, result.changedPaths);
31
+ },
32
+ candidatePreview: async (input) => createCandidateFilePreview({
33
+ files: (await backend.candidateFiles(input)).files,
34
+ path: input.path,
35
+ view: input.view,
36
+ }),
37
+ evaluations: async () => {
38
+ const snapshot = await backend.snapshot();
39
+ return buildWorkbenchEvaluationComparison(snapshot.evaluations);
40
+ },
41
+ evaluation: (input) => backend.evaluation(input),
42
+ caseReview: async (input) => {
43
+ if (backend.caseReview) {
44
+ return await backend.caseReview(input);
45
+ }
46
+ const candidate = await backend.candidate({ id: input.candidateId });
47
+ const jobs = (await backend.run({ id: input.runId, includeJobs: true })).jobs ?? [];
48
+ return createCaseReview({
49
+ candidate,
50
+ caseId: input.caseId,
51
+ executions: buildCandidateCaseExecutionRefs({
52
+ jobs,
53
+ candidateId: input.candidateId,
54
+ caseId: input.caseId,
55
+ sampleIndex: input.sampleIndex,
56
+ }),
57
+ });
58
+ },
59
+ run: (input) => backend.run(input),
60
+ executionTrace: async (input) => {
61
+ if (backend.executionTrace) {
62
+ return await backend.executionTrace(input);
63
+ }
64
+ if (!backend.jobInRun || !backend.traceForJob) {
65
+ throw new WorkbenchInspectionError("Execution traces are not available for this Workbench inspection backend.", { status: 404 });
66
+ }
67
+ const jobs = [await backend.jobInRun(input)];
68
+ return {
69
+ projectId: backend.projectId,
70
+ runId: input.runId,
71
+ executions: buildWorkbenchExecutionEvidence({
72
+ jobs,
73
+ traceIdPrefix: `${backend.projectId}-execution`,
74
+ traceForJob: backend.traceForJob,
75
+ traceSessionsForJob: backend.traceSessionsForJob,
76
+ }),
77
+ };
78
+ },
79
+ executionFiles: async (input) => {
80
+ const files = await backend.executionFiles(input);
81
+ return summarizeCandidateFiles(files, files.map((file) => file.path));
82
+ },
83
+ executionPreview: async (input) => createCandidateFilePreview({
84
+ files: await backend.executionFiles(input),
85
+ path: input.path,
86
+ view: input.view,
87
+ }),
88
+ lineage: async () => {
89
+ const snapshot = await backend.snapshot();
90
+ return buildCandidateLineage({
91
+ summaries: snapshot.summaries,
92
+ activeId: snapshot.activeId,
93
+ });
94
+ },
95
+ diagnose: async (input = {}) => {
96
+ const snapshot = await backend.snapshot();
97
+ return await diagnoseWorkbenchFailures({
98
+ snapshot,
99
+ backend,
100
+ targetId: input.targetId?.trim() || null,
101
+ });
102
+ },
103
+ };
104
+ }
105
+ async function diagnoseWorkbenchFailures(args) {
106
+ const targetRun = args.targetId
107
+ ? args.snapshot.runs.find((run) => run.id === args.targetId)
108
+ : null;
109
+ const targetEvaluation = args.targetId
110
+ ? args.snapshot.evaluations.find((evaluation) => evaluation.id === args.targetId)
111
+ : null;
112
+ const failures = [];
113
+ if (args.targetId && targetRun) {
114
+ const detail = await args.backend.run({ id: targetRun.id, includeJobs: true });
115
+ failures.push(...runFailures(detail.run));
116
+ failures.push(...jobFailures(detail.jobs ?? []));
117
+ }
118
+ else if (args.targetId && targetEvaluation) {
119
+ const evaluation = await args.backend.evaluation({ id: targetEvaluation.id });
120
+ failures.push(...evaluationFailures(evaluation));
121
+ }
122
+ else {
123
+ for (const run of args.snapshot.runs) {
124
+ failures.push(...runFailures(run));
125
+ }
126
+ for (const evaluation of args.snapshot.evaluations) {
127
+ failures.push(...evaluationSummaryFailures(evaluation));
128
+ }
129
+ }
130
+ return {
131
+ targetId: args.targetId,
132
+ failures,
133
+ failedRunCount: failures.filter((failure) => failure.kind === "run").length,
134
+ failedEvaluationCount: failures.filter((failure) => failure.kind === "evaluation").length,
135
+ failedJobCount: failures.filter((failure) => failure.kind === "job").length,
136
+ };
137
+ }
138
+ function runFailures(run) {
139
+ if (run.status !== "finished" || (run.outcome !== "error" && run.outcome !== "cancelled")) {
140
+ return [];
141
+ }
142
+ return [{
143
+ kind: "run",
144
+ id: run.id,
145
+ runId: run.id,
146
+ candidateId: run.outputCandidateId ?? run.candidateId ?? undefined,
147
+ status: run.outcome,
148
+ ...(run.error ? { error: run.error } : {}),
149
+ }];
150
+ }
151
+ function evaluationSummaryFailures(evaluation) {
152
+ if (evaluation.status === "completed" &&
153
+ evaluation.errorSampleCount === 0 &&
154
+ !evaluation.error) {
155
+ return [];
156
+ }
157
+ return [{
158
+ kind: "evaluation",
159
+ id: evaluation.id,
160
+ evaluationId: evaluation.id,
161
+ runId: evaluation.runId,
162
+ candidateId: evaluation.candidateId,
163
+ status: evaluation.status,
164
+ ...(evaluation.error ? { error: evaluation.error } : {}),
165
+ }];
166
+ }
167
+ function evaluationFailures(evaluation) {
168
+ const failures = evaluationSummaryFailures(evaluation);
169
+ for (const sample of evaluation.evaluation.samples) {
170
+ if (!sample.error && !(sample.cases ?? []).some((entry) => entry.status && entry.status !== "completed")) {
171
+ continue;
172
+ }
173
+ failures.push({
174
+ kind: "sample",
175
+ id: `${evaluation.id}:sample:${sample.index}`,
176
+ evaluationId: evaluation.id,
177
+ runId: evaluation.runId,
178
+ candidateId: evaluation.candidateId,
179
+ sampleIndex: sample.index,
180
+ status: sample.status,
181
+ ...(sample.error ? { error: sample.error } : {}),
182
+ });
183
+ for (const result of sample.cases ?? []) {
184
+ if (!result.status || result.status === "completed") {
185
+ continue;
186
+ }
187
+ failures.push({
188
+ kind: "case",
189
+ id: `${evaluation.id}:case:${result.id}:sample:${sample.index}`,
190
+ evaluationId: evaluation.id,
191
+ runId: evaluation.runId,
192
+ candidateId: evaluation.candidateId,
193
+ caseId: result.id,
194
+ sampleIndex: sample.index,
195
+ status: result.status,
196
+ });
197
+ }
198
+ }
199
+ return failures;
200
+ }
201
+ function jobFailures(jobs) {
202
+ return jobs
203
+ .filter((job) => isFailedJobStatus(job.status))
204
+ .map((job) => ({
205
+ kind: "job",
206
+ id: job.id,
207
+ jobId: job.id,
208
+ runId: job.runId,
209
+ candidateId: job.candidateId,
210
+ status: job.status,
211
+ attemptIndex: typeof job.attempt === "number" ? job.attempt : undefined,
212
+ ...(job.error ? { error: job.error } : {}),
213
+ }));
214
+ }
215
+ function isFailedJobStatus(status) {
216
+ return status === "failed" || status === "cancelled";
217
+ }
@@ -1,10 +1,15 @@
1
- import type { Json } from "@workbench-ai/workbench-contract";
1
+ import type { Json, SurfaceSnapshotFile } from "@workbench-ai/workbench-contract";
2
2
  export declare function importNodeModule<T>(specifier: string): Promise<T>;
3
3
  export declare function nodeBuiltin(name: string): string;
4
4
  export declare function asRuntimeRecord(value: unknown): Record<string, unknown>;
5
5
  export declare function jsonRecord(value: unknown): Record<string, Json>;
6
6
  export declare function numberValue(value: unknown): number | undefined;
7
7
  export declare function stringValue(value: unknown): string | undefined;
8
+ export declare function normalizeRelativePath(filePath: string): string;
9
+ export declare function writeSurfaceFiles(root: string, files: readonly SurfaceSnapshotFile[]): Promise<void>;
10
+ export declare function readSurfaceFiles(root: string, options?: {
11
+ ignorePath?: (path: string) => boolean;
12
+ }): Promise<SurfaceSnapshotFile[]>;
8
13
  export declare function isJsonPayload(value: unknown): value is import("@workbench-ai/workbench-contract").Json;
9
14
  export declare function resolveDockerRuntimeImageRef(imageRef: string, options: {
10
15
  runtimeRegistry: string;
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-utils.d.ts","sourceRoot":"","sources":["../src/runtime-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,IAAI,EACL,MAAM,kCAAkC,CAAC;AAE1C,wBAAsB,gBAAgB,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAEvE;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAIvE;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAI/D;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAE9D;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAE9D;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,OAAO,kCAAkC,EAAE,IAAI,CAWtG;AAED,wBAAgB,4BAA4B,CAC1C,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE;IAAE,eAAe,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAAE,GAChF,MAAM,CASR;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE9D;AAeD,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAS7E;AAED,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,SAAS,OAAO,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CASjG;AAoBD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKnD"}
1
+ {"version":3,"file":"runtime-utils.d.ts","sourceRoot":"","sources":["../src/runtime-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,IAAI,EACJ,mBAAmB,EACpB,MAAM,kCAAkC,CAAC;AAE1C,wBAAsB,gBAAgB,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAEvE;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAIvE;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAI/D;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAE9D;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAE9D;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAU9D;AAED,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,SAAS,mBAAmB,EAAE,GACpC,OAAO,CAAC,IAAI,CAAC,CAgBf;AAED,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAA;CAAO,GACvD,OAAO,CAAC,mBAAmB,EAAE,CAAC,CA+ChC;AAwBD,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,OAAO,kCAAkC,EAAE,IAAI,CAWtG;AAED,wBAAgB,4BAA4B,CAC1C,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE;IAAE,eAAe,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAAE,GAChF,MAAM,CASR;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE9D;AAeD,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAS7E;AAED,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,SAAS,OAAO,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CASjG;AAoBD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKnD"}
@@ -28,6 +28,98 @@ export function numberValue(value) {
28
28
  export function stringValue(value) {
29
29
  return typeof value === "string" && value.length > 0 ? value : undefined;
30
30
  }
31
+ export function normalizeRelativePath(filePath) {
32
+ const normalized = filePath.replace(/\\/gu, "/").replace(/^\/+/u, "");
33
+ if (!normalized || normalized.includes("\0")) {
34
+ throw new Error("File paths must be non-empty relative paths.");
35
+ }
36
+ const parts = normalized.split("/");
37
+ if (parts.some((part) => part === ".." || part === "." || part === "")) {
38
+ throw new Error(`Unsafe relative file path: ${filePath}`);
39
+ }
40
+ return normalized;
41
+ }
42
+ export async function writeSurfaceFiles(root, files) {
43
+ const fs = await importNodeModule(nodeBuiltin("fs/promises"));
44
+ const path = await importNodeModule(nodeBuiltin("path"));
45
+ await fs.mkdir(root, { recursive: true });
46
+ for (const file of files) {
47
+ const target = path.join(root, normalizeRelativePath(file.path));
48
+ await fs.mkdir(path.dirname(target), { recursive: true });
49
+ const body = file.encoding === "base64"
50
+ ? Buffer.from(file.content, "base64")
51
+ : Buffer.from(file.content, "utf8");
52
+ await fs.writeFile(target, body);
53
+ if (file.executable) {
54
+ await fs.chmod(target, 0o755).catch(() => undefined);
55
+ }
56
+ }
57
+ }
58
+ export async function readSurfaceFiles(root, options = {}) {
59
+ const fs = await importNodeModule(nodeBuiltin("fs/promises"));
60
+ const path = await importNodeModule(nodeBuiltin("path"));
61
+ const utf8Decoder = new TextDecoder("utf-8", { fatal: true });
62
+ const files = [];
63
+ async function walk(directory) {
64
+ const entries = await fs
65
+ .readdir(directory, { withFileTypes: true })
66
+ .catch(() => []);
67
+ for (const entry of entries) {
68
+ const absolutePath = path.join(directory, entry.name);
69
+ const relativePath = normalizeRelativePath(path.relative(root, absolutePath).replace(/\\/gu, "/"));
70
+ if (options.ignorePath?.(relativePath)) {
71
+ continue;
72
+ }
73
+ if (entry.isDirectory()) {
74
+ await walk(absolutePath);
75
+ continue;
76
+ }
77
+ if (!entry.isFile()) {
78
+ continue;
79
+ }
80
+ let body;
81
+ let stats;
82
+ try {
83
+ body = await fs.readFile(absolutePath);
84
+ stats = await fs.stat(absolutePath);
85
+ }
86
+ catch (error) {
87
+ if (isVanishedWalkEntry(error)) {
88
+ continue;
89
+ }
90
+ throw error;
91
+ }
92
+ const content = encodeSurfaceSnapshotContent(body, utf8Decoder);
93
+ files.push({
94
+ path: relativePath,
95
+ kind: content.encoding === "base64" ? "binary" : "text",
96
+ encoding: content.encoding,
97
+ content: content.content,
98
+ executable: (stats.mode & 0o111) !== 0,
99
+ });
100
+ }
101
+ }
102
+ await walk(root);
103
+ return files.sort((left, right) => left.path.localeCompare(right.path));
104
+ }
105
+ function isVanishedWalkEntry(error) {
106
+ const code = error?.code;
107
+ return code === "ENOENT" || code === "ENOTDIR";
108
+ }
109
+ function encodeSurfaceSnapshotContent(body, utf8Decoder) {
110
+ try {
111
+ return {
112
+ encoding: "utf8",
113
+ content: utf8Decoder.decode(body),
114
+ };
115
+ }
116
+ catch {
117
+ return {
118
+ encoding: "base64",
119
+ content: body.toString("base64"),
120
+ };
121
+ }
122
+ }
31
123
  export function isJsonPayload(value) {
32
124
  if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
33
125
  return Number.isFinite(value) || typeof value !== "number";
@@ -1,37 +1,29 @@
1
1
  import type { WorkbenchExecutionRuntimeInput } from "../execution-runtime-types.ts";
2
2
  import type { SandboxBackendCapabilities, SandboxExecutionFileStore, SandboxPlane } from "../sandbox-plane.ts";
3
- import { type WorkbenchSandboxProviderName } from "./names.ts";
4
- export { DOCKER_SANDBOX_BACKEND, resolveWorkbenchSandboxProviderName, type WorkbenchSandboxProviderName, } from "./names.ts";
3
+ import { type WorkbenchSandboxBackendName } from "./names.ts";
4
+ export { DOCKER_SANDBOX_BACKEND, resolveWorkbenchSandboxBackendName, type WorkbenchSandboxBackendName, } from "./names.ts";
5
5
  export { createDockerSandboxBackendDescriptor, createDockerSandboxPlane, } from "./docker.ts";
6
6
  export interface SandboxHostHealthExpectation {
7
- provider: WorkbenchSandboxProviderName;
8
- backend: string;
7
+ backend: WorkbenchSandboxBackendName;
9
8
  capabilities: SandboxBackendCapabilities;
10
9
  }
11
- export interface SandboxProviderRequestedResources {
10
+ export interface SandboxBackendRequestedResources {
12
11
  cpu: number;
13
12
  memoryGb: number;
14
13
  diskGb?: number;
15
14
  timeoutMinutes?: number;
16
15
  }
17
- export interface SandboxProviderHostCost {
16
+ export interface SandboxBackendHostCost {
18
17
  cpu: number;
19
18
  memoryGb: number;
20
19
  diskGb: number;
21
20
  }
22
- export interface SandboxProviderLeaseRequest {
23
- scope: string;
24
- units: number;
21
+ export interface SandboxBackendAdmission {
22
+ backend: WorkbenchSandboxBackendName;
23
+ hostCost: SandboxBackendHostCost;
25
24
  }
26
- export interface SandboxProviderAdmission {
27
- provider: WorkbenchSandboxProviderName;
28
- hostCost: SandboxProviderHostCost;
29
- providerLeases: SandboxProviderLeaseRequest[];
30
- }
31
- export declare function createSandboxBackendPlaneForProvider(provider: string, args: WorkbenchExecutionRuntimeInput, startedAt: string, fileStore: SandboxExecutionFileStore): SandboxPlane;
32
- export declare function sandboxHostHealthExpectationForProvider(provider: WorkbenchSandboxProviderName): SandboxHostHealthExpectation;
33
- export declare function assertSandboxHostHealthForProvider(value: unknown, provider: WorkbenchSandboxProviderName): void;
34
- export declare function sandboxProviderDefaultMaxConcurrentJobs(_provider: WorkbenchSandboxProviderName): number | null;
35
- export declare function sandboxProviderAdmissionForResources(provider: WorkbenchSandboxProviderName, resources: SandboxProviderRequestedResources): SandboxProviderAdmission;
36
- export declare function sandboxProviderLeaseScope(provider: WorkbenchSandboxProviderName): string;
25
+ export declare function createSandboxBackendPlaneForBackend(backend: string, args: WorkbenchExecutionRuntimeInput, startedAt: string, fileStore: SandboxExecutionFileStore): SandboxPlane;
26
+ export declare function sandboxHostHealthExpectationForBackend(backend: WorkbenchSandboxBackendName): SandboxHostHealthExpectation;
27
+ export declare function assertSandboxHostHealthForBackend(value: unknown, backend: WorkbenchSandboxBackendName): void;
28
+ export declare function sandboxBackendAdmissionForResources(backend: WorkbenchSandboxBackendName, resources: SandboxBackendRequestedResources): SandboxBackendAdmission;
37
29
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sandbox-backends/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,8BAA8B,EAC/B,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EACV,0BAA0B,EAE1B,yBAAyB,EACzB,YAAY,EACb,MAAM,qBAAqB,CAAC;AAK7B,OAAO,EAEL,KAAK,4BAA4B,EAElC,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,sBAAsB,EACtB,mCAAmC,EACnC,KAAK,4BAA4B,GAClC,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,oCAAoC,EACpC,wBAAwB,GACzB,MAAM,aAAa,CAAC;AAErB,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,EAAE,4BAA4B,CAAC;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,0BAA0B,CAAC;CAC1C;AAED,MAAM,WAAW,iCAAiC;IAChD,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,uBAAuB;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,2BAA2B;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,4BAA4B,CAAC;IACvC,QAAQ,EAAE,uBAAuB,CAAC;IAClC,cAAc,EAAE,2BAA2B,EAAE,CAAC;CAC/C;AAED,wBAAgB,oCAAoC,CAClD,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,8BAA8B,EACpC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,yBAAyB,GACnC,YAAY,CAMd;AAED,wBAAgB,uCAAuC,CACrD,QAAQ,EAAE,4BAA4B,GACrC,4BAA4B,CAS9B;AAED,wBAAgB,kCAAkC,CAChD,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,4BAA4B,GACrC,IAAI,CAeN;AAED,wBAAgB,uCAAuC,CACrD,SAAS,EAAE,4BAA4B,GACtC,MAAM,GAAG,IAAI,CAEf;AAED,wBAAgB,oCAAoC,CAClD,QAAQ,EAAE,4BAA4B,EACtC,SAAS,EAAE,iCAAiC,GAC3C,wBAAwB,CAkB1B;AAED,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,4BAA4B,GACrC,MAAM,CAER"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sandbox-backends/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,8BAA8B,EAC/B,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EACV,0BAA0B,EAE1B,yBAAyB,EACzB,YAAY,EACb,MAAM,qBAAqB,CAAC;AAK7B,OAAO,EAEL,KAAK,2BAA2B,EAEjC,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,sBAAsB,EACtB,kCAAkC,EAClC,KAAK,2BAA2B,GACjC,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,oCAAoC,EACpC,wBAAwB,GACzB,MAAM,aAAa,CAAC;AAErB,MAAM,WAAW,4BAA4B;IAC3C,OAAO,EAAE,2BAA2B,CAAC;IACrC,YAAY,EAAE,0BAA0B,CAAC;CAC1C;AAED,MAAM,WAAW,gCAAgC;IAC/C,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,sBAAsB;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,2BAA2B,CAAC;IACrC,QAAQ,EAAE,sBAAsB,CAAC;CAClC;AAED,wBAAgB,mCAAmC,CACjD,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,8BAA8B,EACpC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,yBAAyB,GACnC,YAAY,CAMd;AAED,wBAAgB,sCAAsC,CACpD,OAAO,EAAE,2BAA2B,GACnC,4BAA4B,CAQ9B;AAED,wBAAgB,iCAAiC,CAC/C,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,2BAA2B,GACnC,IAAI,CAYN;AAED,wBAAgB,mCAAmC,CACjD,OAAO,EAAE,2BAA2B,EACpC,SAAS,EAAE,gCAAgC,GAC1C,uBAAuB,CAiBzB"}
@@ -1,34 +1,30 @@
1
1
  import { isWorkbenchExecutionNetworkEgress, } from "@workbench-ai/workbench-contract";
2
2
  import { createDockerSandboxBackendDescriptor, createDockerSandboxPlane, } from "./docker.js";
3
- import { DOCKER_SANDBOX_BACKEND, resolveWorkbenchSandboxProviderName, } from "./names.js";
4
- export { DOCKER_SANDBOX_BACKEND, resolveWorkbenchSandboxProviderName, } from "./names.js";
3
+ import { DOCKER_SANDBOX_BACKEND, resolveWorkbenchSandboxBackendName, } from "./names.js";
4
+ export { DOCKER_SANDBOX_BACKEND, resolveWorkbenchSandboxBackendName, } from "./names.js";
5
5
  export { createDockerSandboxBackendDescriptor, createDockerSandboxPlane, } from "./docker.js";
6
- export function createSandboxBackendPlaneForProvider(provider, args, startedAt, fileStore) {
7
- const resolved = resolveWorkbenchSandboxProviderName(provider);
6
+ export function createSandboxBackendPlaneForBackend(backend, args, startedAt, fileStore) {
7
+ const resolved = resolveWorkbenchSandboxBackendName(backend);
8
8
  if (resolved !== DOCKER_SANDBOX_BACKEND) {
9
- throw new Error(`Unsupported local sandbox provider ${provider}.`);
9
+ throw new Error(`Unsupported local sandbox backend ${backend}.`);
10
10
  }
11
11
  return createDockerSandboxPlane(args, startedAt, fileStore);
12
12
  }
13
- export function sandboxHostHealthExpectationForProvider(provider) {
14
- if (provider !== DOCKER_SANDBOX_BACKEND) {
15
- resolveWorkbenchSandboxProviderName(provider);
13
+ export function sandboxHostHealthExpectationForBackend(backend) {
14
+ if (backend !== DOCKER_SANDBOX_BACKEND) {
15
+ resolveWorkbenchSandboxBackendName(backend);
16
16
  }
17
17
  return {
18
- provider,
19
- backend: provider,
18
+ backend,
20
19
  capabilities: createDockerSandboxBackendDescriptor().capabilities,
21
20
  };
22
21
  }
23
- export function assertSandboxHostHealthForProvider(value, provider) {
24
- const expected = sandboxHostHealthExpectationForProvider(provider);
22
+ export function assertSandboxHostHealthForBackend(value, backend) {
23
+ const expected = sandboxHostHealthExpectationForBackend(backend);
25
24
  if (!value || typeof value !== "object" || Array.isArray(value)) {
26
25
  throw new Error("sandbox host health response must be an object.");
27
26
  }
28
27
  const record = value;
29
- if (record.provider !== expected.provider) {
30
- throw new Error(`sandbox host provider ${String(record.provider ?? "missing")} does not match expected ${expected.provider}.`);
31
- }
32
28
  if (record.backend !== expected.backend) {
33
29
  throw new Error(`sandbox host backend ${String(record.backend ?? "missing")} does not match expected ${expected.backend}.`);
34
30
  }
@@ -36,12 +32,9 @@ export function assertSandboxHostHealthForProvider(value, provider) {
36
32
  throw new Error(`sandbox host capabilities are invalid for backend ${expected.backend}.`);
37
33
  }
38
34
  }
39
- export function sandboxProviderDefaultMaxConcurrentJobs(_provider) {
40
- return null;
41
- }
42
- export function sandboxProviderAdmissionForResources(provider, resources) {
43
- if (provider !== DOCKER_SANDBOX_BACKEND) {
44
- resolveWorkbenchSandboxProviderName(provider);
35
+ export function sandboxBackendAdmissionForResources(backend, resources) {
36
+ if (backend !== DOCKER_SANDBOX_BACKEND) {
37
+ resolveWorkbenchSandboxBackendName(backend);
45
38
  }
46
39
  assertPositiveResource(resources.cpu, "resources.cpu");
47
40
  assertPositiveResource(resources.memoryGb, "resources.memoryGb");
@@ -49,18 +42,14 @@ export function sandboxProviderAdmissionForResources(provider, resources) {
49
42
  assertPositiveResource(resources.diskGb, "resources.diskGb");
50
43
  }
51
44
  return {
52
- provider,
45
+ backend,
53
46
  hostCost: {
54
47
  cpu: resources.cpu,
55
48
  memoryGb: resources.memoryGb,
56
49
  diskGb: resources.diskGb ?? 1,
57
50
  },
58
- providerLeases: [],
59
51
  };
60
52
  }
61
- export function sandboxProviderLeaseScope(provider) {
62
- throw new Error(`Local sandbox provider ${provider} does not use provider leases.`);
63
- }
64
53
  function isSandboxBackendCapabilities(value) {
65
54
  if (!value || typeof value !== "object" || Array.isArray(value)) {
66
55
  return false;
@@ -1,6 +1,5 @@
1
1
  export declare const DOCKER_SANDBOX_BACKEND = "docker";
2
2
  export type WorkbenchSandboxBackendName = typeof DOCKER_SANDBOX_BACKEND;
3
- export type WorkbenchSandboxProviderName = WorkbenchSandboxBackendName;
4
- export declare function isWorkbenchSandboxProviderName(value: string): value is WorkbenchSandboxProviderName;
5
- export declare function resolveWorkbenchSandboxProviderName(value: string | null | undefined): WorkbenchSandboxProviderName;
3
+ export declare function isWorkbenchSandboxBackendName(value: string): value is WorkbenchSandboxBackendName;
4
+ export declare function resolveWorkbenchSandboxBackendName(value: string | null | undefined): WorkbenchSandboxBackendName;
6
5
  //# sourceMappingURL=names.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"names.d.ts","sourceRoot":"","sources":["../../src/sandbox-backends/names.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,sBAAsB,WAAW,CAAC;AAE/C,MAAM,MAAM,2BAA2B,GAAG,OAAO,sBAAsB,CAAC;AAExE,MAAM,MAAM,4BAA4B,GAAG,2BAA2B,CAAC;AAEvE,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,4BAA4B,CAEnG;AAED,wBAAgB,mCAAmC,CACjD,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC/B,4BAA4B,CAS9B"}
1
+ {"version":3,"file":"names.d.ts","sourceRoot":"","sources":["../../src/sandbox-backends/names.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,sBAAsB,WAAW,CAAC;AAE/C,MAAM,MAAM,2BAA2B,GAAG,OAAO,sBAAsB,CAAC;AAExE,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,2BAA2B,CAEjG;AAED,wBAAgB,kCAAkC,CAChD,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC/B,2BAA2B,CAS7B"}
@@ -1,14 +1,14 @@
1
1
  export const DOCKER_SANDBOX_BACKEND = "docker";
2
- export function isWorkbenchSandboxProviderName(value) {
2
+ export function isWorkbenchSandboxBackendName(value) {
3
3
  return value === DOCKER_SANDBOX_BACKEND;
4
4
  }
5
- export function resolveWorkbenchSandboxProviderName(value) {
5
+ export function resolveWorkbenchSandboxBackendName(value) {
6
6
  const normalized = value?.trim();
7
7
  if (!normalized) {
8
- throw new Error("Sandbox provider is required.");
8
+ throw new Error("Sandbox backend is required.");
9
9
  }
10
- if (isWorkbenchSandboxProviderName(normalized)) {
10
+ if (isWorkbenchSandboxBackendName(normalized)) {
11
11
  return normalized;
12
12
  }
13
- throw new Error(`Unsupported local sandbox provider ${normalized}. Supported providers: docker.`);
13
+ throw new Error(`Unsupported local sandbox backend ${normalized}. Supported backends: docker.`);
14
14
  }
@@ -1,15 +1,15 @@
1
- import type { BlobObjectRef, HostedWorkbenchJob, Json, SurfaceSnapshotFile, WorkbenchExecutionCapability, WorkbenchExecutionResult, WorkbenchExecutionSpec, WorkbenchSandboxExecutionMetadata } from "@workbench-ai/workbench-contract";
1
+ import type { BlobObjectRef, RemoteWorkbenchJob, Json, SurfaceSnapshotFile, WorkbenchExecutionCapability, WorkbenchExecutionResult, WorkbenchExecutionSpec, WorkbenchSandboxExecutionMetadata } from "@workbench-ai/workbench-contract";
2
2
  import { type SandboxCreateRequest, type SandboxExecutionFileStore, type SandboxMaterializedInput } from "./sandbox-plane.ts";
3
3
  import type { WorkbenchExecutionRuntimeInput } from "./execution-runtime-types.ts";
4
- export declare function readWorkbenchExecutionSpec(job: HostedWorkbenchJob): WorkbenchExecutionSpec;
4
+ export declare function readWorkbenchExecutionSpec(job: RemoteWorkbenchJob): WorkbenchExecutionSpec;
5
5
  export declare function createWorkbenchSandboxFileStore(args: WorkbenchExecutionRuntimeInput): SandboxExecutionFileStore;
6
6
  export declare function materializeWorkbenchSandboxInput(args: WorkbenchExecutionRuntimeInput, execution: WorkbenchExecutionSpec, input: WorkbenchExecutionSpec["inputs"][number]): SandboxMaterializedInput;
7
7
  export declare function materializedFileInput(input: WorkbenchExecutionSpec["inputs"][number], files: readonly SurfaceSnapshotFile[]): SandboxMaterializedInput;
8
8
  export declare function createSandboxAdapterRequest(args: WorkbenchExecutionRuntimeInput, request: SandboxCreateRequest, startedAt: string): Json;
9
- export declare function sanitizeWorkbenchExecutionJobForSandbox(job: HostedWorkbenchJob, execution: WorkbenchExecutionSpec): HostedWorkbenchJob;
9
+ export declare function sanitizeWorkbenchExecutionJobForSandbox(job: RemoteWorkbenchJob, execution: WorkbenchExecutionSpec): RemoteWorkbenchJob;
10
10
  export declare function materializedInputForSandboxRequest(input: SandboxMaterializedInput): Record<string, Json>;
11
11
  export declare function executionResultFromCompletedSandboxJob(args: {
12
- completedJob: HostedWorkbenchJob;
12
+ completedJob: RemoteWorkbenchJob;
13
13
  execution: WorkbenchExecutionSpec;
14
14
  startedAt: string;
15
15
  backend: string;
@@ -21,7 +21,7 @@ export declare function executionResultFromCompletedSandboxJob(args: {
21
21
  export declare function outputPayloadForContract(output: Record<string, unknown>, outputName: string): Json | undefined;
22
22
  export declare function sandboxOutputRef(capability: WorkbenchExecutionCapability, outputName: string, body: string): Promise<BlobObjectRef>;
23
23
  export declare function sha256Hex(body: string): Promise<string>;
24
- export declare function withSandboxCompletionMetadata(job: HostedWorkbenchJob, metadata: WorkbenchSandboxExecutionMetadata): HostedWorkbenchJob;
25
- export declare function attachSandboxMetadataToJob(job: HostedWorkbenchJob, metadata: unknown): HostedWorkbenchJob;
24
+ export declare function withSandboxCompletionMetadata(job: RemoteWorkbenchJob, metadata: WorkbenchSandboxExecutionMetadata): RemoteWorkbenchJob;
25
+ export declare function attachSandboxMetadataToJob(job: RemoteWorkbenchJob, metadata: unknown): RemoteWorkbenchJob;
26
26
  export declare function isSurfaceSnapshotFile(value: unknown): value is SurfaceSnapshotFile;
27
27
  //# sourceMappingURL=sandbox-inputs.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"trace-files.d.ts","sourceRoot":"","sources":["../src/trace-files.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EACnB,yBAAyB,EAC1B,MAAM,kCAAkC,CAAC;AAO1C,eAAO,MAAM,oBAAoB,sBAAsB,CAAC;AAExD,wBAAsB,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAiChH;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,SAAS,mBAAmB,EAAE,GAAG,MAAM,EAAE,CAK9E;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,MAAM,CAET;AAED,wBAAgB,gCAAgC,CAAC,IAAI,EAAE;IACrD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,yBAAyB,CAAC;CACpC,GAAG,MAAM,CAET;AAED,wBAAgB,8BAA8B,CAAC,IAAI,EAAE;IACnD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,MAAM,CAKT"}
1
+ {"version":3,"file":"trace-files.d.ts","sourceRoot":"","sources":["../src/trace-files.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EACnB,yBAAyB,EAC1B,MAAM,kCAAkC,CAAC;AAO1C,eAAO,MAAM,oBAAoB,sBAAsB,CAAC;AAExD,wBAAsB,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAOhH;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,SAAS,mBAAmB,EAAE,GAAG,MAAM,EAAE,CAK9E;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,MAAM,CAET;AAED,wBAAgB,gCAAgC,CAAC,IAAI,EAAE;IACrD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,yBAAyB,CAAC;CACpC,GAAG,MAAM,CAET;AAED,wBAAgB,8BAA8B,CAAC,IAAI,EAAE;IACnD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,MAAM,CAKT"}
@@ -1,38 +1,12 @@
1
- import { importNodeModule, nodeBuiltin, } from "./runtime-utils.js";
1
+ import { normalizeRelativePath, readSurfaceFiles, } from "./runtime-utils.js";
2
2
  export const WORKBENCH_TRACE_ROOT = ".workbench/traces";
3
3
  export async function readOutputTraceFiles(outputRoot, traceRoot) {
4
- const fs = await importNodeModule(nodeBuiltin("fs/promises"));
5
- const path = await importNodeModule(nodeBuiltin("path"));
6
- const decoder = new TextDecoder("utf-8", { fatal: true });
7
- const files = [];
8
- async function walk(directory) {
9
- const entries = await fs.readdir(directory, { withFileTypes: true }).catch(() => []);
10
- for (const entry of entries) {
11
- const absolutePath = path.join(directory, entry.name);
12
- if (entry.isDirectory()) {
13
- const relativeDirectory = normalizeRelativePath(path.relative(outputRoot, absolutePath).replace(/\\/gu, "/"));
14
- if (shouldSkipTraceDirectory(relativeDirectory)) {
15
- continue;
16
- }
17
- await walk(absolutePath);
18
- continue;
19
- }
20
- if (!entry.isFile()) {
21
- continue;
22
- }
23
- const body = await fs.readFile(absolutePath);
24
- const content = encodeTraceFileContent(body, decoder);
25
- files.push({
26
- path: normalizeRelativePath(`${traceRoot}/${path.relative(outputRoot, absolutePath).replace(/\\/gu, "/")}`),
27
- kind: content.encoding === "base64" ? "binary" : "text",
28
- encoding: content.encoding,
29
- content: content.content,
30
- executable: false,
31
- });
32
- }
33
- }
34
- await walk(outputRoot);
35
- return files;
4
+ return (await readSurfaceFiles(outputRoot, { ignorePath: shouldSkipTraceDirectory }))
5
+ .map((file) => ({
6
+ ...file,
7
+ path: normalizeRelativePath(`${traceRoot}/${file.path}`),
8
+ executable: false,
9
+ }));
36
10
  }
37
11
  export function traceFilePaths(files) {
38
12
  return files
@@ -62,20 +36,6 @@ function shouldSkipTraceDirectory(relativeDirectory) {
62
36
  || relativeDirectory.endsWith("/session/workspace")
63
37
  || relativeDirectory.includes("/session/workspace/");
64
38
  }
65
- function encodeTraceFileContent(body, utf8Decoder) {
66
- try {
67
- return {
68
- encoding: "utf8",
69
- content: utf8Decoder.decode(body),
70
- };
71
- }
72
- catch {
73
- return {
74
- encoding: "base64",
75
- content: body.toString("base64"),
76
- };
77
- }
78
- }
79
39
  function sanitizeTracePathSegment(value) {
80
40
  const sanitized = value
81
41
  .trim()
@@ -89,6 +49,3 @@ function tracePurposeSequence(purpose) {
89
49
  }
90
50
  return 2;
91
51
  }
92
- function normalizeRelativePath(filePath) {
93
- return filePath.replace(/\\/gu, "/").replace(/^\/+/u, "").replace(/\/+/gu, "/");
94
- }