@workbench-ai/workbench-protocol 0.0.43 → 0.0.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,145 +1,237 @@
1
1
  import { promises as fs } from "node:fs";
2
2
  import path from "node:path";
3
- export async function readWorkbenchAdapterCommandRequest(configuredPath) {
3
+ import { normalizeWorkbenchEngineResolveResult, } from "./engine-resolve-result.js";
4
+ import { normalizeWorkbenchAdapterOperation, } from "./adapter-manifest.js";
5
+ export const WORKBENCH_ADAPTER_PROTOCOL = "workbench.adapter.v3";
6
+ export const WORKBENCH_ADAPTER_RESULT_PROTOCOL = "workbench.adapter-result.v1";
7
+ export const WORKBENCH_ADAPTER_RESULT_FILE = "workbench-result.json";
8
+ export async function readWorkbenchAdapterOperationRequest(configuredPath) {
4
9
  const requestPath = configuredPath ?? process.env.WORKBENCH_ADAPTER_REQUEST;
5
10
  if (!requestPath) {
6
11
  throw new Error("WORKBENCH_ADAPTER_REQUEST is required.");
7
12
  }
8
13
  const parsed = JSON.parse(await fs.readFile(requestPath, "utf8"));
9
- return normalizeWorkbenchAdapterCommandRequest(parsed);
14
+ return normalizeWorkbenchAdapterOperationRequest(parsed);
10
15
  }
11
- export function normalizeWorkbenchAdapterCommandRequest(value) {
16
+ export function normalizeWorkbenchAdapterOperationRequest(value) {
12
17
  const record = requiredJsonRecord(value, "adapter request");
13
- if (record.protocol !== "workbench.adapter.v1") {
14
- throw new Error("Adapter request protocol must be workbench.adapter.v1.");
18
+ if (record.protocol !== WORKBENCH_ADAPTER_PROTOCOL) {
19
+ throw new Error(`Adapter request protocol must be ${WORKBENCH_ADAPTER_PROTOCOL}.`);
15
20
  }
16
- const execution = requiredJsonRecord(record.execution, "adapter request execution");
17
- const adapter = requiredJsonRecord(record.adapter, "adapter request adapter");
21
+ const invocation = requiredJsonRecord(record.invocation, "adapter request invocation");
18
22
  const paths = requiredJsonRecord(record.paths, "adapter request paths");
19
- const purpose = requiredPurpose(execution.purpose, "adapter request execution.purpose");
20
- const role = normalizeExecutionRole(execution.role, purpose);
21
- const use = requiredString(adapter.use, "adapter request adapter.use");
23
+ rejectUnknownJsonKeys(paths, "adapter request paths", [
24
+ "workspace",
25
+ "cwd",
26
+ "output",
27
+ "result",
28
+ "subject",
29
+ "case",
30
+ "traces",
31
+ "enginePrivate",
32
+ "logs",
33
+ ]);
34
+ const operation = requiredOperation(record.operation, "adapter request operation");
35
+ const use = requiredString(invocation.use, "adapter request invocation.use");
22
36
  return {
23
- protocol: "workbench.adapter.v1",
24
- execution: {
25
- id: requiredString(execution.id, "adapter request execution.id"),
26
- ...(typeof execution.jobId === "string" ? { jobId: execution.jobId } : {}),
27
- purpose,
28
- role,
29
- ...(typeof execution.candidateId === "string" ? { candidateId: execution.candidateId } : {}),
30
- ...(typeof execution.trialIndex === "number" ? { trialIndex: execution.trialIndex } : {}),
31
- ...(typeof execution.sampleIndex === "number" ? { sampleIndex: execution.sampleIndex } : {}),
32
- ...(typeof execution.caseId === "string" ? { caseId: execution.caseId } : {}),
33
- },
34
- adapter: {
37
+ protocol: WORKBENCH_ADAPTER_PROTOCOL,
38
+ id: requiredString(record.id, "adapter request id"),
39
+ ...(typeof record.jobId === "string" ? { jobId: record.jobId } : {}),
40
+ operation,
41
+ invocation: {
35
42
  use,
36
- with: adapter.with !== undefined ? adapter.with : {},
37
- ...(adapter.auth !== undefined ? { auth: adapter.auth } : {}),
43
+ with: invocation.with !== undefined ? invocation.with : {},
44
+ ...(invocation.auth !== undefined ? { auth: invocation.auth } : {}),
38
45
  },
39
46
  ...(record.auth !== undefined ? { auth: record.auth } : {}),
40
- ...(record.benchmark !== undefined ? { benchmark: normalizeAdapterCommandBenchmark(record.benchmark) } : {}),
41
- ...(record.candidate !== undefined ? { candidate: normalizeAdapterCommandCandidate(record.candidate) } : {}),
42
- ...(record.optimizer !== undefined ? { optimizer: normalizeAdapterCommandOptimizer(record.optimizer) } : {}),
43
- ...(record.task !== undefined ? { task: normalizeAdapterCommandTask(record.task) } : {}),
44
- ...(Array.isArray(record.expectedOutputs)
45
- ? { expectedOutputs: normalizeAdapterCommandExpectedOutputs(record.expectedOutputs) }
46
- : {}),
47
+ ...(record.context !== undefined ? { context: normalizeAdapterRequestContext(record.context) } : {}),
47
48
  paths: {
48
49
  workspace: requiredString(paths.workspace, "adapter request paths.workspace"),
49
50
  output: requiredString(paths.output, "adapter request paths.output"),
50
- ...(typeof paths.input === "string" ? { input: paths.input } : {}),
51
- ...(typeof paths.candidate === "string" ? { candidate: paths.candidate } : {}),
52
- ...(typeof paths.task === "string" ? { task: paths.task } : {}),
53
- ...(typeof paths.runnerOutput === "string" ? { runnerOutput: paths.runnerOutput } : {}),
51
+ result: requiredString(paths.result, "adapter request paths.result"),
52
+ ...(typeof paths.case === "string" ? { case: paths.case } : {}),
54
53
  ...(typeof paths.traces === "string" ? { traces: paths.traces } : {}),
55
54
  ...(typeof paths.cwd === "string" ? { cwd: paths.cwd } : {}),
56
55
  ...(typeof paths.subject === "string" ? { subject: paths.subject } : {}),
57
- ...(typeof paths.tests === "string" ? { tests: paths.tests } : {}),
56
+ ...(typeof paths.enginePrivate === "string" ? { enginePrivate: paths.enginePrivate } : {}),
58
57
  ...(typeof paths.logs === "string" ? { logs: paths.logs } : {}),
59
- ...(typeof paths.artifacts === "string" ? { artifacts: paths.artifacts } : {}),
60
- ...(typeof paths.scorecard === "string" ? { scorecard: paths.scorecard } : {}),
61
58
  },
62
59
  };
63
60
  }
64
61
  export async function ensureWorkbenchAdapterOutputDir(request) {
65
62
  await fs.mkdir(request.paths.output, { recursive: true });
66
63
  }
67
- export function workbenchAdapterResultPath(outputRoot) {
68
- return path.join(outputRoot, ".workbench", "result.json");
64
+ export function workbenchAdapterOperationResultPath(outputRoot) {
65
+ return path.join(outputRoot, WORKBENCH_ADAPTER_RESULT_FILE);
69
66
  }
70
- export async function writeWorkbenchAdapterResultMetadata(outputRoot, result) {
71
- const resultPath = workbenchAdapterResultPath(outputRoot);
67
+ export async function writeWorkbenchAdapterOperationResult(outputRoot, result) {
68
+ const normalized = normalizeWorkbenchAdapterOperationResult(result);
69
+ const resultPath = workbenchAdapterOperationResultPath(outputRoot);
72
70
  await fs.mkdir(path.dirname(resultPath), { recursive: true });
73
- await fs.writeFile(resultPath, `${JSON.stringify(result, null, 2)}\n`);
74
- }
75
- export async function readWorkbenchAdapterResultMetadata(outputRoot) {
76
- const source = await fs.readFile(workbenchAdapterResultPath(outputRoot), "utf8").catch((error) => {
77
- if (error.code === "ENOENT") {
78
- return null;
79
- }
80
- throw error;
81
- });
82
- if (source === null) {
83
- return {};
71
+ await fs.writeFile(resultPath, `${JSON.stringify(normalized, null, 2)}\n`);
72
+ }
73
+ export async function readWorkbenchAdapterOperationResult(outputRoot, operation) {
74
+ const parsed = JSON.parse(await fs.readFile(workbenchAdapterOperationResultPath(outputRoot), "utf8"));
75
+ return normalizeWorkbenchAdapterOperationResult(parsed, operation);
76
+ }
77
+ export function normalizeWorkbenchAdapterOperationResult(value, operation) {
78
+ const record = requiredJsonRecord(value, WORKBENCH_ADAPTER_RESULT_FILE);
79
+ if (record.protocol !== WORKBENCH_ADAPTER_RESULT_PROTOCOL) {
80
+ throw new Error(`${WORKBENCH_ADAPTER_RESULT_FILE}.protocol must be ${WORKBENCH_ADAPTER_RESULT_PROTOCOL}.`);
81
+ }
82
+ const resultOperation = requiredOperation(record.operation, `${WORKBENCH_ADAPTER_RESULT_FILE}.operation`);
83
+ const expectedOperation = operation
84
+ ? normalizeWorkbenchAdapterOperation(operation, "expected adapter result operation")
85
+ : undefined;
86
+ if (expectedOperation && resultOperation !== expectedOperation) {
87
+ throw new Error(`${WORKBENCH_ADAPTER_RESULT_FILE}.operation must be ${expectedOperation}.`);
84
88
  }
85
- const parsed = JSON.parse(source);
86
- const record = jsonRecord(parsed);
87
89
  return {
90
+ protocol: WORKBENCH_ADAPTER_RESULT_PROTOCOL,
91
+ operation: resultOperation,
88
92
  ...(record.ok === true || record.ok === false ? { ok: record.ok } : {}),
93
+ ...(record.value !== undefined
94
+ ? { value: normalizeOperationResultValue(resultOperation, record.value) }
95
+ : {}),
89
96
  ...(typeof record.summary === "string" ? { summary: record.summary } : {}),
90
97
  ...(record.feedback !== undefined ? { feedback: record.feedback } : {}),
91
98
  ...(record.usage !== undefined ? { usage: normalizeUsageSummary(record.usage) } : {}),
92
99
  };
93
100
  }
94
- function normalizeAdapterCommandExpectedOutputs(value) {
95
- return value.flatMap((entry) => {
96
- if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
97
- return [];
98
- }
99
- return [{
100
- ...(typeof entry.name === "string" ? { name: entry.name } : {}),
101
- ...(typeof entry.path === "string" ? { path: entry.path } : {}),
102
- }];
103
- });
104
- }
105
- function normalizeAdapterCommandBenchmark(value) {
106
- const record = requiredJsonRecord(value, "adapter request benchmark");
101
+ export function assertWorkbenchAdapterOperationResultOk(result, label = "Adapter operation") {
102
+ if (result.ok !== false) {
103
+ return;
104
+ }
105
+ throw new Error(`${label} returned ok false${result.summary ? `: ${result.summary}` : "."}`);
106
+ }
107
+ function normalizeOperationResultValue(operation, value) {
108
+ if (operation === "engine.resolve") {
109
+ return normalizeWorkbenchEngineResolveResult(value);
110
+ }
111
+ if (operation === "engine.run") {
112
+ return normalizeResult(value, `${WORKBENCH_ADAPTER_RESULT_FILE}.value`);
113
+ }
114
+ if (operation === "optimizer.improve") {
115
+ return normalizeSubjectPatch(value, `${WORKBENCH_ADAPTER_RESULT_FILE}.value`);
116
+ }
117
+ if (value === undefined || value === null) {
118
+ return null;
119
+ }
120
+ return value;
121
+ }
122
+ function normalizeAdapterRequestContext(value) {
123
+ const record = requiredJsonRecord(value, "adapter request context");
124
+ return {
125
+ ...(record.benchmark !== undefined ? { benchmark: normalizeBenchmarkContext(record.benchmark) } : {}),
126
+ ...(record.subject !== undefined ? { subject: normalizeSubjectContext(record.subject) } : {}),
127
+ ...(record.optimizer !== undefined ? { optimizer: normalizeOptimizerContext(record.optimizer) } : {}),
128
+ ...(record.attempt !== undefined ? { attempt: normalizeAttemptContext(record.attempt) } : {}),
129
+ ...(record.case !== undefined ? { case: normalizeCaseContext(record.case) } : {}),
130
+ };
131
+ }
132
+ function normalizeBenchmarkContext(value) {
133
+ const record = requiredJsonRecord(value, "adapter request context.benchmark");
107
134
  return {
108
135
  ...(typeof record.name === "string" ? { name: record.name } : {}),
109
136
  ...(typeof record.description === "string" ? { description: record.description } : {}),
110
137
  };
111
138
  }
112
- function normalizeAdapterCommandCandidate(value) {
113
- const record = requiredJsonRecord(value, "adapter request candidate");
139
+ function normalizeSubjectContext(value) {
140
+ const record = requiredJsonRecord(value, "adapter request context.subject");
114
141
  return {
142
+ ...(typeof record.id === "string" ? { id: record.id } : {}),
115
143
  ...(typeof record.path === "string" ? { path: record.path } : {}),
144
+ ...(record.run !== undefined ? { run: normalizeContextInvocation(record.run, "adapter request context.subject.run") } : {}),
145
+ };
146
+ }
147
+ function normalizeContextInvocation(value, label) {
148
+ const record = requiredJsonRecord(value, label);
149
+ const use = requiredString(record.use, `${label}.use`);
150
+ return {
151
+ use,
152
+ with: record.with !== undefined ? record.with : {},
153
+ ...(record.auth !== undefined ? { auth: record.auth } : {}),
154
+ ...(typeof record.command === "string" && record.command.trim() ? { command: record.command } : {}),
116
155
  };
117
156
  }
118
- function normalizeAdapterCommandOptimizer(value) {
119
- const record = requiredJsonRecord(value, "adapter request optimizer");
157
+ function normalizeOptimizerContext(value) {
158
+ const record = requiredJsonRecord(value, "adapter request context.optimizer");
120
159
  return {
121
160
  edits: Array.isArray(record.edits)
122
161
  ? record.edits.filter((entry) => typeof entry === "string")
123
162
  : [],
124
163
  };
125
164
  }
126
- function normalizeAdapterCommandTask(value) {
127
- const record = requiredJsonRecord(value, "adapter request task");
165
+ function normalizeAttemptContext(value) {
166
+ const record = requiredJsonRecord(value, "adapter request context.attempt");
167
+ return {
168
+ ...(typeof record.attemptIndex === "number" ? { attemptIndex: record.attemptIndex } : {}),
169
+ ...(typeof record.sampleIndex === "number" ? { sampleIndex: record.sampleIndex } : {}),
170
+ ...(typeof record.caseId === "string" ? { caseId: record.caseId } : {}),
171
+ };
172
+ }
173
+ function normalizeCaseContext(value) {
174
+ const record = requiredJsonRecord(value, "adapter request context.case");
128
175
  return {
129
- ...(typeof record.text === "string" ? { text: record.text } : {}),
176
+ ...(typeof record.id === "string" ? { id: record.id } : {}),
177
+ ...(typeof record.prompt === "string" ? { prompt: record.prompt } : {}),
130
178
  };
131
179
  }
180
+ function normalizeResult(value, label) {
181
+ const record = requiredJsonRecord(value, label);
182
+ if (typeof record.score !== "number" || !Number.isFinite(record.score)) {
183
+ throw new Error(`${label}.score must be a finite number.`);
184
+ }
185
+ return {
186
+ score: record.score,
187
+ ...(isNumberRecord(record.metrics) ? { metrics: record.metrics } : {}),
188
+ ...(Array.isArray(record.cases) ? { cases: record.cases } : {}),
189
+ ...(record.usage !== undefined ? { usage: normalizeUsageSummary(record.usage) } : {}),
190
+ ...(typeof record.summary === "string" ? { summary: record.summary } : {}),
191
+ ...(record.feedback !== undefined ? { feedback: record.feedback } : {}),
192
+ };
193
+ }
194
+ function normalizeSubjectPatch(value, label) {
195
+ const record = requiredJsonRecord(value, label);
196
+ if (!Array.isArray(record.files)) {
197
+ throw new Error(`${label}.files must be an array.`);
198
+ }
199
+ if (!Array.isArray(record.fileChanges) || !record.fileChanges.every((entry) => typeof entry === "string")) {
200
+ throw new Error(`${label}.fileChanges must be a string array.`);
201
+ }
202
+ return {
203
+ files: record.files,
204
+ fileChanges: [...record.fileChanges],
205
+ ...(typeof record.summary === "string" ? { summary: record.summary } : {}),
206
+ ...(record.feedback !== undefined ? { feedback: record.feedback } : {}),
207
+ };
208
+ }
209
+ function normalizeUsageSummary(value) {
210
+ return value && typeof value === "object" && !Array.isArray(value)
211
+ ? value
212
+ : {};
213
+ }
214
+ function isNumberRecord(value) {
215
+ return !!value &&
216
+ typeof value === "object" &&
217
+ !Array.isArray(value) &&
218
+ Object.values(value).every((entry) => typeof entry === "number" && Number.isFinite(entry));
219
+ }
132
220
  function requiredJsonRecord(value, label) {
133
221
  if (!value || typeof value !== "object" || Array.isArray(value)) {
134
222
  throw new Error(`${label} must be an object.`);
135
223
  }
136
224
  return value;
137
225
  }
138
- function requiredPurpose(value, label) {
139
- if (value === "improve" || value === "trial" || value === "run-task" || value === "grade-task") {
140
- return value;
226
+ function rejectUnknownJsonKeys(record, label, allowed) {
227
+ const allowedSet = new Set(allowed);
228
+ const unknown = Object.keys(record).filter((key) => !allowedSet.has(key));
229
+ if (unknown.length > 0) {
230
+ throw new Error(`${label} includes unsupported fields: ${unknown.join(", ")}.`);
141
231
  }
142
- throw new Error(`${label} must be improve, trial, run-task, or grade-task.`);
232
+ }
233
+ function requiredOperation(value, label) {
234
+ return normalizeWorkbenchAdapterOperation(value, label);
143
235
  }
144
236
  function requiredString(value, label) {
145
237
  if (typeof value !== "string" || value.length === 0) {
@@ -147,55 +239,3 @@ function requiredString(value, label) {
147
239
  }
148
240
  return value;
149
241
  }
150
- function executionPurposeRole(purpose) {
151
- if (purpose === "improve") {
152
- return "optimizer";
153
- }
154
- if (purpose === "run-task") {
155
- return "runner";
156
- }
157
- if (purpose === "trial") {
158
- return "runner";
159
- }
160
- return "grader";
161
- }
162
- function normalizeExecutionRole(value, purpose) {
163
- if (value === "optimizer" || value === "runner" || value === "grader") {
164
- return value;
165
- }
166
- return executionPurposeRole(purpose);
167
- }
168
- function jsonRecord(value) {
169
- return value && typeof value === "object" && !Array.isArray(value)
170
- ? value
171
- : {};
172
- }
173
- function normalizeUsageSummary(value) {
174
- const record = jsonRecord(value);
175
- return {
176
- ...(record.total !== undefined ? { total: normalizeExecutionUsage(record.total) } : {}),
177
- ...(record.optimizer !== undefined ? { optimizer: normalizeExecutionUsage(record.optimizer) } : {}),
178
- ...(record.runner !== undefined ? { runner: normalizeExecutionUsage(record.runner) } : {}),
179
- ...(record.grader !== undefined ? { grader: normalizeExecutionUsage(record.grader) } : {}),
180
- };
181
- }
182
- function normalizeExecutionUsage(value) {
183
- const record = jsonRecord(value);
184
- return {
185
- ...(typeof record.provider === "string" ? { provider: record.provider } : {}),
186
- ...(typeof record.model === "string" ? { model: record.model } : {}),
187
- ...(typeof record.inputTokens === "number" ? { inputTokens: record.inputTokens } : {}),
188
- ...(typeof record.uncachedInputTokens === "number" ? { uncachedInputTokens: record.uncachedInputTokens } : {}),
189
- ...(typeof record.cachedInputTokens === "number" ? { cachedInputTokens: record.cachedInputTokens } : {}),
190
- ...(typeof record.cacheCreationInputTokens === "number" ? { cacheCreationInputTokens: record.cacheCreationInputTokens } : {}),
191
- ...(typeof record.cacheReadInputTokens === "number" ? { cacheReadInputTokens: record.cacheReadInputTokens } : {}),
192
- ...(typeof record.outputTokens === "number" ? { outputTokens: record.outputTokens } : {}),
193
- ...(typeof record.reasoningOutputTokens === "number" ? { reasoningOutputTokens: record.reasoningOutputTokens } : {}),
194
- ...(typeof record.totalTokens === "number" ? { totalTokens: record.totalTokens } : {}),
195
- ...(typeof record.costUsd === "number" ? { costUsd: record.costUsd } : {}),
196
- ...(record.costSource === "provider" || record.costSource === "estimated" || record.costSource === "mixed"
197
- ? { costSource: record.costSource }
198
- : {}),
199
- ...(typeof record.pricingSource === "string" ? { pricingSource: record.pricingSource } : {}),
200
- };
201
- }
@@ -0,0 +1,31 @@
1
+ import type { SurfaceSnapshotFile, WorkbenchExecutionNetworkPolicy, WorkbenchExecutionResources } from "@workbench-ai/workbench-contract";
2
+ export interface WorkbenchEngineCaseEnvironmentSpec {
3
+ dockerfile?: string;
4
+ workdir?: string;
5
+ resources?: Partial<WorkbenchExecutionResources>;
6
+ network?: WorkbenchExecutionNetworkPolicy;
7
+ }
8
+ export interface WorkbenchEngineCaseSpec {
9
+ version: 3;
10
+ prompt: string;
11
+ environment?: WorkbenchEngineCaseEnvironmentSpec;
12
+ }
13
+ export interface WorkbenchEngineCaseFiles {
14
+ subjectVisible?: SurfaceSnapshotFile[];
15
+ enginePrivate?: SurfaceSnapshotFile[];
16
+ source?: SurfaceSnapshotFile[];
17
+ }
18
+ export interface WorkbenchEngineCase {
19
+ id: string;
20
+ case: WorkbenchEngineCaseSpec;
21
+ files: WorkbenchEngineCaseFiles;
22
+ }
23
+ export interface WorkbenchEngineResolveResult {
24
+ cases: WorkbenchEngineCase[];
25
+ environment?: WorkbenchEngineCaseEnvironmentSpec;
26
+ }
27
+ export declare function normalizeWorkbenchEngineResolveResult(value: unknown, label?: string): WorkbenchEngineResolveResult;
28
+ export declare function normalizeWorkbenchEngineCase(value: unknown, label: string): WorkbenchEngineCase;
29
+ export declare function normalizeWorkbenchEngineCaseFiles(value: unknown, label: string): WorkbenchEngineCaseFiles;
30
+ export declare function normalizeWorkbenchEngineCaseSpec(value: unknown, label: string): WorkbenchEngineCaseSpec;
31
+ //# sourceMappingURL=engine-resolve-result.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine-resolve-result.d.ts","sourceRoot":"","sources":["../src/engine-resolve-result.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,mBAAmB,EACnB,+BAA+B,EAC/B,2BAA2B,EAC5B,MAAM,kCAAkC,CAAC;AAE1C,MAAM,WAAW,kCAAkC;IACjD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC,2BAA2B,CAAC,CAAC;IACjD,OAAO,CAAC,EAAE,+BAA+B,CAAC;CAC3C;AAED,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,CAAC,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,kCAAkC,CAAC;CAClD;AAED,MAAM,WAAW,wBAAwB;IACvC,cAAc,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACvC,aAAa,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACtC,MAAM,CAAC,EAAE,mBAAmB,EAAE,CAAC;CAChC;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,uBAAuB,CAAC;IAC9B,KAAK,EAAE,wBAAwB,CAAC;CACjC;AAED,MAAM,WAAW,4BAA4B;IAC3C,KAAK,EAAE,mBAAmB,EAAE,CAAC;IAC7B,WAAW,CAAC,EAAE,kCAAkC,CAAC;CAClD;AAED,wBAAgB,qCAAqC,CACnD,KAAK,EAAE,OAAO,EACd,KAAK,SAA0B,GAC9B,4BAA4B,CAkB9B;AAED,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,OAAO,EACd,KAAK,EAAE,MAAM,GACZ,mBAAmB,CAWrB;AAED,wBAAgB,iCAAiC,CAC/C,KAAK,EAAE,OAAO,EACd,KAAK,EAAE,MAAM,GACZ,wBAAwB,CAc1B;AAED,wBAAgB,gCAAgC,CAC9C,KAAK,EAAE,OAAO,EACd,KAAK,EAAE,MAAM,GACZ,uBAAuB,CAezB"}
@@ -0,0 +1,162 @@
1
+ import path from "node:path";
2
+ export function normalizeWorkbenchEngineResolveResult(value, label = "engine.resolve result") {
3
+ const record = requiredRecord(value, label);
4
+ if (!Array.isArray(record.cases)) {
5
+ throw new Error(`${label}.cases must be an array.`);
6
+ }
7
+ const cases = record.cases.map((entry, index) => normalizeWorkbenchEngineCase(entry, `${label}.cases[${index}]`));
8
+ const duplicate = firstDuplicate(cases.map((engineCase) => engineCase.id));
9
+ if (duplicate) {
10
+ throw new Error(`${label} contains duplicate case id: ${duplicate}`);
11
+ }
12
+ return {
13
+ cases,
14
+ ...(record.environment !== undefined
15
+ ? { environment: normalizeCaseEnvironment(record.environment, `${label}.environment`) }
16
+ : {}),
17
+ };
18
+ }
19
+ export function normalizeWorkbenchEngineCase(value, label) {
20
+ const record = requiredRecord(value, label);
21
+ rejectUnknownKeys(record, label, ["id", "case", "files"]);
22
+ if (typeof record.id !== "string" || record.id.trim().length === 0) {
23
+ throw new Error(`${label}.id must be a non-empty string.`);
24
+ }
25
+ return {
26
+ id: normalizeRelativePath(record.id, `${label}.id`),
27
+ case: normalizeWorkbenchEngineCaseSpec(record.case, `${label}.case`),
28
+ files: normalizeWorkbenchEngineCaseFiles(record.files, `${label}.files`),
29
+ };
30
+ }
31
+ export function normalizeWorkbenchEngineCaseFiles(value, label) {
32
+ const record = requiredRecord(value, label);
33
+ rejectUnknownKeys(record, label, ["subjectVisible", "enginePrivate", "source"]);
34
+ return {
35
+ ...(record.subjectVisible !== undefined
36
+ ? { subjectVisible: normalizeEngineResolveFiles(record.subjectVisible, `${label}.subjectVisible`) }
37
+ : {}),
38
+ ...(record.enginePrivate !== undefined
39
+ ? { enginePrivate: normalizeEngineResolveFiles(record.enginePrivate, `${label}.enginePrivate`) }
40
+ : {}),
41
+ ...(record.source !== undefined
42
+ ? { source: normalizeEngineResolveFiles(record.source, `${label}.source`) }
43
+ : {}),
44
+ };
45
+ }
46
+ export function normalizeWorkbenchEngineCaseSpec(value, label) {
47
+ const record = requiredRecord(value, label);
48
+ if (record.version !== 3) {
49
+ throw new Error(`${label}.version must be 3.`);
50
+ }
51
+ if (typeof record.prompt !== "string" || record.prompt.trim().length === 0) {
52
+ throw new Error(`${label}.prompt must be a non-empty string.`);
53
+ }
54
+ return {
55
+ version: 3,
56
+ prompt: record.prompt,
57
+ ...(record.environment !== undefined
58
+ ? { environment: normalizeCaseEnvironment(record.environment, `${label}.environment`) }
59
+ : {}),
60
+ };
61
+ }
62
+ function normalizeCaseEnvironment(value, label) {
63
+ const record = requiredRecord(value, label);
64
+ const resources = record.resources === undefined
65
+ ? undefined
66
+ : normalizeCaseResources(record.resources, `${label}.resources`);
67
+ const network = record.network === undefined
68
+ ? undefined
69
+ : normalizeNetworkPolicy(record.network, `${label}.network`);
70
+ return {
71
+ ...(typeof record.dockerfile === "string" && record.dockerfile.trim()
72
+ ? { dockerfile: record.dockerfile }
73
+ : {}),
74
+ ...(typeof record.workdir === "string" && record.workdir.trim()
75
+ ? { workdir: record.workdir }
76
+ : {}),
77
+ ...(resources ? { resources } : {}),
78
+ ...(network ? { network } : {}),
79
+ };
80
+ }
81
+ function normalizeCaseResources(value, label) {
82
+ const record = requiredRecord(value, label);
83
+ return {
84
+ ...(typeof record.cpu === "number" ? { cpu: record.cpu } : {}),
85
+ ...(typeof record.memoryGb === "number" ? { memoryGb: record.memoryGb } : {}),
86
+ ...(typeof record.diskGb === "number" ? { diskGb: record.diskGb } : {}),
87
+ ...(typeof record.timeoutMinutes === "number" ? { timeoutMinutes: record.timeoutMinutes } : {}),
88
+ };
89
+ }
90
+ function normalizeNetworkPolicy(value, label) {
91
+ const record = requiredRecord(value, label);
92
+ if (record.egress !== "none" &&
93
+ record.egress !== "allowlist" &&
94
+ record.egress !== "open") {
95
+ throw new Error(`${label}.egress must be none, allowlist, or open.`);
96
+ }
97
+ return {
98
+ egress: record.egress,
99
+ ...(Array.isArray(record.allow)
100
+ ? { allow: record.allow.filter((entry) => typeof entry === "string") }
101
+ : {}),
102
+ };
103
+ }
104
+ function normalizeEngineResolveFiles(value, label) {
105
+ if (value === undefined) {
106
+ return [];
107
+ }
108
+ if (!Array.isArray(value)) {
109
+ throw new Error(`${label} must be an array.`);
110
+ }
111
+ return value.map((entry, index) => {
112
+ const record = requiredRecord(entry, `${label}[${index}]`);
113
+ if (typeof record.path !== "string" || record.path.trim().length === 0) {
114
+ throw new Error(`${label}[${index}].path must be a non-empty string.`);
115
+ }
116
+ if (typeof record.content !== "string") {
117
+ throw new Error(`${label}[${index}].content must be a string.`);
118
+ }
119
+ const encoding = record.encoding === "base64" ? "base64" : "utf8";
120
+ const kind = record.kind === "binary" || encoding === "base64" ? "binary" : "text";
121
+ return {
122
+ path: normalizeRelativePath(record.path, `${label}[${index}].path`),
123
+ kind,
124
+ encoding,
125
+ content: record.content,
126
+ executable: record.executable === true,
127
+ };
128
+ }).sort((left, right) => left.path.localeCompare(right.path));
129
+ }
130
+ function normalizeRelativePath(filePath, label) {
131
+ const normalized = path.posix.normalize(filePath.replace(/\\/gu, "/").replace(/^\/+/u, ""));
132
+ if (!normalized || normalized === "." || normalized.includes("\0")) {
133
+ throw new Error(`${label} must be a non-empty relative path.`);
134
+ }
135
+ if (normalized === ".." || normalized.startsWith("../") || path.posix.isAbsolute(normalized)) {
136
+ throw new Error(`${label} must not escape the engine-resolve result.`);
137
+ }
138
+ return normalized;
139
+ }
140
+ function requiredRecord(value, label) {
141
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
142
+ throw new Error(`${label} must be an object.`);
143
+ }
144
+ return value;
145
+ }
146
+ function rejectUnknownKeys(record, label, allowed) {
147
+ const allowedSet = new Set(allowed);
148
+ const unknown = Object.keys(record).filter((key) => !allowedSet.has(key));
149
+ if (unknown.length > 0) {
150
+ throw new Error(`${label} includes unsupported fields: ${unknown.join(", ")}.`);
151
+ }
152
+ }
153
+ function firstDuplicate(values) {
154
+ const seen = new Set();
155
+ for (const value of values) {
156
+ if (seen.has(value)) {
157
+ return value;
158
+ }
159
+ seen.add(value);
160
+ }
161
+ return null;
162
+ }
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
- export { adapterCommandName, cloneWorkbenchAdapterManifest, collectWorkbenchAdapterAuthRequirements, collectWorkbenchAdapterInvocations, parseWorkbenchAdapterManifest, workbenchAdapterManifestRequiresAuth, withDefaultWorkbenchAdapterAuth, withDefaultWorkbenchAdapterAuthProfiles, type WorkbenchAdapterAuthEnvManifest, type WorkbenchAdapterAuthFileManifest, type WorkbenchAdapterAuthManifest, type WorkbenchAdapterAuthMethodManifest, type WorkbenchAdapterAuthRequirement, type WorkbenchAdapterInvocationLike, type WorkbenchAdapterManifest, } from "./adapter-manifest.ts";
2
- export { ensureWorkbenchAdapterOutputDir, normalizeWorkbenchAdapterCommandRequest, readWorkbenchAdapterCommandRequest, readWorkbenchAdapterResultMetadata, workbenchAdapterResultPath, writeWorkbenchAdapterResultMetadata, type WorkbenchAdapterCommandRequest, type WorkbenchAdapterResultMetadata, } from "./adapter-protocol.ts";
1
+ export { WORKBENCH_ADAPTER_MANIFEST_PROTOCOL, adapterCommandName, assertWorkbenchAdapterOperationSupport, cloneWorkbenchAdapterManifest, collectWorkbenchAdapterAuthRequirements, collectWorkbenchAdapterInvocations, collectWorkbenchAdapterOperationIssues, collectWorkbenchAdapterOperationRequirements, parseWorkbenchAdapterManifest, normalizeWorkbenchAdapterOperation, workbenchAdapterManifestSupportsOperation, workbenchAdapterOperationCommand, workbenchAdapterManifestRequiresAuth, withDefaultWorkbenchAdapterAuth, withDefaultWorkbenchAdapterAuthProfiles, type WorkbenchPrimitiveAdapterOperation, type WorkbenchAdapterOperation, type WorkbenchAdapterOperationManifest, type WorkbenchAdapterSlotManifest, type WorkbenchAdapterAuthEnvManifest, type WorkbenchAdapterAuthFileManifest, type WorkbenchAdapterAuthManifest, type WorkbenchAdapterAuthMethodManifest, type WorkbenchAdapterAuthRequirement, type WorkbenchAdapterInvocationLike, type WorkbenchAdapterManifest, type WorkbenchAdapterOperationRequirement, } from "./adapter-manifest.ts";
2
+ export { WORKBENCH_ADAPTER_PROTOCOL, WORKBENCH_ADAPTER_RESULT_FILE, WORKBENCH_ADAPTER_RESULT_PROTOCOL, assertWorkbenchAdapterOperationResultOk, ensureWorkbenchAdapterOutputDir, normalizeWorkbenchAdapterOperationRequest, normalizeWorkbenchAdapterOperationResult, readWorkbenchAdapterOperationRequest, readWorkbenchAdapterOperationResult, workbenchAdapterOperationResultPath, writeWorkbenchAdapterOperationResult, type WorkbenchAdapterOperationRequest, type WorkbenchAdapterOperationResult, type WorkbenchAdapterOperationResultValue, } from "./adapter-protocol.ts";
3
+ export { normalizeWorkbenchEngineCase, normalizeWorkbenchEngineCaseFiles, normalizeWorkbenchEngineCaseSpec, normalizeWorkbenchEngineResolveResult, type WorkbenchEngineCase, type WorkbenchEngineCaseFiles, type WorkbenchEngineCaseEnvironmentSpec, type WorkbenchEngineCaseSpec, type WorkbenchEngineResolveResult, } from "./engine-resolve-result.ts";
4
+ export { adapterResult, adapterSlot, adapterSlotInvocation, defineAdapter, defineEngineResolver, defineSubject, defineEngineRunner, defineOptimizer, operationDefinitionForRequest, runSubjectFromEngine, runDefinedAdapter, workbenchAdapterManifestFromDefinition, type WorkbenchAdapterDefinition, type WorkbenchAdapterHandlerContext, type WorkbenchAdapterHandlerReturn, type WorkbenchAdapterOperationDefinition, type WorkbenchAdapterOperationHandler, type RunDefinedWorkbenchAdapterOptions, } from "./adapter-definition.ts";
3
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,6BAA6B,EAC7B,uCAAuC,EACvC,kCAAkC,EAClC,6BAA6B,EAC7B,oCAAoC,EACpC,+BAA+B,EAC/B,uCAAuC,EACvC,KAAK,+BAA+B,EACpC,KAAK,gCAAgC,EACrC,KAAK,4BAA4B,EACjC,KAAK,kCAAkC,EACvC,KAAK,+BAA+B,EACpC,KAAK,8BAA8B,EACnC,KAAK,wBAAwB,GAC9B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,+BAA+B,EAC/B,uCAAuC,EACvC,kCAAkC,EAClC,kCAAkC,EAClC,0BAA0B,EAC1B,mCAAmC,EACnC,KAAK,8BAA8B,EACnC,KAAK,8BAA8B,GACpC,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mCAAmC,EACnC,kBAAkB,EAClB,sCAAsC,EACtC,6BAA6B,EAC7B,uCAAuC,EACvC,kCAAkC,EAClC,sCAAsC,EACtC,4CAA4C,EAC5C,6BAA6B,EAC7B,kCAAkC,EAClC,yCAAyC,EACzC,gCAAgC,EAChC,oCAAoC,EACpC,+BAA+B,EAC/B,uCAAuC,EACvC,KAAK,kCAAkC,EACvC,KAAK,yBAAyB,EAC9B,KAAK,iCAAiC,EACtC,KAAK,4BAA4B,EACjC,KAAK,+BAA+B,EACpC,KAAK,gCAAgC,EACrC,KAAK,4BAA4B,EACjC,KAAK,kCAAkC,EACvC,KAAK,+BAA+B,EACpC,KAAK,8BAA8B,EACnC,KAAK,wBAAwB,EAC7B,KAAK,oCAAoC,GAC1C,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,0BAA0B,EAC1B,6BAA6B,EAC7B,iCAAiC,EACjC,uCAAuC,EACvC,+BAA+B,EAC/B,yCAAyC,EACzC,wCAAwC,EACxC,oCAAoC,EACpC,mCAAmC,EACnC,mCAAmC,EACnC,oCAAoC,EACpC,KAAK,gCAAgC,EACrC,KAAK,+BAA+B,EACpC,KAAK,oCAAoC,GAC1C,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,4BAA4B,EAC5B,iCAAiC,EACjC,gCAAgC,EAChC,qCAAqC,EACrC,KAAK,mBAAmB,EACxB,KAAK,wBAAwB,EAC7B,KAAK,kCAAkC,EACvC,KAAK,uBAAuB,EAC5B,KAAK,4BAA4B,GAClC,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,aAAa,EACb,WAAW,EACX,qBAAqB,EACrB,aAAa,EACb,oBAAoB,EACpB,aAAa,EACb,kBAAkB,EAClB,eAAe,EACf,6BAA6B,EAC7B,oBAAoB,EACpB,iBAAiB,EACjB,sCAAsC,EACtC,KAAK,0BAA0B,EAC/B,KAAK,8BAA8B,EACnC,KAAK,6BAA6B,EAClC,KAAK,mCAAmC,EACxC,KAAK,gCAAgC,EACrC,KAAK,iCAAiC,GACvC,MAAM,yBAAyB,CAAC"}
package/dist/index.js CHANGED
@@ -1,2 +1,4 @@
1
- export { adapterCommandName, cloneWorkbenchAdapterManifest, collectWorkbenchAdapterAuthRequirements, collectWorkbenchAdapterInvocations, parseWorkbenchAdapterManifest, workbenchAdapterManifestRequiresAuth, withDefaultWorkbenchAdapterAuth, withDefaultWorkbenchAdapterAuthProfiles, } from "./adapter-manifest.js";
2
- export { ensureWorkbenchAdapterOutputDir, normalizeWorkbenchAdapterCommandRequest, readWorkbenchAdapterCommandRequest, readWorkbenchAdapterResultMetadata, workbenchAdapterResultPath, writeWorkbenchAdapterResultMetadata, } from "./adapter-protocol.js";
1
+ export { WORKBENCH_ADAPTER_MANIFEST_PROTOCOL, adapterCommandName, assertWorkbenchAdapterOperationSupport, cloneWorkbenchAdapterManifest, collectWorkbenchAdapterAuthRequirements, collectWorkbenchAdapterInvocations, collectWorkbenchAdapterOperationIssues, collectWorkbenchAdapterOperationRequirements, parseWorkbenchAdapterManifest, normalizeWorkbenchAdapterOperation, workbenchAdapterManifestSupportsOperation, workbenchAdapterOperationCommand, workbenchAdapterManifestRequiresAuth, withDefaultWorkbenchAdapterAuth, withDefaultWorkbenchAdapterAuthProfiles, } from "./adapter-manifest.js";
2
+ export { WORKBENCH_ADAPTER_PROTOCOL, WORKBENCH_ADAPTER_RESULT_FILE, WORKBENCH_ADAPTER_RESULT_PROTOCOL, assertWorkbenchAdapterOperationResultOk, ensureWorkbenchAdapterOutputDir, normalizeWorkbenchAdapterOperationRequest, normalizeWorkbenchAdapterOperationResult, readWorkbenchAdapterOperationRequest, readWorkbenchAdapterOperationResult, workbenchAdapterOperationResultPath, writeWorkbenchAdapterOperationResult, } from "./adapter-protocol.js";
3
+ export { normalizeWorkbenchEngineCase, normalizeWorkbenchEngineCaseFiles, normalizeWorkbenchEngineCaseSpec, normalizeWorkbenchEngineResolveResult, } from "./engine-resolve-result.js";
4
+ export { adapterResult, adapterSlot, adapterSlotInvocation, defineAdapter, defineEngineResolver, defineSubject, defineEngineRunner, defineOptimizer, operationDefinitionForRequest, runSubjectFromEngine, runDefinedAdapter, workbenchAdapterManifestFromDefinition, } from "./adapter-definition.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workbench-ai/workbench-protocol",
3
- "version": "0.0.43",
3
+ "version": "0.0.45",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -20,7 +20,7 @@
20
20
  ],
21
21
  "dependencies": {
22
22
  "yaml": "^2.8.2",
23
- "@workbench-ai/workbench-contract": "0.0.43"
23
+ "@workbench-ai/workbench-contract": "0.0.45"
24
24
  },
25
25
  "devDependencies": {
26
26
  "@types/node": "^24.3.1",