@workbench-ai/workbench-protocol 0.0.43 → 0.0.44

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,160 @@
1
+ import path from "node:path";
2
+ export function normalizeWorkbenchTaskSourceResult(value, label = "tasks.resolve result") {
3
+ const record = requiredRecord(value, label);
4
+ if (!Array.isArray(record.tasks)) {
5
+ throw new Error(`${label}.tasks must be an array.`);
6
+ }
7
+ const tasks = record.tasks.map((entry, index) => normalizeWorkbenchTaskBundle(entry, `${label}.tasks[${index}]`));
8
+ const duplicate = firstDuplicate(tasks.map((task) => task.id));
9
+ if (duplicate) {
10
+ throw new Error(`${label} contains duplicate task id: ${duplicate}`);
11
+ }
12
+ return {
13
+ tasks,
14
+ ...(record.environment !== undefined
15
+ ? { environment: normalizeTaskEnvironment(record.environment, `${label}.environment`) }
16
+ : {}),
17
+ };
18
+ }
19
+ export function normalizeWorkbenchTaskBundle(value, label) {
20
+ const record = requiredRecord(value, label);
21
+ if (typeof record.id !== "string" || record.id.trim().length === 0) {
22
+ throw new Error(`${label}.id must be a non-empty string.`);
23
+ }
24
+ return {
25
+ id: normalizeRelativePath(record.id, `${label}.id`),
26
+ task: normalizeWorkbenchTaskSpec(record.task, `${label}.task`),
27
+ publicFiles: normalizeTaskSourceFiles(record.publicFiles, `${label}.publicFiles`),
28
+ testFiles: normalizeTaskSourceFiles(record.testFiles, `${label}.testFiles`),
29
+ ...(record.solutionFiles !== undefined
30
+ ? { solutionFiles: normalizeTaskSourceFiles(record.solutionFiles, `${label}.solutionFiles`) }
31
+ : {}),
32
+ ...(record.sourceFiles !== undefined
33
+ ? { sourceFiles: normalizeTaskSourceFiles(record.sourceFiles, `${label}.sourceFiles`) }
34
+ : {}),
35
+ };
36
+ }
37
+ export function normalizeWorkbenchTaskSpec(value, label) {
38
+ const record = requiredRecord(value, label);
39
+ if (record.version !== 2) {
40
+ throw new Error(`${label}.version must be 2.`);
41
+ }
42
+ if (typeof record.task !== "string" || record.task.trim().length === 0) {
43
+ throw new Error(`${label}.task must be a non-empty string.`);
44
+ }
45
+ return {
46
+ version: 2,
47
+ task: record.task,
48
+ ...(record.environment !== undefined
49
+ ? { environment: normalizeTaskEnvironment(record.environment, `${label}.environment`) }
50
+ : {}),
51
+ ...(record.score !== undefined
52
+ ? { score: normalizeAdapterInvocation(record.score, `${label}.score`) }
53
+ : {}),
54
+ };
55
+ }
56
+ function normalizeTaskEnvironment(value, label) {
57
+ const record = requiredRecord(value, label);
58
+ const resources = record.resources === undefined
59
+ ? undefined
60
+ : normalizeTaskResources(record.resources, `${label}.resources`);
61
+ const network = record.network === undefined
62
+ ? undefined
63
+ : normalizeNetworkPolicy(record.network, `${label}.network`);
64
+ return {
65
+ ...(typeof record.dockerfile === "string" && record.dockerfile.trim()
66
+ ? { dockerfile: record.dockerfile }
67
+ : {}),
68
+ ...(typeof record.workdir === "string" && record.workdir.trim()
69
+ ? { workdir: record.workdir }
70
+ : {}),
71
+ ...(resources ? { resources } : {}),
72
+ ...(network ? { network } : {}),
73
+ };
74
+ }
75
+ function normalizeTaskResources(value, label) {
76
+ const record = requiredRecord(value, label);
77
+ return {
78
+ ...(typeof record.cpu === "number" ? { cpu: record.cpu } : {}),
79
+ ...(typeof record.memoryGb === "number" ? { memoryGb: record.memoryGb } : {}),
80
+ ...(typeof record.diskGb === "number" ? { diskGb: record.diskGb } : {}),
81
+ ...(typeof record.timeoutMinutes === "number" ? { timeoutMinutes: record.timeoutMinutes } : {}),
82
+ };
83
+ }
84
+ function normalizeNetworkPolicy(value, label) {
85
+ const record = requiredRecord(value, label);
86
+ if (record.egress !== "none" &&
87
+ record.egress !== "allowlist" &&
88
+ record.egress !== "open") {
89
+ throw new Error(`${label}.egress must be none, allowlist, or open.`);
90
+ }
91
+ return {
92
+ egress: record.egress,
93
+ ...(Array.isArray(record.allow)
94
+ ? { allow: record.allow.filter((entry) => typeof entry === "string") }
95
+ : {}),
96
+ };
97
+ }
98
+ function normalizeAdapterInvocation(value, label) {
99
+ const record = requiredRecord(value, label);
100
+ if (typeof record.use !== "string" || record.use.trim().length === 0) {
101
+ throw new Error(`${label}.use must be a non-empty string.`);
102
+ }
103
+ return {
104
+ use: record.use,
105
+ with: record.with === undefined ? {} : record.with,
106
+ ...(record.auth !== undefined ? { auth: record.auth } : {}),
107
+ };
108
+ }
109
+ function normalizeTaskSourceFiles(value, label) {
110
+ if (value === undefined) {
111
+ return [];
112
+ }
113
+ if (!Array.isArray(value)) {
114
+ throw new Error(`${label} must be an array.`);
115
+ }
116
+ return value.map((entry, index) => {
117
+ const record = requiredRecord(entry, `${label}[${index}]`);
118
+ if (typeof record.path !== "string" || record.path.trim().length === 0) {
119
+ throw new Error(`${label}[${index}].path must be a non-empty string.`);
120
+ }
121
+ if (typeof record.content !== "string") {
122
+ throw new Error(`${label}[${index}].content must be a string.`);
123
+ }
124
+ const encoding = record.encoding === "base64" ? "base64" : "utf8";
125
+ const kind = record.kind === "binary" || encoding === "base64" ? "binary" : "text";
126
+ return {
127
+ path: normalizeRelativePath(record.path, `${label}[${index}].path`),
128
+ kind,
129
+ encoding,
130
+ content: record.content,
131
+ executable: record.executable === true,
132
+ };
133
+ }).sort((left, right) => left.path.localeCompare(right.path));
134
+ }
135
+ function normalizeRelativePath(filePath, label) {
136
+ const normalized = path.posix.normalize(filePath.replace(/\\/gu, "/").replace(/^\/+/u, ""));
137
+ if (!normalized || normalized === "." || normalized.includes("\0")) {
138
+ throw new Error(`${label} must be a non-empty relative path.`);
139
+ }
140
+ if (normalized === ".." || normalized.startsWith("../") || path.posix.isAbsolute(normalized)) {
141
+ throw new Error(`${label} must not escape the task-source result.`);
142
+ }
143
+ return normalized;
144
+ }
145
+ function requiredRecord(value, label) {
146
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
147
+ throw new Error(`${label} must be an object.`);
148
+ }
149
+ return value;
150
+ }
151
+ function firstDuplicate(values) {
152
+ const seen = new Set();
153
+ for (const value of values) {
154
+ if (seen.has(value)) {
155
+ return value;
156
+ }
157
+ seen.add(value);
158
+ }
159
+ return null;
160
+ }
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.44",
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.44"
24
24
  },
25
25
  "devDependencies": {
26
26
  "@types/node": "^24.3.1",