@benchsdk/runner 0.3.0 → 0.5.2

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.
package/dist/index.cjs CHANGED
@@ -30,21 +30,36 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var src_exports = {};
32
32
  __export(src_exports, {
33
+ AuthError: () => import_cli4.AuthError,
33
34
  BENCHSDK_RUNNER_VERSION: () => BENCHSDK_RUNNER_VERSION,
35
+ BenchmarkApiError: () => import_api3.BenchmarkApiError,
36
+ BenchmarkConfigError: () => BenchmarkConfigError,
37
+ BenchmarkReporter: () => import_worker3.BenchmarkReporter,
34
38
  NoAvailableParticipantsError: () => NoAvailableParticipantsError,
35
39
  ScoringSpecError: () => ScoringSpecError,
36
40
  TaskError: () => TaskError,
41
+ claimBenchmarkReporter: () => import_worker3.claimBenchmarkReporter,
42
+ createApiClient: () => import_cli4.createApiClient,
43
+ createBenchmarkClient: () => import_api3.createBenchmarkClient,
44
+ createSystemMetricsCollector: () => import_worker3.createSystemMetricsCollector,
37
45
  defineBenchmarkConfig: () => defineBenchmarkConfig,
46
+ defineOnComplete: () => defineOnComplete,
38
47
  defineTask: () => defineTask,
48
+ filterParticipantsByEnv: () => import_worker3.filterParticipantsByEnv,
39
49
  higherIsBetter: () => higherIsBetter,
40
50
  lowerIsBetter: () => lowerIsBetter,
41
51
  mergeConfig: () => mergeConfig,
42
52
  parseCliArgs: () => parseCliArgs,
53
+ resolveAuth: () => import_cli4.resolveAuth,
43
54
  run: () => run,
44
55
  runBenchmark: () => runBenchmark,
45
56
  runBenchmarkFile: () => runBenchmarkFile,
57
+ runBenchmarkWorker: () => runBenchmarkWorker,
58
+ runWorker: () => import_worker3.runWorker,
46
59
  score: () => score,
47
60
  scoringConfigToSpec: () => scoringConfigToSpec,
61
+ selectParticipants: () => import_worker3.selectParticipants,
62
+ validateBenchmarkConfig: () => validateBenchmarkConfig,
48
63
  validateScoringSpec: () => validateScoringSpec
49
64
  });
50
65
  module.exports = __toCommonJS(src_exports);
@@ -54,14 +69,55 @@ var TaskError = class extends Error {
54
69
  code;
55
70
  data;
56
71
  steps;
72
+ step;
73
+ timeoutMs;
57
74
  constructor(message, opts) {
58
75
  super(message);
59
76
  this.name = "TaskError";
60
77
  this.code = opts?.code;
61
78
  this.data = opts?.data;
62
79
  this.steps = opts?.steps;
80
+ this.step = opts?.step;
81
+ this.timeoutMs = opts?.timeoutMs;
82
+ }
83
+ toString() {
84
+ let s = `[${this.name}${this.code ? ` (${this.code})` : ""}] ${this.message}`;
85
+ if (this.step) {
86
+ s += `
87
+ step: ${this.step}`;
88
+ }
89
+ if (this.timeoutMs !== void 0) {
90
+ s += `
91
+ timeoutMs: ${this.timeoutMs}`;
92
+ }
93
+ if (this.data && Object.keys(this.data).length > 0) {
94
+ s += `
95
+ data: ${JSON.stringify(this.data, null, 2)}`;
96
+ }
97
+ return s;
98
+ }
99
+ };
100
+ var BenchmarkConfigError = class _BenchmarkConfigError extends Error {
101
+ issues;
102
+ constructor(issues) {
103
+ super(_BenchmarkConfigError.formatIssues(issues));
104
+ this.name = "BenchmarkConfigError";
105
+ this.issues = issues;
106
+ }
107
+ static formatIssues(issues) {
108
+ const lines = issues.map((i) => ` - ${i.field}: ${i.message}`);
109
+ return `Invalid benchmark config:
110
+ ${lines.join("\n")}
111
+
112
+ Fix the fields above and try again.`;
63
113
  }
64
114
  };
115
+ function assertFiniteNumber(value, field) {
116
+ if (typeof value !== "number" || !Number.isFinite(value)) {
117
+ throw new Error(`${field} must be a finite number (got ${value})`);
118
+ }
119
+ return value;
120
+ }
65
121
  function assertNonEmptyString(value, field) {
66
122
  if (typeof value !== "string" || value.trim() === "") {
67
123
  throw new Error(`${field} must be a non-empty string`);
@@ -75,18 +131,6 @@ function assertOnlyAllowedKeys(value, allowed, field) {
75
131
  }
76
132
  }
77
133
  }
