self-bench 0.3.2 → 0.3.4

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 (203) hide show
  1. package/.dockerignore +0 -1
  2. package/Dockerfile +2 -1
  3. package/Dockerfile.sandbox +1 -1
  4. package/README.md +114 -170
  5. package/bun.lock +60 -12
  6. package/compose.yaml +11 -5
  7. package/dist/agent-smoke-main.js +3 -2
  8. package/dist/agent-smoke-main.js.map +1 -1
  9. package/dist/agent-smoke.d.ts +2 -1
  10. package/dist/agent-smoke.d.ts.map +1 -1
  11. package/dist/agent-smoke.js +6 -1
  12. package/dist/agent-smoke.js.map +1 -1
  13. package/dist/api.d.ts +24 -0
  14. package/dist/api.d.ts.map +1 -1
  15. package/dist/api.js +65 -20
  16. package/dist/api.js.map +1 -1
  17. package/dist/artifacts.d.ts +0 -1
  18. package/dist/artifacts.d.ts.map +1 -1
  19. package/dist/artifacts.js +0 -3
  20. package/dist/artifacts.js.map +1 -1
  21. package/dist/build-metadata.js +1 -1
  22. package/dist/cli.js +75 -11
  23. package/dist/cli.js.map +1 -1
  24. package/dist/codex-review.d.ts +2 -1
  25. package/dist/codex-review.d.ts.map +1 -1
  26. package/dist/codex-review.js +26 -22
  27. package/dist/codex-review.js.map +1 -1
  28. package/dist/config.d.ts +36 -11
  29. package/dist/config.d.ts.map +1 -1
  30. package/dist/config.js +89 -17
  31. package/dist/config.js.map +1 -1
  32. package/dist/contracts.d.ts +23 -3
  33. package/dist/contracts.d.ts.map +1 -1
  34. package/dist/contracts.js +31 -6
  35. package/dist/contracts.js.map +1 -1
  36. package/dist/eval-main.js +5 -4
  37. package/dist/eval-main.js.map +1 -1
  38. package/dist/evaluate.d.ts +2 -1
  39. package/dist/evaluate.d.ts.map +1 -1
  40. package/dist/evaluate.js +14 -8
  41. package/dist/evaluate.js.map +1 -1
  42. package/dist/harbor-environment.d.ts +2 -0
  43. package/dist/harbor-environment.d.ts.map +1 -0
  44. package/dist/harbor-environment.js +15 -0
  45. package/dist/harbor-environment.js.map +1 -0
  46. package/dist/harbor-results.d.ts.map +1 -1
  47. package/dist/harbor-results.js +2 -0
  48. package/dist/harbor-results.js.map +1 -1
  49. package/dist/process.d.ts +2 -1
  50. package/dist/process.d.ts.map +1 -1
  51. package/dist/process.js.map +1 -1
  52. package/dist/provenance.d.ts +0 -1
  53. package/dist/provenance.d.ts.map +1 -1
  54. package/dist/provenance.js.map +1 -1
  55. package/dist/providers.d.ts +8 -0
  56. package/dist/providers.d.ts.map +1 -0
  57. package/dist/providers.js +14 -0
  58. package/dist/providers.js.map +1 -0
  59. package/dist/reaudit-main.js +14 -11
  60. package/dist/reaudit-main.js.map +1 -1
  61. package/dist/repair-main.js +12 -9
  62. package/dist/repair-main.js.map +1 -1
  63. package/dist/review/assets/index-CZ3G759O.js +1568 -0
  64. package/dist/review/assets/index-DgSLzX8l.css +1 -0
  65. package/dist/review/assets/{mojo-Blby8dQK.js → mojo-DJz3ZmWd.js} +1 -1
  66. package/dist/review/assets/typst-BUadGCkm.js +1 -0
  67. package/dist/review/index.html +2 -2
  68. package/dist/sandbox-author.bundle.js +40 -6
  69. package/dist/sandbox-repair.bundle.js +8 -7
  70. package/dist/sandbox-repair.js +7 -7
  71. package/dist/sandbox-repair.js.map +1 -1
  72. package/dist/sandbox-review.bundle.js +29 -25
  73. package/dist/sandbox-review.js +5 -3
  74. package/dist/sandbox-review.js.map +1 -1
  75. package/dist/sandbox-timeout.d.ts +11 -0
  76. package/dist/sandbox-timeout.d.ts.map +1 -0
  77. package/dist/sandbox-timeout.js +41 -0
  78. package/dist/sandbox-timeout.js.map +1 -0
  79. package/dist/sandbox-validation-repair.bundle.js +49 -15
  80. package/dist/sandbox-validation-repair.js +11 -9
  81. package/dist/sandbox-validation-repair.js.map +1 -1
  82. package/dist/sandbox.d.ts +2 -2
  83. package/dist/sandbox.d.ts.map +1 -1
  84. package/dist/sandbox.js +10 -3
  85. package/dist/sandbox.js.map +1 -1
  86. package/dist/subscription-auth.d.ts +12 -1
  87. package/dist/subscription-auth.d.ts.map +1 -1
  88. package/dist/subscription-auth.js +16 -16
  89. package/dist/subscription-auth.js.map +1 -1
  90. package/dist/{activities.d.ts → temporal/activities.d.ts} +3 -4
  91. package/dist/temporal/activities.d.ts.map +1 -0
  92. package/dist/{activities.js → temporal/activities.js} +51 -34
  93. package/dist/temporal/activities.js.map +1 -0
  94. package/dist/{temporal.d.ts → temporal/connection.d.ts} +2 -2
  95. package/dist/temporal/connection.d.ts.map +1 -0
  96. package/dist/{temporal.js → temporal/connection.js} +1 -1
  97. package/dist/temporal/connection.js.map +1 -0
  98. package/dist/{worker-main.d.ts.map → temporal/worker-main.d.ts.map} +1 -1
  99. package/dist/{worker-main.js → temporal/worker-main.js} +6 -10
  100. package/dist/temporal/worker-main.js.map +1 -0
  101. package/dist/{workflow.d.ts → temporal/workflow.d.ts} +1 -1
  102. package/dist/temporal/workflow.d.ts.map +1 -0
  103. package/dist/temporal/workflow.js.map +1 -0
  104. package/dist/terminal-prompts.d.ts +26 -0
  105. package/dist/terminal-prompts.d.ts.map +1 -0
  106. package/dist/terminal-prompts.js +137 -0
  107. package/dist/terminal-prompts.js.map +1 -0
  108. package/dist/terminal-reporter.d.ts +30 -0
  109. package/dist/terminal-reporter.d.ts.map +1 -0
  110. package/dist/terminal-reporter.js +90 -0
  111. package/dist/terminal-reporter.js.map +1 -0
  112. package/dist/validate-main.js +8 -2
  113. package/dist/validate-main.js.map +1 -1
  114. package/dist/vercel-cli.d.ts +64 -0
  115. package/dist/vercel-cli.d.ts.map +1 -0
  116. package/dist/vercel-cli.js +303 -0
  117. package/dist/vercel-cli.js.map +1 -0
  118. package/dist/vercel-command.d.ts +14 -0
  119. package/dist/vercel-command.d.ts.map +1 -0
  120. package/dist/vercel-command.js +93 -0
  121. package/dist/vercel-command.js.map +1 -0
  122. package/dist/vercel-executor.d.ts +14 -0
  123. package/dist/vercel-executor.d.ts.map +1 -0
  124. package/dist/vercel-executor.js +356 -0
  125. package/dist/vercel-executor.js.map +1 -0
  126. package/dist/vercel-fetch.d.ts +6 -0
  127. package/dist/vercel-fetch.d.ts.map +1 -0
  128. package/dist/vercel-fetch.js +39 -0
  129. package/dist/vercel-fetch.js.map +1 -0
  130. package/dist/vercel-profile.d.ts +34 -0
  131. package/dist/vercel-profile.d.ts.map +1 -0
  132. package/dist/vercel-profile.js +244 -0
  133. package/dist/vercel-profile.js.map +1 -0
  134. package/dist/vercel-runtime-image.d.ts +51 -0
  135. package/dist/vercel-runtime-image.d.ts.map +1 -0
  136. package/dist/vercel-runtime-image.js +123 -0
  137. package/dist/vercel-runtime-image.js.map +1 -0
  138. package/dist/vercel-setup-probe.d.ts +49 -0
  139. package/dist/vercel-setup-probe.d.ts.map +1 -0
  140. package/dist/vercel-setup-probe.js +151 -0
  141. package/dist/vercel-setup-probe.js.map +1 -0
  142. package/dist/vercel-setup.d.ts +42 -0
  143. package/dist/vercel-setup.d.ts.map +1 -0
  144. package/dist/vercel-setup.js +280 -0
  145. package/dist/vercel-setup.js.map +1 -0
  146. package/docs/evaluations.md +15 -12
  147. package/docs/operations.md +134 -21
  148. package/package.json +8 -6
  149. package/src/agent-smoke-main.ts +3 -2
  150. package/src/agent-smoke.ts +8 -2
  151. package/src/api.ts +71 -20
  152. package/src/artifacts.ts +0 -4
  153. package/src/cli.ts +81 -11
  154. package/src/codex-review.ts +63 -54
  155. package/src/config.ts +150 -28
  156. package/src/contracts.ts +33 -11
  157. package/src/eval-main.ts +5 -4
  158. package/src/evaluate.ts +18 -11
  159. package/src/harbor-environment.ts +17 -0
  160. package/src/harbor-results.ts +2 -0
  161. package/src/process.ts +3 -1
  162. package/src/provenance.ts +0 -1
  163. package/src/providers.ts +22 -0
  164. package/src/reaudit-main.ts +17 -11
  165. package/src/repair-main.ts +15 -9
  166. package/src/sandbox-repair.ts +7 -11
  167. package/src/sandbox-review.ts +5 -3
  168. package/src/sandbox-timeout.ts +56 -0
  169. package/src/sandbox-validation-repair.ts +13 -10
  170. package/src/sandbox.ts +15 -5
  171. package/src/subscription-auth.ts +30 -17
  172. package/src/{activities.ts → temporal/activities.ts} +53 -42
  173. package/src/{temporal.ts → temporal/connection.ts} +1 -1
  174. package/src/{worker-main.ts → temporal/worker-main.ts} +6 -10
  175. package/src/{workflow.ts → temporal/workflow.ts} +10 -10
  176. package/src/terminal-prompts.ts +197 -0
  177. package/src/terminal-reporter.ts +126 -0
  178. package/src/validate-main.ts +9 -3
  179. package/src/vercel-cli.ts +403 -0
  180. package/src/vercel-command.ts +127 -0
  181. package/src/vercel-executor.ts +448 -0
  182. package/src/vercel-fetch.ts +45 -0
  183. package/src/vercel-profile.ts +319 -0
  184. package/src/vercel-runtime-image.ts +204 -0
  185. package/src/vercel-setup-probe.ts +229 -0
  186. package/src/vercel-setup.ts +419 -0
  187. package/dist/activities.d.ts.map +0 -1
  188. package/dist/activities.js.map +0 -1
  189. package/dist/extensions/review.d.ts +0 -3
  190. package/dist/extensions/review.d.ts.map +0 -1
  191. package/dist/extensions/review.js +0 -44
  192. package/dist/extensions/review.js.map +0 -1
  193. package/dist/review/assets/index-Dq-6DFzI.css +0 -1
  194. package/dist/review/assets/index-DurW_RD7.js +0 -1567
  195. package/dist/review/assets/typst-DHCkPAjA.js +0 -1
  196. package/dist/temporal.d.ts.map +0 -1
  197. package/dist/temporal.js.map +0 -1
  198. package/dist/worker-main.js.map +0 -1
  199. package/dist/workflow.d.ts.map +0 -1
  200. package/dist/workflow.js.map +0 -1
  201. package/src/extensions/review.ts +0 -54
  202. /package/dist/{worker-main.d.ts → temporal/worker-main.d.ts} +0 -0
  203. /package/dist/{workflow.js → temporal/workflow.js} +0 -0
@@ -28,45 +28,54 @@ export const couplingReviewSchema = z.object({
28
28
  export type CouplingReview = z.infer<typeof couplingReviewSchema>;
29
29
 
30
30
  export async function reviewCouplingWithCodex(input: {
31
- readonly authJson: string;
31
+ readonly apiKey?: string;
32
+ readonly authJson?: string;
32
33
  readonly prompt: string;
33
34
  readonly signal?: AbortSignal;
34
35
  readonly fetch?: typeof fetch;
35
36
  }): Promise<CouplingReview> {
36
- const credential = parseCredential(input.authJson);
37
- const accountId = accountIdFromToken(credential.access);
37
+ const subscription =
38
+ !input.apiKey && input.authJson ? parseCredential(input.authJson) : undefined;
39
+ if (!input.apiKey && !subscription) {
40
+ throw new Error("OpenAI model authentication is required");
41
+ }
38
42
  const request = input.fetch ?? fetch;
39
- const response = await request("https://chatgpt.com/backend-api/codex/responses", {
40
- method: "POST",
41
- headers: {
42
- Authorization: `Bearer ${credential.access}`,
43
- "ChatGPT-Account-Id": accountId,
44
- Originator: "selfbench",
45
- "OpenAI-Beta": "responses=experimental",
46
- Accept: "text/event-stream",
47
- "Content-Type": "application/json",
43
+ const response = await request(
44
+ subscription
45
+ ? "https://chatgpt.com/backend-api/codex/responses"
46
+ : "https://api.openai.com/v1/responses",
47
+ {
48
+ method: "POST",
49
+ headers: {
50
+ Authorization: `Bearer ${subscription?.access ?? input.apiKey}`,
51
+ ...(subscription ? { "ChatGPT-Account-Id": accountIdFromToken(subscription.access) } : {}),
52
+ Originator: "selfbench",
53
+ "OpenAI-Beta": "responses=experimental",
54
+ Accept: "text/event-stream",
55
+ "Content-Type": "application/json",
56
+ },
57
+ body: JSON.stringify({
58
+ model: COUPLING_REVIEW_MODEL,
59
+ store: false,
60
+ stream: true,
61
+ instructions:
62
+ "You are an independent benchmark-quality reviewer. Analyze the supplied evidence and call submit_review exactly once.",
63
+ input: [
64
+ {
65
+ role: "user",
66
+ content: [{ type: "input_text", text: input.prompt }],
67
+ },
68
+ ],
69
+ reasoning: { effort: "high", summary: "auto" },
70
+ text: { verbosity: "low" },
71
+ include: ["reasoning.encrypted_content"],
72
+ tools: [reviewTool()],
73
+ tool_choice: { type: "function", name: "submit_review" },
74
+ parallel_tool_calls: false,
75
+ }),
76
+ ...(input.signal ? { signal: input.signal } : {}),
48
77
  },
49
- body: JSON.stringify({
50
- model: COUPLING_REVIEW_MODEL,
51
- store: false,
52
- stream: true,
53
- instructions:
54
- "You are an independent benchmark-quality reviewer. Analyze the supplied evidence and call submit_review exactly once.",
55
- input: [
56
- {
57
- role: "user",
58
- content: [{ type: "input_text", text: input.prompt }],
59
- },
60
- ],
61
- reasoning: { effort: "high", summary: "auto" },
62
- text: { verbosity: "low" },
63
- include: ["reasoning.encrypted_content"],
64
- tools: [reviewTool()],
65
- tool_choice: { type: "function", name: "submit_review" },
66
- parallel_tool_calls: false,
67
- }),
68
- ...(input.signal ? { signal: input.signal } : {}),
69
- });
78
+ );
70
79
  const responseText = await response.text();
71
80
  if (!response.ok) {
72
81
  throw new Error(
@@ -76,6 +85,27 @@ export async function reviewCouplingWithCodex(input: {
76
85
  return parseCodexReviewEvents(responseText);
77
86
  }
78
87
 
88
+ function parseCredential(value: string): { access: string } {
89
+ const parsed = JSON.parse(value) as unknown;
90
+ const credential = isRecord(parsed) ? parsed["openai-codex"] : undefined;
91
+ if (!isRecord(credential) || typeof credential.access !== "string") {
92
+ throw new Error("Pi auth does not contain an OpenAI Codex subscription access token");
93
+ }
94
+ return { access: credential.access };
95
+ }
96
+
97
+ function accountIdFromToken(token: string): string {
98
+ const payload = JSON.parse(
99
+ Buffer.from(token.split(".")[1] ?? "", "base64url").toString("utf8"),
100
+ ) as unknown;
101
+ const auth = isRecord(payload) ? payload["https://api.openai.com/auth"] : undefined;
102
+ const accountId = isRecord(auth) ? auth.chatgpt_account_id : undefined;
103
+ if (typeof accountId !== "string" || !accountId) {
104
+ throw new Error("OpenAI Codex subscription token has no ChatGPT account ID");
105
+ }
106
+ return accountId;
107
+ }
108
+
79
109
  export function parseCodexReviewEvents(value: string): CouplingReview {
80
110
  for (const line of value.split("\n")) {
81
111
  if (!line.startsWith("data: ")) {
@@ -148,27 +178,6 @@ ${goldPatch}
148
178
  `;
149
179
  }
150
180
 
151
- function parseCredential(value: string): { access: string } {
152
- const parsed = JSON.parse(value) as unknown;
153
- const credential = isRecord(parsed) ? parsed["openai-codex"] : undefined;
154
- if (!isRecord(credential) || typeof credential.access !== "string") {
155
- throw new Error("Pi auth does not contain an OpenAI Codex subscription access token");
156
- }
157
- return { access: credential.access };
158
- }
159
-
160
- function accountIdFromToken(token: string): string {
161
- const payload = JSON.parse(
162
- Buffer.from(token.split(".")[1] ?? "", "base64url").toString("utf8"),
163
- ) as unknown;
164
- const auth = isRecord(payload) ? payload["https://api.openai.com/auth"] : undefined;
165
- const accountId = isRecord(auth) ? auth.chatgpt_account_id : undefined;
166
- if (typeof accountId !== "string" || !accountId) {
167
- throw new Error("OpenAI Codex subscription token has no ChatGPT account ID");
168
- }
169
- return accountId;
170
- }
171
-
172
181
  function reviewTool(): Record<string, unknown> {
173
182
  return {
174
183
  type: "function",
package/src/config.ts CHANGED
@@ -1,20 +1,37 @@
1
1
  import { z } from "zod";
2
+ import {
3
+ EXECUTION_BACKENDS,
4
+ type ExecutionBackend,
5
+ HARBOR_ENVIRONMENTS,
6
+ type HarborEnvironment,
7
+ matchingHarborEnvironment,
8
+ } from "./providers.js";
9
+ import { parseSandboxTimeoutCapText, STANDARD_VERCEL_TIMEOUT_CAP_MS } from "./sandbox-timeout.js";
10
+
11
+ const emptyStringAsUndefined = (value: unknown): unknown =>
12
+ typeof value === "string" && value.trim() === "" ? undefined : value;
2
13
 
3
14
  const environmentSchema = z.object({
4
15
  SELFBENCH_API_HOST: z.string().default("127.0.0.1"),
5
16
  SELFBENCH_API_PORT: z.coerce.number().int().min(1).max(65535).default(8080),
6
- SELFBENCH_API_URL: z.string().url().default("http://127.0.0.1:8080"),
7
17
  SELFBENCH_API_TOKEN: z.string().min(1).optional(),
8
18
  SELFBENCH_ARTIFACT_BACKEND: z.enum(["local", "gcs"]).default("local"),
9
19
  SELFBENCH_ARTIFACT_DIR: z.string().default(".selfbench/artifacts"),
10
20
  SELFBENCH_GCS_BUCKET: z.string().optional(),
11
21
  SELFBENCH_GCS_PREFIX: z.string().default("selfbench"),
12
- SELFBENCH_EXECUTION_BACKEND: z.enum(["docker", "modal"]).default("docker"),
22
+ SELFBENCH_EXECUTION_BACKEND: z.enum(EXECUTION_BACKENDS).default("docker"),
13
23
  SELFBENCH_DOCKER_IMAGE: z.string().default("selfbench-sandbox:local"),
14
- SELFBENCH_HARBOR_ENVIRONMENT: z.enum(["docker", "modal"]).optional(),
24
+ SELFBENCH_HARBOR_ENVIRONMENT: z.preprocess(
25
+ emptyStringAsUndefined,
26
+ z.enum(HARBOR_ENVIRONMENTS).optional(),
27
+ ),
15
28
  SELFBENCH_MODAL_APP: z.string().default("selfbench"),
16
29
  SELFBENCH_MODAL_ENVIRONMENT: z.string().optional(),
17
30
  SELFBENCH_MODAL_IMAGE: z.string().default("node:22-bookworm"),
31
+ SELFBENCH_VERCEL_IMAGE: z.preprocess(emptyStringAsUndefined, z.string().trim().min(1).optional()),
32
+ // Keep provider-specific validation in the Vercel branch so a stale Vercel
33
+ // variable cannot break an otherwise unrelated Docker or Modal worker.
34
+ SELFBENCH_VERCEL_TIMEOUT_CAP: z.preprocess(emptyStringAsUndefined, z.string().optional()),
18
35
  SELFBENCH_BUILD_COMMIT: z.preprocess(
19
36
  (value) => (value === "" ? undefined : value),
20
37
  z
@@ -22,7 +39,10 @@ const environmentSchema = z.object({
22
39
  .regex(/^[0-9a-f]{40}$/i)
23
40
  .optional(),
24
41
  ),
25
- SELFBENCH_ACTIVITY_CONCURRENCY: z.coerce.number().int().min(1).max(100).optional(),
42
+ SELFBENCH_ACTIVITY_CONCURRENCY: z.preprocess(
43
+ emptyStringAsUndefined,
44
+ z.coerce.number().int().min(1).max(100).optional(),
45
+ ),
26
46
  SELFBENCH_TASK_QUEUE: z.string().default("selfbench-dev"),
27
47
  SELFBENCH_TEMPORAL_ADDRESS: z.string().default("127.0.0.1:7233"),
28
48
  SELFBENCH_TEMPORAL_NAMESPACE: z.string().default("default"),
@@ -33,25 +53,48 @@ const environmentSchema = z.object({
33
53
  .default(false),
34
54
  });
35
55
 
56
+ export interface VercelCredentials {
57
+ readonly token: string;
58
+ readonly teamId: string;
59
+ readonly projectId: string;
60
+ }
61
+
62
+ type ExecutionConfig =
63
+ | { readonly kind: "docker"; readonly image: string }
64
+ | {
65
+ readonly kind: "modal";
66
+ readonly app: string;
67
+ readonly environment?: string;
68
+ readonly image: string;
69
+ }
70
+ | { readonly kind: "vercel"; readonly image: string; readonly timeoutCapMs: number };
71
+
72
+ type WorkerExecutionConfig =
73
+ | Exclude<ExecutionConfig, { readonly kind: "vercel" }>
74
+ | {
75
+ readonly kind: "vercel";
76
+ readonly image: string;
77
+ readonly timeoutCapMs: number;
78
+ readonly credentials: VercelCredentials;
79
+ };
80
+
81
+ const DEFAULT_ACTIVITY_CONCURRENCY = {
82
+ docker: 1,
83
+ modal: 20,
84
+ vercel: 4,
85
+ } as const satisfies Record<ExecutionBackend, number>;
86
+
36
87
  export interface SelfBenchConfig {
37
88
  readonly apiHost: string;
38
89
  readonly apiPort: number;
39
- readonly apiUrl: string;
40
90
  readonly apiToken?: string;
41
91
  readonly buildCommit?: string;
42
92
  readonly activityConcurrency: number;
43
93
  readonly artifact:
44
94
  | { readonly kind: "local"; readonly directory: string }
45
95
  | { readonly kind: "gcs"; readonly bucket: string; readonly prefix: string };
46
- readonly execution:
47
- | { readonly kind: "docker"; readonly image: string }
48
- | {
49
- readonly kind: "modal";
50
- readonly app: string;
51
- readonly environment?: string;
52
- readonly image: string;
53
- };
54
- readonly harborEnvironment: "docker" | "modal";
96
+ readonly execution: ExecutionConfig;
97
+ readonly harborEnvironment: HarborEnvironment;
55
98
  readonly temporal: {
56
99
  readonly address: string;
57
100
  readonly namespace: string;
@@ -61,6 +104,14 @@ export interface SelfBenchConfig {
61
104
  };
62
105
  }
63
106
 
107
+ export interface SelfBenchWorkerConfig extends Omit<SelfBenchConfig, "execution"> {
108
+ readonly execution: WorkerExecutionConfig;
109
+ }
110
+
111
+ export function isDigestPinnedOciImage(image: string): boolean {
112
+ return /^[^@\s]+@sha256:[0-9a-f]{64}$/i.test(image);
113
+ }
114
+
64
115
  export function loadConfig(environment: NodeJS.ProcessEnv = process.env): SelfBenchConfig {
65
116
  const value = environmentSchema.parse(environment);
66
117
  if (
@@ -78,30 +129,56 @@ export function loadConfig(environment: NodeJS.ProcessEnv = process.env): SelfBe
78
129
  }
79
130
  : { kind: "local" as const, directory: value.SELFBENCH_ARTIFACT_DIR };
80
131
 
132
+ const harborEnvironment =
133
+ value.SELFBENCH_HARBOR_ENVIRONMENT ??
134
+ matchingHarborEnvironment(value.SELFBENCH_EXECUTION_BACKEND) ??
135
+ fail(
136
+ `SELFBENCH_HARBOR_ENVIRONMENT is required for ${value.SELFBENCH_EXECUTION_BACKEND} execution`,
137
+ );
138
+ let execution: ExecutionConfig;
139
+ switch (value.SELFBENCH_EXECUTION_BACKEND) {
140
+ case "docker":
141
+ execution = { kind: "docker", image: value.SELFBENCH_DOCKER_IMAGE };
142
+ break;
143
+ case "modal":
144
+ execution = {
145
+ kind: "modal",
146
+ app: value.SELFBENCH_MODAL_APP,
147
+ image: value.SELFBENCH_MODAL_IMAGE,
148
+ ...(value.SELFBENCH_MODAL_ENVIRONMENT
149
+ ? { environment: value.SELFBENCH_MODAL_ENVIRONMENT }
150
+ : {}),
151
+ };
152
+ break;
153
+ case "vercel": {
154
+ const image =
155
+ value.SELFBENCH_VERCEL_IMAGE ??
156
+ fail("SELFBENCH_VERCEL_IMAGE is required for Vercel execution");
157
+ if (!isDigestPinnedOciImage(image)) {
158
+ fail("SELFBENCH_VERCEL_IMAGE must be pinned by sha256 digest");
159
+ }
160
+ execution = {
161
+ kind: "vercel",
162
+ image,
163
+ timeoutCapMs: vercelTimeoutCap(value.SELFBENCH_VERCEL_TIMEOUT_CAP),
164
+ };
165
+ break;
166
+ }
167
+ }
168
+
81
169
  return {
82
170
  apiHost: value.SELFBENCH_API_HOST,
83
171
  apiPort: value.SELFBENCH_API_PORT,
84
- apiUrl: value.SELFBENCH_API_URL,
85
172
  ...(value.SELFBENCH_API_TOKEN ? { apiToken: value.SELFBENCH_API_TOKEN } : {}),
86
173
  ...(value.SELFBENCH_BUILD_COMMIT
87
174
  ? { buildCommit: value.SELFBENCH_BUILD_COMMIT.toLowerCase() }
88
175
  : {}),
89
176
  activityConcurrency:
90
177
  value.SELFBENCH_ACTIVITY_CONCURRENCY ??
91
- (value.SELFBENCH_EXECUTION_BACKEND === "modal" ? 20 : 1),
178
+ DEFAULT_ACTIVITY_CONCURRENCY[value.SELFBENCH_EXECUTION_BACKEND],
92
179
  artifact,
93
- harborEnvironment: value.SELFBENCH_HARBOR_ENVIRONMENT ?? value.SELFBENCH_EXECUTION_BACKEND,
94
- execution:
95
- value.SELFBENCH_EXECUTION_BACKEND === "modal"
96
- ? {
97
- kind: "modal",
98
- app: value.SELFBENCH_MODAL_APP,
99
- image: value.SELFBENCH_MODAL_IMAGE,
100
- ...(value.SELFBENCH_MODAL_ENVIRONMENT
101
- ? { environment: value.SELFBENCH_MODAL_ENVIRONMENT }
102
- : {}),
103
- }
104
- : { kind: "docker", image: value.SELFBENCH_DOCKER_IMAGE },
180
+ harborEnvironment,
181
+ execution,
105
182
  temporal: {
106
183
  address: value.SELFBENCH_TEMPORAL_ADDRESS,
107
184
  namespace: value.SELFBENCH_TEMPORAL_NAMESPACE,
@@ -112,6 +189,51 @@ export function loadConfig(environment: NodeJS.ProcessEnv = process.env): SelfBe
112
189
  };
113
190
  }
114
191
 
192
+ export function loadWorkerConfig(
193
+ environment: NodeJS.ProcessEnv = process.env,
194
+ ): SelfBenchWorkerConfig {
195
+ const config = loadConfig(environment);
196
+ if (config.execution.kind !== "vercel") {
197
+ return { ...config, execution: config.execution };
198
+ }
199
+ return {
200
+ ...config,
201
+ execution: {
202
+ ...config.execution,
203
+ credentials: vercelCredentials(environment),
204
+ },
205
+ };
206
+ }
207
+
208
+ export function defaultStandaloneConcurrency(
209
+ config: Pick<SelfBenchConfig, "activityConcurrency" | "execution">,
210
+ existingDefault: number,
211
+ ): number {
212
+ return config.execution.kind === "vercel" ? config.activityConcurrency : existingDefault;
213
+ }
214
+
215
+ function vercelCredentials(environment: NodeJS.ProcessEnv): VercelCredentials {
216
+ const token = environment.VERCEL_TOKEN?.trim();
217
+ const teamId = environment.VERCEL_TEAM_ID?.trim();
218
+ const projectId = environment.VERCEL_PROJECT_ID?.trim();
219
+ if (!token || !teamId || !projectId) {
220
+ fail("VERCEL_TOKEN, VERCEL_TEAM_ID, and VERCEL_PROJECT_ID are required for Vercel execution");
221
+ }
222
+ return { token, teamId, projectId };
223
+ }
224
+
225
+ function vercelTimeoutCap(value: string | undefined): number {
226
+ if (value === undefined) {
227
+ return STANDARD_VERCEL_TIMEOUT_CAP_MS;
228
+ }
229
+ return z
230
+ .number()
231
+ .int()
232
+ .min(100)
233
+ .max(STANDARD_VERCEL_TIMEOUT_CAP_MS)
234
+ .parse(parseSandboxTimeoutCapText(value));
235
+ }
236
+
115
237
  function fail(message: string): never {
116
238
  throw new Error(message);
117
239
  }
package/src/contracts.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { EXECUTION_BACKENDS, HARBOR_ENVIRONMENTS, matchingHarborEnvironment } from "./providers.js";
2
3
 
3
4
  export const commitSchema = z.string().regex(/^[0-9a-f]{40}$/i, "expected a full commit SHA");
4
5
 
@@ -34,6 +35,37 @@ const candidateCountsSchema = z
34
35
  message: "at most 100 candidates may be requested",
35
36
  });
36
37
 
38
+ const runVersionSchema = z
39
+ .object({
40
+ selfbenchCommit: commitSchema,
41
+ executionBackend: z.enum(EXECUTION_BACKENDS),
42
+ harborEnvironment: z.enum(HARBOR_ENVIRONMENTS).optional(),
43
+ sandboxImage: z.string().min(1),
44
+ sandboxTimeoutCapMs: z.number().int().min(100).optional(),
45
+ schema: z.literal(1),
46
+ })
47
+ .transform((version, context) => {
48
+ const harborEnvironment =
49
+ version.harborEnvironment ?? matchingHarborEnvironment(version.executionBackend);
50
+ if (!harborEnvironment) {
51
+ context.addIssue({
52
+ code: "custom",
53
+ message: `harborEnvironment is required for ${version.executionBackend} execution`,
54
+ path: ["harborEnvironment"],
55
+ });
56
+ return z.NEVER;
57
+ }
58
+ if (version.sandboxTimeoutCapMs !== undefined && version.executionBackend !== "vercel") {
59
+ context.addIssue({
60
+ code: "custom",
61
+ message: "sandboxTimeoutCapMs is only valid for Vercel execution",
62
+ path: ["sandboxTimeoutCapMs"],
63
+ });
64
+ return z.NEVER;
65
+ }
66
+ return { ...version, harborEnvironment };
67
+ });
68
+
37
69
  export const runRequestSchema = z.object({
38
70
  runId: z.string().regex(/^[a-z0-9][a-z0-9-]{2,62}$/),
39
71
  repository: repositoryRefSchema,
@@ -44,12 +76,7 @@ export const runRequestSchema = z.object({
44
76
  model: z.string().min(1),
45
77
  reasoningEffort: z.literal("high"),
46
78
  }),
47
- version: z.object({
48
- selfbenchCommit: commitSchema,
49
- executionBackend: z.enum(["docker", "modal"]),
50
- sandboxImage: z.string().min(1),
51
- schema: z.literal(1),
52
- }),
79
+ version: runVersionSchema,
53
80
  });
54
81
 
55
82
  export type RunRequest = z.infer<typeof runRequestSchema>;
@@ -130,15 +157,10 @@ export type RunPhase =
130
157
  | "queued"
131
158
  | "discovering"
132
159
  | "authoring"
133
- | "validating"
134
- | "reviewing"
135
- | "repairing"
136
- | "auditing"
137
160
  | "exporting"
138
161
  | "complete"
139
162
  | "blocked"
140
163
  | "failed"
141
- | "cancelling"
142
164
  | "cancelled";
143
165
 
144
166
  export interface TaskProgress {
package/src/eval-main.ts CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { parseArgs } from "node:util";
4
4
  import { MATRIX_MODELS, type MatrixModel, runMatrix } from "./evaluate.js";
5
+ import { isHarborEnvironment } from "./providers.js";
5
6
 
6
7
  const parsed = parseArgs({
7
8
  options: {
@@ -18,7 +19,7 @@ const parsed = parseArgs({
18
19
  strict: true,
19
20
  });
20
21
  if (parsed.values.help) {
21
- console.log(`Run Harbor tasks through the fixed Codex subscription model matrix.
22
+ console.log(`Run Harbor tasks through the fixed Codex model matrix.
22
23
 
23
24
  Usage:
24
25
  self-bench-eval (--export FILE.tar.gz | --tasks DIRECTORY) --jobs DIRECTORY [options]
@@ -29,12 +30,12 @@ Options:
29
30
  --environment docker|modal Execution environment (default: modal)
30
31
  --concurrency N Concurrent trials (default: 3)
31
32
  --model MODEL Run only this model; may be repeated
32
- --auth FILE Codex ChatGPT auth.json path
33
+ --auth FILE Optional Codex ChatGPT auth.json fallback
33
34
  -h, --help Show this help`);
34
35
  process.exit(0);
35
36
  }
36
- const environment = parsed.values.environment;
37
- if (environment !== "docker" && environment !== "modal") {
37
+ const environment = parsed.values.environment ?? "modal";
38
+ if (!isHarborEnvironment(environment)) {
38
39
  throw new Error("--environment must be docker or modal");
39
40
  }
40
41
  const concurrency = positiveInteger(parsed.values.concurrency, "--concurrency");
package/src/evaluate.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  import { cp, mkdir, mkdtemp, readdir, readFile, rm, writeFile } from "node:fs/promises";
2
2
  import { homedir, tmpdir } from "node:os";
3
3
  import { basename, join, resolve } from "node:path";
4
+ import { harborChildEnvironment } from "./harbor-environment.js";
4
5
  import { archiveIncompleteHarborJob, tryReadHarborJobResult } from "./harbor-results.js";
5
6
  import { parallelMap } from "./parallel.js";
6
7
  import { runCommand } from "./process.js";
7
- import { assertCodexSubscriptionAuth } from "./subscription-auth.js";
8
+ import type { HarborEnvironment } from "./providers.js";
9
+ import { assertCodexSubscriptionAuth, openAiApiKey } from "./subscription-auth.js";
8
10
 
9
11
  export const MATRIX_MODELS = ["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"] as const;
10
12
  export type MatrixModel = (typeof MATRIX_MODELS)[number];
@@ -15,7 +17,7 @@ export interface MatrixOptions {
15
17
  readonly tasksPath?: string;
16
18
  readonly jobsDirectory: string;
17
19
  readonly harborPath?: string;
18
- readonly environment?: "docker" | "modal";
20
+ readonly environment?: HarborEnvironment;
19
21
  readonly concurrency?: number;
20
22
  readonly authPath?: string;
21
23
  readonly models?: readonly MatrixModel[];
@@ -40,8 +42,11 @@ export async function runMatrix(options: MatrixOptions): Promise<readonly Matrix
40
42
  const tasksDirectory = join(root, "tasks");
41
43
  await mkdir(root, { recursive: true });
42
44
  const taskDirectories = await resolveMatrixTasks(options, tasksDirectory);
45
+ const apiKey = openAiApiKey();
43
46
  const authPath = resolve(options.authPath ?? join(homedir(), ".codex/auth.json"));
44
- await assertSubscriptionAuth(authPath);
47
+ if (!apiKey) {
48
+ await assertSubscriptionAuth(authPath);
49
+ }
45
50
  const models = options.models ?? MATRIX_MODELS;
46
51
  if (models.length < 1) {
47
52
  throw new Error("provide at least one evaluation model");
@@ -55,7 +60,7 @@ export async function runMatrix(options: MatrixOptions): Promise<readonly Matrix
55
60
  jobsDirectory: root,
56
61
  harborPath: options.harborPath ?? "harbor",
57
62
  environment: options.environment ?? "modal",
58
- authPath,
63
+ ...(!apiKey ? { authPath } : {}),
59
64
  ...item,
60
65
  });
61
66
  completed += 1;
@@ -69,7 +74,7 @@ export async function runMatrix(options: MatrixOptions): Promise<readonly Matrix
69
74
  schemaVersion: 1,
70
75
  agent: "codex",
71
76
  agentVersion: CODEX_VERSION,
72
- auth: "codex-subscription",
77
+ auth: apiKey ? "openai-api-key" : "codex-subscription",
73
78
  reasoningEffort: "high",
74
79
  models,
75
80
  taskCount: taskDirectories.length,
@@ -111,8 +116,8 @@ async function runTrial(input: {
111
116
  readonly model: MatrixModel;
112
117
  readonly jobsDirectory: string;
113
118
  readonly harborPath: string;
114
- readonly environment: "docker" | "modal";
115
- readonly authPath: string;
119
+ readonly environment: HarborEnvironment;
120
+ readonly authPath?: string;
116
121
  }): Promise<MatrixTrialSummary> {
117
122
  const taskId = basename(input.taskDirectory);
118
123
  const jobName = `${taskId}-${input.model}`.toLowerCase().replace(/[^a-z0-9_.-]/g, "-");
@@ -122,10 +127,12 @@ async function runTrial(input: {
122
127
  }
123
128
  await archiveIncompleteHarborJob(input.jobsDirectory, jobName);
124
129
 
125
- const environment = { ...process.env };
126
- delete environment.OPENAI_API_KEY;
127
- environment.CODEX_FORCE_AUTH_JSON = "1";
128
- environment.CODEX_AUTH_JSON_PATH = input.authPath;
130
+ const environment = harborChildEnvironment();
131
+ if (input.authPath) {
132
+ delete environment.OPENAI_API_KEY;
133
+ environment.CODEX_FORCE_AUTH_JSON = "1";
134
+ environment.CODEX_AUTH_JSON_PATH = input.authPath;
135
+ }
129
136
  let result: Awaited<ReturnType<typeof runCommand>>;
130
137
  try {
131
138
  result = await runCommand(
@@ -0,0 +1,17 @@
1
+ const VERCEL_CONTROL_CREDENTIALS = [
2
+ "VERCEL_AUTH_TOKEN",
3
+ "VERCEL_TOKEN",
4
+ "VERCEL_TEAM_ID",
5
+ "VERCEL_PROJECT_ID",
6
+ "VERCEL_OIDC_TOKEN",
7
+ ] as const;
8
+
9
+ export function harborChildEnvironment(
10
+ environment: NodeJS.ProcessEnv = process.env,
11
+ ): NodeJS.ProcessEnv {
12
+ const child = { ...environment };
13
+ for (const key of VERCEL_CONTROL_CREDENTIALS) {
14
+ delete child[key];
15
+ }
16
+ return child;
17
+ }
@@ -20,6 +20,8 @@ const infrastructurePatterns = [
20
20
  /modal.*(?:unavailable|timed out|timeout)/i,
21
21
  /image build for im-[a-z0-9]+ failed/i,
22
22
  /all predefined address pools have been fully subnetted/i,
23
+ /mounts denied/i,
24
+ /network-policy[\s\S]*could not process rule/i,
23
25
  ];
24
26
 
25
27
  class IncompleteHarborJobError extends Error {}
package/src/process.ts CHANGED
@@ -6,6 +6,8 @@ export interface CommandResult {
6
6
  readonly exitCode: number;
7
7
  }
8
8
 
9
+ export type CommandOutputHandler = (stream: "stdout" | "stderr", chunk: Uint8Array) => void;
10
+
9
11
  export interface CommandOptions {
10
12
  readonly cwd?: string;
11
13
  readonly env?: NodeJS.ProcessEnv;
@@ -14,7 +16,7 @@ export interface CommandOptions {
14
16
  readonly inactivityTimeoutMs?: number;
15
17
  readonly allowFailure?: boolean;
16
18
  readonly signal?: AbortSignal;
17
- readonly onOutput?: (stream: "stdout" | "stderr", chunk: Uint8Array) => void;
19
+ readonly onOutput?: CommandOutputHandler;
18
20
  }
19
21
 
20
22
  export class InactivityTimeoutError extends Error {
package/src/provenance.ts CHANGED
@@ -4,7 +4,6 @@ import { assertPullRequestBelongsToRepository, githubRepository } from "./github
4
4
  import { runCommand } from "./process.js";
5
5
 
6
6
  export type SessionProvenanceFormat = "codex" | "claude-code" | "pi" | "generic";
7
- export type ProvenanceFormat = SessionProvenanceFormat | "github-pull-request";
8
7
 
9
8
  interface ProvenanceMessageBase {
10
9
  readonly sessionId: string;
@@ -0,0 +1,22 @@
1
+ export const EXECUTION_BACKENDS = ["docker", "modal", "vercel"] as const;
2
+ export type ExecutionBackend = (typeof EXECUTION_BACKENDS)[number];
3
+
4
+ export const HARBOR_ENVIRONMENTS = ["docker", "modal"] as const;
5
+ export type HarborEnvironment = (typeof HARBOR_ENVIRONMENTS)[number];
6
+
7
+ const executionBackends: ReadonlySet<string> = new Set(EXECUTION_BACKENDS);
8
+ const harborEnvironments: ReadonlySet<string> = new Set(HARBOR_ENVIRONMENTS);
9
+
10
+ export function isExecutionBackend(value: string): value is ExecutionBackend {
11
+ return executionBackends.has(value);
12
+ }
13
+
14
+ export function isHarborEnvironment(value: string): value is HarborEnvironment {
15
+ return harborEnvironments.has(value);
16
+ }
17
+
18
+ export function matchingHarborEnvironment(
19
+ backend: ExecutionBackend,
20
+ ): HarborEnvironment | undefined {
21
+ return isHarborEnvironment(backend) ? backend : undefined;
22
+ }