78
- function assertPositiveInt(value, field) {
79
- if (value === void 0) return;
80
- if (!Number.isInteger(value) || value < 1) {
81
- throw new Error(`${field} must be an integer >= 1 (got ${value})`);
82
- }
83
- }
84
- function assertFiniteNumber(value, field) {
85
- if (typeof value !== "number" || !Number.isFinite(value)) {
86
- throw new Error(`${field} must be a finite number (got ${value})`);
87
- }
88
- return value;
89
- }
90
134
  function validateBenchmarkScoringConfig(scoring, display) {
91
135
  if (!Array.isArray(scoring.metrics) || scoring.metrics.length === 0) {
92
136
  throw new Error("scoring.metrics must be a non-empty array");
@@ -157,154 +201,236 @@ function validateBenchmarkScoringConfig(scoring, display) {
157
201
  throw new Error(`scoring metric weights must sum to 1.0 (got ${totalWeight.toFixed(3)})`);
158
202
  }
159
203
  }
204
+ function validateBenchmarkDisplayConfig(display) {
205
+ if (typeof display !== "object" || display === null || Array.isArray(display)) {
206
+ throw new Error("display must be an object");
207
+ }
208
+ assertOnlyAllowedKeys(display, ["metrics", "steps", "overview"], "display");
209
+ const displayMetricKeys = /* @__PURE__ */ new Set();
210
+ if (display.metrics !== void 0) {
211
+ if (!Array.isArray(display.metrics)) {
212
+ throw new Error("display.metrics must be an array");
213
+ }
214
+ for (let i = 0; i < display.metrics.length; i++) {
215
+ const metric = display.metrics[i];
216
+ if (metric === null || typeof metric !== "object" || Array.isArray(metric)) {
217
+ throw new Error(`display.metrics[${i}] must be an object`);
218
+ }
219
+ assertOnlyAllowedKeys(metric, ["key", "label", "unit", "direction", "decimals", "order"], `display.metrics[${i}]`);
220
+ const key = assertNonEmptyString(metric.key, `display.metrics[${i}].key`);
221
+ if (displayMetricKeys.has(key)) {
222
+ throw new Error(`duplicate display metric key: ${key}`);
223
+ }
224
+ displayMetricKeys.add(key);
225
+ assertNonEmptyString(metric.label, `display.metrics[${i}].label`);
226
+ if (metric.unit !== void 0 && typeof metric.unit !== "string") {
227
+ throw new Error(`display.metrics[${i}].unit must be a string`);
228
+ }
229
+ if (metric.direction !== void 0 && metric.direction !== "higher-better" && metric.direction !== "lower-better") {
230
+ throw new Error(`display.metrics[${i}].direction must be 'higher-better' or 'lower-better'`);
231
+ }
232
+ if (metric.decimals !== void 0 && (!Number.isInteger(metric.decimals) || metric.decimals < 0)) {
233
+ throw new Error(`display.metrics[${i}].decimals must be a non-negative integer`);
234
+ }
235
+ if (metric.order !== void 0 && (!Number.isInteger(metric.order) || metric.order < 0)) {
236
+ throw new Error(`display.metrics[${i}].order must be a non-negative integer`);
237
+ }
238
+ }
239
+ }
240
+ if (display.steps !== void 0) {
241
+ if (!Array.isArray(display.steps)) {
242
+ throw new Error("display.steps must be an array");
243
+ }
244
+ const seenStepKeys = /* @__PURE__ */ new Set();
245
+ for (let i = 0; i < display.steps.length; i++) {
246
+ const step = display.steps[i];
247
+ if (step === null || typeof step !== "object" || Array.isArray(step)) {
248
+ throw new Error(`display.steps[${i}] must be an object`);
249
+ }
250
+ assertOnlyAllowedKeys(step, ["key", "label", "order"], `display.steps[${i}]`);
251
+ const key = assertNonEmptyString(step.key, `display.steps[${i}].key`);
252
+ if (seenStepKeys.has(key)) {
253
+ throw new Error(`duplicate display step key: ${key}`);
254
+ }
255
+ seenStepKeys.add(key);
256
+ assertNonEmptyString(step.label, `display.steps[${i}].label`);
257
+ if (step.order !== void 0 && (!Number.isInteger(step.order) || step.order < 0)) {
258
+ throw new Error(`display.steps[${i}].order must be a non-negative integer`);
259
+ }
260
+ }
261
+ }
262
+ if (display.overview !== void 0) {
263
+ if (typeof display.overview !== "object" || display.overview === null || Array.isArray(display.overview)) {
264
+ throw new Error("display.overview must be an object");
265
+ }
266
+ assertOnlyAllowedKeys(display.overview, ["defaultMetric", "defaultLayout"], "display.overview");
267
+ const { defaultMetric, defaultLayout } = display.overview;
268
+ if (defaultMetric !== void 0) {
269
+ const metric = assertNonEmptyString(defaultMetric, "display.overview.defaultMetric");
270
+ const validDefaultMetrics = new Set(displayMetricKeys);
271
+ validDefaultMetrics.add("compositeScore");
272
+ validDefaultMetrics.add("task");
273
+ if (display.metrics !== void 0 && !validDefaultMetrics.has(metric)) {
274
+ throw new Error(`display.overview.defaultMetric '${metric}' is not declared in display.metrics and is not a known default (compositeScore, task)`);
275
+ }
276
+ }
277
+ if (defaultLayout !== void 0 && !["ranking", "cards", "chart", "leaderboard"].includes(defaultLayout)) {
278
+ throw new Error("display.overview.defaultLayout must be 'ranking', 'cards', 'chart', or 'leaderboard'");
279
+ }
280
+ }
281
+ }
160
282
  function defineBenchmarkConfig(config) {
283
+ const issues = validateBenchmarkConfig(config);
284
+ if (config.scoring !== void 0) {
285
+ try {
286
+ validateBenchmarkScoringConfig(config.scoring, config.display);
287
+ } catch (error) {
288
+ issues.push({
289
+ field: "scoring",
290
+ message: error instanceof Error ? error.message : String(error)
291
+ });
292
+ }
293
+ }
294
+ if (issues.length > 0) {
295
+ throw new BenchmarkConfigError(issues);
296
+ }
297
+ return config;
298
+ }
299
+ function defineTask(task) {
300
+ if (typeof task !== "function") {
301
+ throw new Error("defineTask requires a task function.");
302
+ }
303
+ return task;
304
+ }
305
+ function validateBenchmarkConfig(config) {
306
+ const issues = [];
161
307
  if (!config.benchmarkSlug || typeof config.benchmarkSlug !== "string") {
162
- throw new Error("benchmarkSlug is required");
308
+ issues.push({ field: "benchmarkSlug", message: "is required" });
163
309
  }
164
310
  if (!config.benchmarkName || typeof config.benchmarkName !== "string") {
165
- throw new Error("benchmarkName is required");
311
+ issues.push({ field: "benchmarkName", message: "is required" });
312
+ }
313
+ if (!Array.isArray(config.participants) || config.participants.length === 0) {
314
+ issues.push({ field: "participants", message: "must be a non-empty array" });
315
+ } else {
316
+ const seenParticipants = /* @__PURE__ */ new Set();
317
+ for (let i = 0; i < config.participants.length; i++) {
318
+ const p = config.participants[i];
319
+ if (p === null || typeof p !== "object" || Array.isArray(p)) {
320
+ issues.push({ field: `participants[${i}]`, message: "must be an object" });
321
+ continue;
322
+ }
323
+ const participant = p;
324
+ if (typeof participant.name !== "string" || participant.name.trim() === "") {
325
+ issues.push({ field: `participants[${i}].name`, message: "must be a non-empty string" });
326
+ } else if (seenParticipants.has(participant.name)) {
327
+ issues.push({ field: `participants[${i}].name`, message: `duplicate participant name: ${participant.name}` });
328
+ } else {
329
+ seenParticipants.add(participant.name);
330
+ }
331
+ if (participant.requiredEnvVars !== void 0 && (!Array.isArray(participant.requiredEnvVars) || !participant.requiredEnvVars.every((v) => typeof v === "string"))) {
332
+ issues.push({ field: `participants[${i}].requiredEnvVars`, message: "must be an array of strings" });
333
+ }
334
+ }
166
335
  }
167
336
  if (config.phases !== void 0) {
168
337
  if (config.iterations !== void 0) {
169
- throw new Error("phases and iterations are mutually exclusive");
338
+ issues.push({ field: "iterations", message: "phases and iterations are mutually exclusive" });
170
339
  }
171
340
  if (!Array.isArray(config.phases) || config.phases.length === 0) {
172
- throw new Error("phases must be a non-empty array");
173
- }
174
- const seen = /* @__PURE__ */ new Set();
175
- for (const phase of config.phases) {
176
- if (!phase.name || typeof phase.name !== "string") {
177
- throw new Error("each phase requires a non-empty name");
178
- }
179
- if (seen.has(phase.name)) {
180
- throw new Error(`duplicate phase name: ${phase.name}`);
341
+ issues.push({ field: "phases", message: "must be a non-empty array" });
342
+ } else {
343
+ const seen = /* @__PURE__ */ new Set();
344
+ for (let i = 0; i < config.phases.length; i++) {
345
+ const phase = config.phases[i];
346
+ if (phase === null || typeof phase !== "object" || Array.isArray(phase)) {
347
+ issues.push({ field: `phases[${i}]`, message: "must be an object" });
348
+ continue;
349
+ }
350
+ const phaseObj = phase;
351
+ if (typeof phaseObj.name !== "string" || phaseObj.name.trim() === "") {
352
+ issues.push({ field: `phases[${i}]`, message: "must have a non-empty string name" });
353
+ } else {
354
+ const name = phaseObj.name;
355
+ if (seen.has(name)) {
356
+ issues.push({ field: `phases['${name}']`, message: `duplicate phase name: ${name}` });
357
+ }
358
+ seen.add(name);
359
+ const iterations = phaseObj.iterations;
360
+ if (typeof iterations !== "number" || !Number.isInteger(iterations) || iterations < 1) {
361
+ issues.push({ field: `phases['${name}'].iterations`, message: `must be an integer >= 1 (got ${iterations})` });
362
+ }
363
+ }
181
364
  }
182
- seen.add(phase.name);
183
- assertPositiveInt(phase.iterations, `phase '${phase.name}' iterations`);
184
365
  }
185
366
  }
186
- assertPositiveInt(config.iterations, "iterations");
187
- assertPositiveInt(config.concurrency, "concurrency");
367
+ if (config.iterations !== void 0 && (!Number.isInteger(config.iterations) || config.iterations < 1)) {
368
+ issues.push({ field: "iterations", message: `must be an integer >= 1 (got ${config.iterations})` });
369
+ }
370
+ if (config.concurrency !== void 0 && (!Number.isInteger(config.concurrency) || config.concurrency < 1)) {
371
+ issues.push({ field: "concurrency", message: `must be an integer >= 1 (got ${config.concurrency})` });
372
+ }
188
373
  if (config.staggerDelayMs !== void 0 && (!Number.isFinite(config.staggerDelayMs) || config.staggerDelayMs < 0)) {
189
- throw new Error(`staggerDelayMs must be a number >= 0 (got ${config.staggerDelayMs})`);
374
+ issues.push({ field: "staggerDelayMs", message: `must be a number >= 0 (got ${config.staggerDelayMs})` });
190
375
  }
191
376
  if (config.groupBy !== void 0 && config.groupBy !== "participant" && config.groupBy !== "round") {
192
- throw new Error(`groupBy must be 'participant' or 'round' (got ${config.groupBy})`);
377
+ issues.push({ field: "groupBy", message: `must be 'participant' or 'round' (got ${config.groupBy})` });
193
378
  }
194
379
  if (config.shapes !== void 0) {
195
- for (const [shapeName, shape] of Object.entries(config.shapes)) {
196
- if (!shape.slug || !/^[a-z0-9][a-z0-9-]*$/.test(shape.slug)) {
197
- throw new Error(`shape '${shapeName}' needs a lowercase slug (got ${JSON.stringify(shape.slug)})`);
198
- }
199
- if (shape.name !== void 0 && (typeof shape.name !== "string" || shape.name.trim() === "")) {
200
- throw new Error(`shape '${shapeName}' name must be a non-empty string`);
201
- }
202
- if (shape.staggerDelayMs !== void 0 && (!Number.isFinite(shape.staggerDelayMs) || shape.staggerDelayMs < 0)) {
203
- throw new Error(`shape '${shapeName}' staggerDelayMs must be a number >= 0 (got ${shape.staggerDelayMs})`);
380
+ if (typeof config.shapes !== "object" || config.shapes === null || Array.isArray(config.shapes)) {
381
+ issues.push({ field: "shapes", message: "must be a plain object" });
382
+ } else {
383
+ for (const [shapeName, shape] of Object.entries(config.shapes)) {
384
+ if (shape === null || typeof shape !== "object" || Array.isArray(shape)) {
385
+ issues.push({ field: `shapes['${shapeName}']`, message: "must be an object" });
386
+ continue;
387
+ }
388
+ const shapeObj = shape;
389
+ const slug = shapeObj.slug;
390
+ if (typeof slug !== "string" || slug === "" || !/^[a-z0-9][a-z0-9-]*$/.test(slug)) {
391
+ issues.push({ field: `shapes['${shapeName}'].slug`, message: `needs a lowercase slug (got ${JSON.stringify(slug)})` });
392
+ }
393
+ const name = shapeObj.name;
394
+ if (name !== void 0 && (typeof name !== "string" || name.trim() === "")) {
395
+ issues.push({ field: `shapes['${shapeName}'].name`, message: "must be a non-empty string" });
396
+ }
397
+ const staggerDelayMs = shapeObj.staggerDelayMs;
398
+ if (staggerDelayMs !== void 0 && (typeof staggerDelayMs !== "number" || !Number.isFinite(staggerDelayMs) || staggerDelayMs < 0)) {
399
+ issues.push({ field: `shapes['${shapeName}'].staggerDelayMs`, message: `must be a number >= 0 (got ${staggerDelayMs})` });
400
+ }
204
401
  }
205
402
  }
206
403
  }
207
404
  if (config.dimensions !== void 0) {
208
405
  if (config.dimensions === null || typeof config.dimensions !== "object" || Array.isArray(config.dimensions)) {
209
- throw new Error("dimensions must be a plain object");
406
+ issues.push({ field: "dimensions", message: "must be a plain object" });
210
407
  }
211
408
  }
212
- if (config.scoring !== void 0) {
213
- validateBenchmarkScoringConfig(config.scoring, config.display);
214
- }
215
409
  if (config.customCliFlags !== void 0) {
216
410
  if (!Array.isArray(config.customCliFlags) || !config.customCliFlags.every((f) => typeof f === "string" && f.startsWith("--"))) {
217
- throw new Error('customCliFlags must be an array of strings starting with "--"');
411
+ issues.push({ field: "customCliFlags", message: 'must be an array of strings starting with "--"' });
218
412
  }
219
413
  }
220
414
  if (config.display !== void 0) {
221
- if (typeof config.display !== "object" || config.display === null || Array.isArray(config.display)) {
222
- throw new Error("display must be an object");
223
- }
224
- assertOnlyAllowedKeys(config.display, ["metrics", "steps", "overview"], "display");
225
- const displayMetricKeys = /* @__PURE__ */ new Set();
226
- if (config.display.metrics !== void 0) {
227
- if (!Array.isArray(config.display.metrics)) {
228
- throw new Error("display.metrics must be an array");
229
- }
230
- for (let i = 0; i < config.display.metrics.length; i++) {
231
- const metric = config.display.metrics[i];
232
- if (metric === null || typeof metric !== "object" || Array.isArray(metric)) {
233
- throw new Error(`display.metrics[${i}] must be an object`);
234
- }
235
- assertOnlyAllowedKeys(metric, ["key", "label", "unit", "direction", "decimals", "order"], `display.metrics[${i}]`);
236
- const key = assertNonEmptyString(metric.key, `display.metrics[${i}].key`);
237
- if (displayMetricKeys.has(key)) {
238
- throw new Error(`duplicate display metric key: ${key}`);
239
- }
240
- displayMetricKeys.add(key);
241
- assertNonEmptyString(metric.label, `display.metrics[${i}].label`);
242
- if (metric.unit !== void 0 && typeof metric.unit !== "string") {
243
- throw new Error(`display.metrics[${i}].unit must be a string`);
244
- }
245
- if (metric.direction !== void 0 && metric.direction !== "higher-better" && metric.direction !== "lower-better") {
246
- throw new Error(`display.metrics[${i}].direction must be 'higher-better' or 'lower-better'`);
247
- }
248
- if (metric.decimals !== void 0 && (!Number.isInteger(metric.decimals) || metric.decimals < 0)) {
249
- throw new Error(`display.metrics[${i}].decimals must be a non-negative integer`);
250
- }
251
- if (metric.order !== void 0 && (!Number.isInteger(metric.order) || metric.order < 0)) {
252
- throw new Error(`display.metrics[${i}].order must be a non-negative integer`);
253
- }
254
- }
255
- }
256
- if (config.display.steps !== void 0) {
257
- if (!Array.isArray(config.display.steps)) {
258
- throw new Error("display.steps must be an array");
259
- }
260
- const seenStepKeys = /* @__PURE__ */ new Set();
261
- for (let i = 0; i < config.display.steps.length; i++) {
262
- const step = config.display.steps[i];
263
- if (step === null || typeof step !== "object" || Array.isArray(step)) {
264
- throw new Error(`display.steps[${i}] must be an object`);
265
- }
266
- assertOnlyAllowedKeys(step, ["key", "label", "order"], `display.steps[${i}]`);
267
- const key = assertNonEmptyString(step.key, `display.steps[${i}].key`);
268
- if (seenStepKeys.has(key)) {
269
- throw new Error(`duplicate display step key: ${key}`);
270
- }
271
- seenStepKeys.add(key);
272
- assertNonEmptyString(step.label, `display.steps[${i}].label`);
273
- if (step.order !== void 0 && (!Number.isInteger(step.order) || step.order < 0)) {
274
- throw new Error(`display.steps[${i}].order must be a non-negative integer`);
275
- }
276
- }
277
- }
278
- if (config.display.overview !== void 0) {
279
- if (typeof config.display.overview !== "object" || config.display.overview === null || Array.isArray(config.display.overview)) {
280
- throw new Error("display.overview must be an object");
281
- }
282
- assertOnlyAllowedKeys(config.display.overview, ["defaultMetric", "defaultLayout"], "display.overview");
283
- const { defaultMetric, defaultLayout } = config.display.overview;
284
- if (defaultMetric !== void 0) {
285
- const metric = assertNonEmptyString(defaultMetric, "display.overview.defaultMetric");
286
- const validDefaultMetrics = new Set(displayMetricKeys);
287
- validDefaultMetrics.add("compositeScore");
288
- validDefaultMetrics.add("task");
289
- if (config.display.metrics !== void 0 && !validDefaultMetrics.has(metric)) {
290
- throw new Error(`display.overview.defaultMetric '${metric}' is not declared in display.metrics and is not a known default (compositeScore, task)`);
291
- }
292
- }
293
- if (defaultLayout !== void 0 && !["ranking", "cards", "chart", "leaderboard"].includes(defaultLayout)) {
294
- throw new Error("display.overview.defaultLayout must be 'ranking', 'cards', 'chart', or 'leaderboard'");
295
- }
415
+ try {
416
+ validateBenchmarkDisplayConfig(config.display);
417
+ } catch (error) {
418
+ issues.push({
419
+ field: "display",
420
+ message: error instanceof Error ? error.message : String(error)
421
+ });
296
422
  }
297
423
  }
298
424
  if (config.display?.overview?.defaultMetric === "compositeScore" && config.scoring === void 0 && config.onScore === void 0) {
299
- throw new Error("display.overview.defaultMetric cannot be 'compositeScore' without config.scoring or config.onScore");
425
+ issues.push({
426
+ field: "display.overview.defaultMetric",
427
+ message: "cannot be 'compositeScore' without config.scoring or config.onScore"
428
+ });
300
429
  }
301
- return config;
430
+ return issues;
302
431
  }
303
- function defineTask(task) {
304
- if (typeof task !== "function") {
305
- throw new Error("defineTask requires a task function.");
306
- }
307
- return task;
432
+ function defineOnComplete(onComplete) {
433
+ return onComplete;
308
434
  }
309
435
 
310
436
  // src/no-available-participants.ts
@@ -590,12 +716,18 @@ function isStepOutcome(value) {
590
716
  if (!keys.every((k) => STEP_OUTCOME_KEYS.has(k))) return false;
591
717
  return typeof o.stdout === "string" || typeof o.stderr === "string" || typeof o.error === "string";
592
718
  }
593
- function withTimeout(promise, ms, name) {
719
+ function withTimeout(promise, { stepName, timeoutMs, participantSlug }) {
594
720
  return new Promise((resolve2, reject) => {
595
- const timer = setTimeout(
596
- () => reject(new TaskError(`Step "${name}" timed out after ${ms}ms`, { code: "step_timeout" })),
597
- ms
598
- );
721
+ const timer = setTimeout(() => {
722
+ const participant = participantSlug ? ` for participant "${participantSlug}"` : "";
723
+ reject(
724
+ new TaskError(`Step "${stepName}" timed out after ${timeoutMs}ms${participant}`, {
725
+ code: "step_timeout",
726
+ step: stepName,
727
+ timeoutMs
728
+ })
729
+ );
730
+ }, timeoutMs);
599
731
  promise.then(
600
732
  (value) => {
601
733
  clearTimeout(timer);
@@ -608,20 +740,23 @@ function withTimeout(promise, ms, name) {
608
740
  );
609
741
  });
610
742
  }
611
- async function runStepInvocations(name, fn, options) {
612
- const requestedConcurrency = options?.concurrency;
613
- if (requestedConcurrency !== void 0 && (!Number.isInteger(requestedConcurrency) || requestedConcurrency < 1)) {
614
- throw new Error(`step "${name}" concurrency must be an integer >= 1 (got ${requestedConcurrency})`);
743
+ async function runStepInvocations(name, fn, options, participantSlug) {
744
+ if (options?.concurrency !== void 0) {
745
+ console.warn(`[benchsdk] step "${name}" option "concurrency" is deprecated; use "parallelInvocations"`);
746
+ }
747
+ const requestedParallelism = options?.parallelInvocations ?? options?.concurrency;
748
+ if (requestedParallelism !== void 0 && (!Number.isInteger(requestedParallelism) || requestedParallelism < 1)) {
749
+ throw new Error(`step "${name}" parallelInvocations must be an integer >= 1 (got ${requestedParallelism})`);
615
750
  }
616
751
  const timeoutMs = options?.timeoutMs;
617
752
  if (timeoutMs !== void 0 && (!Number.isFinite(timeoutMs) || timeoutMs < 0)) {
618
753
  throw new Error(`step "${name}" timeoutMs must be a number >= 0 (got ${timeoutMs})`);
619
754
  }
620
- const count = requestedConcurrency ?? 1;
755
+ const count = requestedParallelism ?? 1;
621
756
  const invocations = Array.from({ length: count }, () => {
622
757
  const promise = Promise.resolve().then(() => fn());
623
758
  if (timeoutMs === void 0) return promise;
624
- return withTimeout(promise, timeoutMs, name);
759
+ return withTimeout(promise, { stepName: name, timeoutMs, participantSlug });
625
760
  });
626
761
  if (count === 1) {
627
762
  return invocations[0];
@@ -639,14 +774,14 @@ async function runStepInvocations(name, fn, options) {
639
774
  if (firstError !== void 0) throw firstError;
640
775
  return results;
641
776
  }
642
- async function runStepWithClient(clientStep, name, fn, options) {
643
- const { concurrency: runnerConcurrency, timeoutMs, ...clientOptions } = options ?? {};
777
+ async function runStepWithClient(clientStep, name, fn, options, participantSlug) {
778
+ const { parallelInvocations: runnerParallelism, concurrency: deprecatedConcurrency, timeoutMs, ...clientOptions } = options ?? {};
644
779
  const clientStepOptions = {
645
780
  ...clientOptions,
646
781
  timeoutMs,
647
- stepConcurrency: runnerConcurrency
782
+ stepConcurrency: runnerParallelism ?? deprecatedConcurrency
648
783
  };
649
- const result = await clientStep(name, () => runStepInvocations(name, fn, options), clientStepOptions);
784
+ const result = await clientStep(name, () => runStepInvocations(name, fn, options, participantSlug), clientStepOptions);
650
785
  return result;
651
786
  }
652
787
  function parseCliArgs(argv, allowedCustomFlags) {
@@ -814,7 +949,9 @@ function defaultOnResult(record, meta) {
814
949
  const data = record.data && Object.keys(record.data).length > 0 ? ` ${JSON.stringify(record.data)}` : "";
815
950
  console.log(` [${meta.participant}] Task ${n}/${meta.iterations}: success${data}`);
816
951
  } else {
817
- console.log(` [${meta.participant}] Task ${n}/${meta.iterations}: FAILED \u2014 ${record.errorCode ?? "unknown error"}`);
952
+ const detail = record.data?.errorMessage ?? record.data?.error;
953
+ const suffix = typeof detail === "string" && detail.length > 0 ? `: ${detail}` : "";
954
+ console.log(` [${meta.participant}] Task ${n}/${meta.iterations}: FAILED \u2014 ${record.errorCode ?? "unknown error"}${suffix}`);
818
955
  }
819
956
  }
820
957
  function resolveShape(config, shapeName) {
@@ -855,7 +992,23 @@ function resolveParticipants(config, resolved) {
855
992
  if (available.length === 0) throw new NoAvailableParticipantsError(skipped);
856
993
  return available;
857
994
  }
858
- function runConfigToJson(config, resolved, participants) {
995
+ function resolveTriggerSource(env = process.env) {
996
+ const source = env.BENCH_TRIGGER_SOURCE?.trim();
997
+ if (source) return source;
998
+ return env.GITHUB_EVENT_NAME?.trim() || "manual";
999
+ }
1000
+ function triggerToJson(env = process.env) {
1001
+ const requestedBy = env.BENCH_TRIGGER_REQUESTED_BY?.trim();
1002
+ const requestId = env.BENCH_TRIGGER_REQUEST_ID?.trim();
1003
+ const event = env.GITHUB_EVENT_NAME?.trim();
1004
+ return {
1005
+ source: resolveTriggerSource(env),
1006
+ ...event ? { event } : {},
1007
+ ...requestedBy ? { requestedBy } : {},
1008
+ ...requestId ? { requestId } : {}
1009
+ };
1010
+ }
1011
+ function runConfigToJson(config, resolved, participants, env = process.env) {
859
1012
  const phases = config.phases?.map((phase) => ({
860
1013
  name: phase.name,
861
1014
  iterations: resolved.phaseIterations ?? phase.iterations
@@ -871,24 +1024,38 @@ function runConfigToJson(config, resolved, participants) {
871
1024
  groupBy: resolved.groupBy,
872
1025
  ...config.dimensions ? { dimensions: config.dimensions } : {},
873
1026
  ...config.scoring ? { scoring: config.scoring } : {},
874
- participants
1027
+ participants,
1028
+ trigger: triggerToJson(env)
875
1029
  };
876
1030
  return JSON.parse(JSON.stringify(runConfig));
877
1031
  }
878
- async function runBenchmark(fileConfig, task, argv = []) {
1032
+ async function runBenchmark(fileConfig, task, argv = [], options = {}) {
879
1033
  const args = parseCliArgs(argv, fileConfig.customCliFlags);
880
1034
  const noIngest = args.noIngest ?? isEnvNoIngest();
881
1035
  const shaped = applyShape(fileConfig, resolveShape(fileConfig, args.shape));
882
1036
  const config = applyIdentityOverrides(shaped, args);
883
1037
  const resolved = mergeConfig(config, args);
884
- const auth = await (0, import_cli.resolveAuth)();
885
- const client = (0, import_api.createBenchmarkClient)({
886
- baseUrl: auth.apiBaseUrl,
887
- apiKey: auth.apiKey,
888
- token: auth.token,
889
- orgSlug: auth.orgSlug,
890
- orgId: auth.orgId
891
- });
1038
+ let baseUrl = "";
1039
+ let apiKey = "";
1040
+ let token;
1041
+ let orgSlug;
1042
+ let orgId;
1043
+ let client = null;
1044
+ const auth = noIngest ? await (0, import_cli.resolveAuth)({ baseUrl: options.baseUrl, apiKey: options.apiKey }).catch(() => null) : await (0, import_cli.resolveAuth)({ baseUrl: options.baseUrl, apiKey: options.apiKey });
1045
+ if (auth) {
1046
+ baseUrl = auth.apiBaseUrl;
1047
+ apiKey = auth.apiKey ?? "";
1048
+ token = auth.token;
1049
+ orgSlug = auth.orgSlug;
1050
+ orgId = auth.orgId;
1051
+ client = (0, import_api.createBenchmarkClient)({
1052
+ baseUrl: auth.apiBaseUrl,
1053
+ apiKey: auth.apiKey,
1054
+ token: auth.token,
1055
+ orgSlug: auth.orgSlug,
1056
+ orgId: auth.orgId
1057
+ });
1058
+ }
892
1059
  const available = resolveParticipants(config, resolved);
893
1060
  const schedule = buildSchedule(config, resolved, task);
894
1061
  const totalTasks = schedule.length;
@@ -909,15 +1076,26 @@ async function runBenchmark(fileConfig, task, argv = []) {
909
1076
  runId = "no-ingest";
910
1077
  dashboardUrl = "";
911
1078
  } else {
912
- if (identityIsOurs) {
1079
+ {
913
1080
  const benchmarkConfig = {
914
1081
  ...config.scoring ? { scoring: config.scoring } : {},
915
1082
  ...config.display ? { display: config.display } : {}
916
1083
  };
917
- await client.upsertBenchmark(config.benchmarkSlug, {
918
- name: config.benchmarkName,
919
- ...Object.keys(benchmarkConfig).length > 0 ? { config: benchmarkConfig } : {}
920
- });
1084
+ let initializeTarget = identityIsOurs;
1085
+ if (!initializeTarget) {
1086
+ try {
1087
+ await client.getBenchmark(config.benchmarkSlug);
1088
+ } catch (error) {
1089
+ if (!(error instanceof import_api.BenchmarkApiError && error.status === 404)) throw error;
1090
+ initializeTarget = true;
1091
+ }
1092
+ }
1093
+ const upsertInput = {};
1094
+ if (initializeTarget) {
1095
+ upsertInput.name = config.benchmarkName;
1096
+ if (Object.keys(benchmarkConfig).length > 0) upsertInput.config = benchmarkConfig;
1097
+ }
1098
+ await client.upsertBenchmark(config.benchmarkSlug, upsertInput);
921
1099
  }
922
1100
  const runConfig = client ? runConfigToJson(config, resolved, available.map((p) => p.name)) : {};
923
1101
  if (args.runKey) {
@@ -926,7 +1104,7 @@ async function runBenchmark(fileConfig, task, argv = []) {
926
1104
  config: runConfig
927
1105
  });
928
1106
  runId = run2.id;
929
- dashboardUrl = dashboardUrlFor(auth.apiBaseUrl, organizationSlug, config.benchmarkSlug, run2.id);
1107
+ dashboardUrl = dashboardUrlFor(baseUrl, organizationSlug, config.benchmarkSlug, run2.id);
930
1108
  for (const participant of available) {
931
1109
  await client.upsertParticipant(config.benchmarkSlug, runId, participant.name, { totalTasks });
932
1110
  }
@@ -941,7 +1119,7 @@ async function runBenchmark(fileConfig, task, argv = []) {
941
1119
  config: runConfig
942
1120
  });
943
1121
  runId = run2.id;
944
- dashboardUrl = dashboardUrlFor(auth.apiBaseUrl, organizationSlug, config.benchmarkSlug, run2.id);
1122
+ dashboardUrl = dashboardUrlFor(baseUrl, organizationSlug, config.benchmarkSlug, run2.id);
945
1123
  console.log(`Run created: ${run2.name} (${runId})`);
946
1124
  console.log(`View at: ${dashboardUrl}
947
1125
  `);
@@ -950,7 +1128,7 @@ async function runBenchmark(fileConfig, task, argv = []) {
950
1128
  const onResult = defaultOnResult;
951
1129
  let participantRecords;
952
1130
  if (resolved.groupBy === "round") {
953
- participantRecords = await runGroupedByRound(config, schedule, available, resolved, client, runId, auth.apiBaseUrl, auth.apiKey, auth.token, auth.orgSlug, auth.orgId, onResult, noIngest);
1131
+ participantRecords = await runGroupedByRound(config, schedule, available, resolved, client, runId, baseUrl, apiKey, token, orgSlug, orgId, onResult, noIngest);
954
1132
  } else {
955
1133
  participantRecords = await runGroupedByParticipant(config, schedule, available, resolved, client, runId, onResult, noIngest);
956
1134
  }
@@ -961,14 +1139,14 @@ async function runBenchmark(fileConfig, task, argv = []) {
961
1139
  participants: participantRecords,
962
1140
  config: resolved
963
1141
  };
964
- if (!noIngest && (config.onScore || config.scoring)) {
1142
+ if (!noIngest && client && (config.onScore || config.scoring)) {
965
1143
  try {
966
1144
  const spec = config.onScore ? await config.onScore(lowerIsBetter, higherIsBetter) : scoringConfigToSpec(config.scoring, config.dimensions, config.display);
967
1145
  const scored = score(outcome, spec, config.display?.metrics);
968
1146
  const run2 = {
969
1147
  gitSha: process.env.GITHUB_SHA ?? getGitSha(),
970
1148
  gitRef: process.env.GITHUB_REF_NAME ?? process.env.GITHUB_REF ?? getGitRef(),
971
- triggeredBy: process.env.GITHUB_EVENT_NAME ?? "manual",
1149
+ triggeredBy: resolveTriggerSource(),
972
1150
  nodeVersion: process.version,
973
1151
  platform: import_node_os.default.platform(),
974
1152
  arch: import_node_os.default.arch()
@@ -987,6 +1165,22 @@ async function runBenchmark(fileConfig, task, argv = []) {
987
1165
  if (config.onComplete) await config.onComplete(outcome);
988
1166
  return outcome;
989
1167
  }
1168
+ async function runBenchmarkWorker(options) {
1169
+ const config = defineBenchmarkConfig({
1170
+ benchmarkSlug: options.benchmarkSlug,
1171
+ benchmarkName: options.benchmarkName ?? options.benchmarkSlug,
1172
+ participants: [options.participant],
1173
+ iterations: options.iterations ?? 1,
1174
+ concurrency: options.concurrency ?? 1,
1175
+ staggerDelayMs: options.staggerDelayMs ?? 0,
1176
+ groupBy: options.groupBy ?? "participant",
1177
+ defaultProviders: [options.participant.name]
1178
+ });
1179
+ const argv = [];
1180
+ if (options.runKey) argv.push("--run-key", options.runKey);
1181
+ if (options.noIngest) argv.push("--dry-run");
1182
+ return runBenchmark(config, options.task, argv);
1183
+ }
990
1184
  function getGitSha() {
991
1185
  if (process.env.GITHUB_SHA) return process.env.GITHUB_SHA;
992
1186
  try {
@@ -1061,7 +1255,7 @@ async function runGroupedByParticipant(config, schedule, available, resolved, cl
1061
1255
  participant,
1062
1256
  taskIndex: scheduleIndex,
1063
1257
  phase: slot.phase,
1064
- step: (name, fn, options) => runStepWithClient(ctx.step, name, fn, options),
1258
+ step: (name, fn, options) => runStepWithClient(ctx.step, name, fn, options, participant.name),
1065
1259
  measure: ctx.measure,
1066
1260
  log: ctx.log
1067
1261
  });
@@ -1218,12 +1412,13 @@ async function runTaskRecord(task, participant, scheduleIndex, taskIndex, phase,
1218
1412
  completedAt: new Date(stepStartedAtMs).toISOString(),
1219
1413
  latencyMs: 0
1220
1414
  };
1221
- if (options?.concurrency !== void 0) stepRecord.concurrency = options.concurrency;
1415
+ const requestedParallelism = options?.parallelInvocations ?? options?.concurrency;
1416
+ if (requestedParallelism !== void 0) stepRecord.concurrency = requestedParallelism;
1222
1417
  if (options?.timeoutMs !== void 0) stepRecord.timeoutMs = options.timeoutMs;
1223
1418
  const previousStep = activeStep;
1224
1419
  activeStep = stepRecord;
1225
1420
  try {
1226
- const result2 = await runStepInvocations(name, fn, options);
1421
+ const result2 = await runStepInvocations(name, fn, options, participant.name);
1227
1422
  const outcome = options?.captureOutput !== false && !Array.isArray(result2) && isStepOutcome(result2) ? result2 : {};
1228
1423
  logBuffer.step(taskIndex, name, outcome);
1229
1424
  return result2;
@@ -1293,16 +1488,144 @@ async function runTaskRecord(task, participant, scheduleIndex, taskIndex, phase,
1293
1488
  var import_node_path = require("path");
1294
1489
  var import_node_url = require("url");
1295
1490
  var import_cli2 = require("@benchsdk/cli");
1296
- var USAGE = 'Usage:\n bench run <file.bench.ts> [--shape name] [--provider a,b] [--run-key key]\n [--benchmark slug] [--name "My benchmark"]\n [--iterations N] [--concurrency N] [--stagger-delay-ms N] [--group-by participant|round]\n [--no-ingest | --dry-run]';
1491
+ var import_api2 = require("@benchsdk/api");
1492
+ var import_worker2 = require("@benchsdk/worker");
1493
+ var USAGE = 'Usage:\n bench run <file.bench.ts> [--shape name] [--provider a,b] [--run-key key]\n [--benchmark slug] [--name "My benchmark"]\n [--iterations N] [--concurrency N] [--stagger-delay-ms N] [--group-by participant|round]\n [--no-ingest | --dry-run] [--check]\n bench check <file.bench.ts> [--base-url <url>] [--api-key <key>]';
1297
1494
  function isBenchmarkConfig(value) {
1298
1495
  if (typeof value !== "object" || value === null) return false;
1299
1496
  const candidate = value;
1300
1497
  return typeof candidate.benchmarkSlug === "string" && Array.isArray(candidate.participants);
1301
1498
  }
1499
+ function shiftFlag(argv, name) {
1500
+ const prefix = `--${name}`;
1501
+ const prefixEq = `${prefix}=`;
1502
+ const result = [];
1503
+ let value;
1504
+ for (let i = 0; i < argv.length; i++) {
1505
+ const arg = argv[i];
1506
+ if (arg === prefix) {
1507
+ const next = argv[++i];
1508
+ if (!next || next.startsWith("--")) throw new Error(USAGE);
1509
+ value = next;
1510
+ continue;
1511
+ }
1512
+ if (arg.startsWith(prefixEq)) {
1513
+ const eqValue = arg.slice(prefixEq.length);
1514
+ if (!eqValue) throw new Error(USAGE);
1515
+ value = eqValue;
1516
+ continue;
1517
+ }
1518
+ result.push(arg);
1519
+ }
1520
+ return { value, argv: result };
1521
+ }
1522
+ function shiftPlatformFlags(flags) {
1523
+ const { value: baseUrl, argv: withoutBaseUrl } = shiftFlag(flags, "base-url");
1524
+ const { value: apiKey, argv: rest } = shiftFlag(withoutBaseUrl, "api-key");
1525
+ return { baseUrl, apiKey, flags: rest };
1526
+ }
1527
+ async function runCheck(argv) {
1528
+ const [command, ...rest] = argv;
1529
+ const [file, ...flags] = rest;
1530
+ if (command !== "check" || !file || file.startsWith("-")) throw new Error(USAGE);
1531
+ const mod = await import((0, import_node_url.pathToFileURL)((0, import_node_path.resolve)(process.cwd(), file)).href);
1532
+ const config = mod.config;
1533
+ const task = mod.task ?? mod.default;
1534
+ if (!isBenchmarkConfig(config)) {
1535
+ throw new Error(`${file} must export a \`config\` created with defineBenchmarkConfig (with participants).`);
1536
+ }
1537
+ if (typeof task !== "function") {
1538
+ throw new Error(`${file} must export a \`task\` created with defineTask.`);
1539
+ }
1540
+ const cfg = config;
1541
+ const configIssues = validateBenchmarkConfig(cfg);
1542
+ if (configIssues.length > 0) {
1543
+ throw new BenchmarkConfigError(configIssues);
1544
+ }
1545
+ const { baseUrl, apiKey, flags: runnerFlags } = shiftPlatformFlags(flags);
1546
+ const parsed = parseCliArgs(runnerFlags, cfg.customCliFlags ?? []);
1547
+ resolveShape(cfg, parsed.shape);
1548
+ const dryRun = parsed.noIngest ?? false;
1549
+ let client;
1550
+ let apiOk = dryRun;
1551
+ let auth = null;
1552
+ if (!dryRun) {
1553
+ try {
1554
+ auth = await (0, import_cli2.resolveAuth)({ baseUrl, apiKey });
1555
+ client = (0, import_api2.createBenchmarkClient)({
1556
+ baseUrl: auth.apiBaseUrl,
1557
+ apiKey: auth.apiKey,
1558
+ token: auth.token,
1559
+ orgSlug: auth.orgSlug,
1560
+ orgId: auth.orgId
1561
+ });
1562
+ await client.listBenchmarks({ limit: 1 });
1563
+ apiOk = true;
1564
+ } catch (err) {
1565
+ const message = err instanceof Error ? err.message : String(err);
1566
+ console.warn(`[benchsdk] API connectivity check failed: ${message}`);
1567
+ }
1568
+ }
1569
+ const effectiveProviderNames = parsed.providers ?? cfg.defaultProviders;
1570
+ let selected;
1571
+ try {
1572
+ selected = (0, import_worker2.selectParticipants)(cfg.participants, effectiveProviderNames);
1573
+ } catch (err) {
1574
+ throw new Error(`Participant selection failed: ${err instanceof Error ? err.message : err}`);
1575
+ }
1576
+ const { available, skipped } = (0, import_worker2.filterParticipantsByEnv)(selected);
1577
+ let scoringOk = true;
1578
+ if (cfg.onScore) {
1579
+ try {
1580
+ const spec = await cfg.onScore(lowerIsBetter, higherIsBetter);
1581
+ validateScoringSpec(spec);
1582
+ } catch (err) {
1583
+ scoringOk = false;
1584
+ const message = err instanceof Error ? err.message : String(err);
1585
+ console.warn(`[benchsdk] Scoring validation failed: ${message}`);
1586
+ }
1587
+ } else if (cfg.scoring) {
1588
+ try {
1589
+ const spec = scoringConfigToSpec(cfg.scoring, cfg.dimensions);
1590
+ validateScoringSpec(spec);
1591
+ } catch (err) {
1592
+ scoringOk = false;
1593
+ const message = err instanceof Error ? err.message : String(err);
1594
+ console.warn(`[benchsdk] Scoring validation failed: ${message}`);
1595
+ }
1596
+ }
1597
+ const missingPlatformAuth = dryRun ? [] : auth ? [] : [["BENCHMARKS_PLATFORM_API_KEY or BENCHMARKS_PLATFORM_TOKEN", void 0]];
1598
+ for (const [name] of missingPlatformAuth) {
1599
+ console.warn(`[benchsdk] ${name} is not set`);
1600
+ }
1601
+ const report = {
1602
+ file,
1603
+ benchmarkSlug: cfg.benchmarkSlug,
1604
+ apiOk,
1605
+ envOk: dryRun || missingPlatformAuth.length === 0,
1606
+ participants: {
1607
+ requested: selected.map((p) => p.name),
1608
+ available: available.map((p) => p.name),
1609
+ skipped: skipped.map((s) => ({ name: s.name, missing: s.missing }))
1610
+ },
1611
+ scoringOk: cfg.scoring || cfg.onScore ? scoringOk : void 0
1612
+ };
1613
+ console.log(JSON.stringify(report, null, 2));
1614
+ const authFailure = !dryRun && missingPlatformAuth.length > 0;
1615
+ if (!apiOk || available.length === 0 || scoringOk === false || authFailure) {
1616
+ throw new Error("Benchmark check failed. See warnings above for details.");
1617
+ }
1618
+ }
1302
1619
  async function runBenchmarkFile(argv) {
1303
1620
  const [command, ...rest] = argv;
1304
1621
  const [file, ...flags] = rest;
1305
1622
  if (command !== "run" || !file || file.startsWith("-")) throw new Error(USAGE);
1623
+ const check = flags.includes("--check") || flags.includes("--validate");
1624
+ if (check) {
1625
+ const checkFlags = flags.filter((f) => f !== "--check" && f !== "--validate");
1626
+ return runCheck(["check", file, ...checkFlags]);
1627
+ }
1628
+ const { baseUrl, apiKey, flags: runnerFlags } = shiftPlatformFlags(flags);
1306
1629
  const mod = await import((0, import_node_url.pathToFileURL)((0, import_node_path.resolve)(process.cwd(), file)).href);
1307
1630
  const config = mod.config;
1308
1631
  const task = mod.task ?? mod.default;
@@ -1312,44 +1635,78 @@ async function runBenchmarkFile(argv) {
1312
1635
  if (typeof task !== "function") {
1313
1636
  throw new Error(`${file} must export a \`task\` created with defineTask.`);
1314
1637
  }
1315
- await runBenchmark(config, task, flags);
1638
+ const envFlags = [];
1639
+ if (process.env.BENCHMARK_SLUG) {
1640
+ envFlags.push("--benchmark", process.env.BENCHMARK_SLUG);
1641
+ }
1642
+ if (process.env.BENCHMARK_NAME) {
1643
+ envFlags.push("--name", process.env.BENCHMARK_NAME);
1644
+ }
1645
+ await runBenchmark(
1646
+ config,
1647
+ task,
1648
+ [...envFlags, ...runnerFlags],
1649
+ { baseUrl, apiKey }
1650
+ );
1316
1651
  }
1317
1652
  async function run(argv) {
1318
- if (argv[0] !== "run") {
1319
- return (0, import_cli2.run)(argv);
1320
- }
1653
+ const [command, ...rest] = argv;
1321
1654
  try {
1322
- await runBenchmarkFile(argv);
1655
+ if (command === "run") {
1656
+ await runBenchmarkFile(argv);
1657
+ } else if (command === "check") {
1658
+ await runCheck(argv);
1659
+ } else {
1660
+ return (0, import_cli2.run)(argv);
1661
+ }
1323
1662
  process.exit(0);
1324
1663
  } catch (err) {
1325
1664
  if (err instanceof NoAvailableParticipantsError) {
1326
1665
  console.log(err.message);
1327
1666
  process.exit(0);
1328
1667
  }
1329
- console.error("Benchmark failed:", err instanceof Error ? err.message : err);
1668
+ console.error("Benchmark failed:", String(err));
1330
1669
  process.exit(1);
1331
1670
  }
1332
1671
  }
1333
1672
 
1334
1673
  // src/index.ts
1335
- var BENCHSDK_RUNNER_VERSION = "0.3.0";
1674
+ var import_cli4 = require("@benchsdk/cli");
1675
+ var import_worker3 = require("@benchsdk/worker");
1676
+ var import_api3 = require("@benchsdk/api");
1677
+ var BENCHSDK_RUNNER_VERSION = "0.5.2";
1336
1678
  // Annotate the CommonJS export names for ESM import in node:
1337
1679
  0 && (module.exports = {
1680
+ AuthError,
1338
1681
  BENCHSDK_RUNNER_VERSION,
1682
+ BenchmarkApiError,
1683
+ BenchmarkConfigError,
1684
+ BenchmarkReporter,
1339
1685
  NoAvailableParticipantsError,
1340
1686
  ScoringSpecError,
1341
1687
  TaskError,
1688
+ claimBenchmarkReporter,
1689
+ createApiClient,
1690
+ createBenchmarkClient,
1691
+ createSystemMetricsCollector,
1342
1692
  defineBenchmarkConfig,
1693
+ defineOnComplete,
1343
1694
  defineTask,
1695
+ filterParticipantsByEnv,
1344
1696
  higherIsBetter,
1345
1697
  lowerIsBetter,
1346
1698
  mergeConfig,
1347
1699
  parseCliArgs,
1700
+ resolveAuth,
1348
1701
  run,
1349
1702
  runBenchmark,
1350
1703
  runBenchmarkFile,
1704
+ runBenchmarkWorker,
1705
+ runWorker,
1351
1706
  score,
1352
1707
  scoringConfigToSpec,
1708
+ selectParticipants,
1709
+ validateBenchmarkConfig,
1353
1710
  validateScoringSpec
1354
1711
  });
1355
1712
  //# sourceMappingURL=index.cjs.map