@benchsdk/runner 0.2.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.js CHANGED
@@ -3,67 +3,231 @@ var TaskError = class extends Error {
3
3
  code;
4
4
  data;
5
5
  steps;
6
+ step;
7
+ timeoutMs;
6
8
  constructor(message, opts) {
7
9
  super(message);
8
10
  this.name = "TaskError";
9
11
  this.code = opts?.code;
10
12
  this.data = opts?.data;
11
13
  this.steps = opts?.steps;
14
+ this.step = opts?.step;
15
+ this.timeoutMs = opts?.timeoutMs;
16
+ }
17
+ toString() {
18
+ let s = `[${this.name}${this.code ? ` (${this.code})` : ""}] ${this.message}`;
19
+ if (this.step) {
20
+ s += `
21
+ step: ${this.step}`;
22
+ }
23
+ if (this.timeoutMs !== void 0) {
24
+ s += `
25
+ timeoutMs: ${this.timeoutMs}`;
26
+ }
27
+ if (this.data && Object.keys(this.data).length > 0) {
28
+ s += `
29
+ data: ${JSON.stringify(this.data, null, 2)}`;
30
+ }
31
+ return s;
32
+ }
33
+ };
34
+ var BenchmarkConfigError = class _BenchmarkConfigError extends Error {
35
+ issues;
36
+ constructor(issues) {
37
+ super(_BenchmarkConfigError.formatIssues(issues));
38
+ this.name = "BenchmarkConfigError";
39
+ this.issues = issues;
40
+ }
41
+ static formatIssues(issues) {
42
+ const lines = issues.map((i) => ` - ${i.field}: ${i.message}`);
43
+ return `Invalid benchmark config:
44
+ ${lines.join("\n")}
45
+
46
+ Fix the fields above and try again.`;
12
47
  }
13
48
  };
14
- function assertPositiveInt(value, field) {
15
- if (value === void 0) return;
16
- if (!Number.isInteger(value) || value < 1) {
17
- throw new Error(`${field} must be an integer >= 1 (got ${value})`);
49
+ function assertFiniteNumber(value, field) {
50
+ if (typeof value !== "number" || !Number.isFinite(value)) {
51
+ throw new Error(`${field} must be a finite number (got ${value})`);
18
52
  }
53
+ return value;
19
54
  }
20
- function defineBenchmarkConfig(config) {
21
- if (!config.benchmarkSlug || typeof config.benchmarkSlug !== "string") {
22
- throw new Error("benchmarkSlug is required");
55
+ function assertNonEmptyString(value, field) {
56
+ if (typeof value !== "string" || value.trim() === "") {
57
+ throw new Error(`${field} must be a non-empty string`);
23
58
  }
24
- if (!config.benchmarkName || typeof config.benchmarkName !== "string") {
25
- throw new Error("benchmarkName is required");
59
+ return value;
60
+ }
61
+ function assertOnlyAllowedKeys(value, allowed, field) {
62
+ for (const key of Object.keys(value)) {
63
+ if (!allowed.includes(key)) {
64
+ throw new Error(`${field} contains unexpected key: '${key}'`);
65
+ }
26
66
  }
27
- if (config.phases !== void 0) {
28
- if (config.iterations !== void 0) {
29
- throw new Error("phases and iterations are mutually exclusive");
67
+ }
68
+ function validateBenchmarkScoringConfig(scoring, display) {
69
+ if (!Array.isArray(scoring.metrics) || scoring.metrics.length === 0) {
70
+ throw new Error("scoring.metrics must be a non-empty array");
71
+ }
72
+ if (scoring.success !== void 0) {
73
+ const requireData = scoring.success.requireData;
74
+ if (requireData === null || typeof requireData !== "object" || Array.isArray(requireData)) {
75
+ throw new Error("scoring.success.requireData must be a plain object");
30
76
  }
31
- if (!Array.isArray(config.phases) || config.phases.length === 0) {
32
- throw new Error("phases must be a non-empty array");
77
+ if (Object.keys(requireData).length === 0) {
78
+ throw new Error("scoring.success.requireData must declare at least one data field");
33
79
  }
34
- const seen = /* @__PURE__ */ new Set();
35
- for (const phase of config.phases) {
36
- if (!phase.name || typeof phase.name !== "string") {
37
- throw new Error("each phase requires a non-empty name");
80
+ for (const [key, value] of Object.entries(requireData)) {
81
+ const type = typeof value;
82
+ if (type !== "string" && type !== "number" && type !== "boolean") {
83
+ throw new Error(
84
+ `scoring.success.requireData.${key} must be a string, number, or boolean (got ${type})`
85
+ );
38
86
  }
39
- if (seen.has(phase.name)) {
40
- throw new Error(`duplicate phase name: ${phase.name}`);
87
+ }
88
+ }
89
+ const seen = /* @__PURE__ */ new Set();
90
+ let totalWeight = 0;
91
+ for (let i = 0; i < scoring.metrics.length; i++) {
92
+ const metric = scoring.metrics[i];
93
+ if (metric === null || typeof metric !== "object" || Array.isArray(metric)) {
94
+ throw new Error(`scoring.metrics[${i}] must be an object`);
95
+ }
96
+ const key = metric.key;
97
+ if (typeof key !== "string" || key.trim() === "") {
98
+ throw new Error(`scoring.metrics[${i}].key must be a non-empty string`);
99
+ }
100
+ if (seen.has(key)) {
101
+ throw new Error(`duplicate scoring metric key: ${key}`);
102
+ }
103
+ seen.add(key);
104
+ const displayMetrics = Array.isArray(display?.metrics) ? display.metrics : void 0;
105
+ const displayMetric = displayMetrics?.find((m) => m?.key === key);
106
+ if (metric.unit !== void 0) {
107
+ if (typeof metric.unit !== "string") {
108
+ throw new Error(`scoring.metrics[${i}].unit must be a string`);
41
109
  }
42
- seen.add(phase.name);
43
- assertPositiveInt(phase.iterations, `phase '${phase.name}' iterations`);
110
+ if (displayMetric?.unit !== void 0 && metric.unit !== displayMetric.unit) {
111
+ throw new Error(
112
+ `scoring.metrics[${i}].unit '${metric.unit}' conflicts with display.metrics[${i}].unit '${displayMetric.unit}' for key '${key}'`
113
+ );
114
+ }
115
+ }
116
+ assertFiniteNumber(metric.ceiling, `scoring.metrics[${i}].ceiling`);
117
+ if (metric.floor !== void 0) {
118
+ assertFiniteNumber(metric.floor, `scoring.metrics[${i}].floor`);
119
+ }
120
+ if (metric.weights === null || typeof metric.weights !== "object" || Array.isArray(metric.weights)) {
121
+ throw new Error(`scoring.metrics[${i}].weights must be an object`);
122
+ }
123
+ const median = assertFiniteNumber(metric.weights.median, `scoring.metrics[${i}].weights.median`);
124
+ const p95 = assertFiniteNumber(metric.weights.p95, `scoring.metrics[${i}].weights.p95`);
125
+ const p99 = assertFiniteNumber(metric.weights.p99, `scoring.metrics[${i}].weights.p99`);
126
+ if (median < 0 || p95 < 0 || p99 < 0) {
127
+ throw new Error(`scoring.metrics[${i}].weights must be non-negative`);
128
+ }
129
+ totalWeight += median + p95 + p99;
130
+ if (metric.trim !== void 0) {
131
+ assertFiniteNumber(metric.trim, `scoring.metrics[${i}].trim`);
44
132
  }
45
133
  }
46
- assertPositiveInt(config.iterations, "iterations");
47
- assertPositiveInt(config.concurrency, "concurrency");
48
- if (config.staggerDelayMs !== void 0 && (!Number.isFinite(config.staggerDelayMs) || config.staggerDelayMs < 0)) {
49
- throw new Error(`staggerDelayMs must be a number >= 0 (got ${config.staggerDelayMs})`);
134
+ if (Math.abs(totalWeight - 1) > 0.01) {
135
+ throw new Error(`scoring metric weights must sum to 1.0 (got ${totalWeight.toFixed(3)})`);
50
136
  }
51
- if (config.groupBy !== void 0 && config.groupBy !== "participant" && config.groupBy !== "round") {
52
- throw new Error(`groupBy must be 'participant' or 'round' (got ${config.groupBy})`);
137
+ }
138
+ function validateBenchmarkDisplayConfig(display) {
139
+ if (typeof display !== "object" || display === null || Array.isArray(display)) {
140
+ throw new Error("display must be an object");
53
141
  }
54
- if (config.shapes !== void 0) {
55
- for (const [shapeName, shape] of Object.entries(config.shapes)) {
56
- if (!shape.slug || !/^[a-z0-9][a-z0-9-]*$/.test(shape.slug)) {
57
- throw new Error(`shape '${shapeName}' needs a lowercase slug (got ${JSON.stringify(shape.slug)})`);
142
+ assertOnlyAllowedKeys(display, ["metrics", "steps", "overview"], "display");
143
+ const displayMetricKeys = /* @__PURE__ */ new Set();
144
+ if (display.metrics !== void 0) {
145
+ if (!Array.isArray(display.metrics)) {
146
+ throw new Error("display.metrics must be an array");
147
+ }
148
+ for (let i = 0; i < display.metrics.length; i++) {
149
+ const metric = display.metrics[i];
150
+ if (metric === null || typeof metric !== "object" || Array.isArray(metric)) {
151
+ throw new Error(`display.metrics[${i}] must be an object`);
152
+ }
153
+ assertOnlyAllowedKeys(metric, ["key", "label", "unit", "direction", "decimals", "order"], `display.metrics[${i}]`);
154
+ const key = assertNonEmptyString(metric.key, `display.metrics[${i}].key`);
155
+ if (displayMetricKeys.has(key)) {
156
+ throw new Error(`duplicate display metric key: ${key}`);
58
157
  }
59
- if (shape.name !== void 0 && (typeof shape.name !== "string" || shape.name.trim() === "")) {
60
- throw new Error(`shape '${shapeName}' name must be a non-empty string`);
158
+ displayMetricKeys.add(key);
159
+ assertNonEmptyString(metric.label, `display.metrics[${i}].label`);
160
+ if (metric.unit !== void 0 && typeof metric.unit !== "string") {
161
+ throw new Error(`display.metrics[${i}].unit must be a string`);
61
162
  }
62
- if (shape.staggerDelayMs !== void 0 && (!Number.isFinite(shape.staggerDelayMs) || shape.staggerDelayMs < 0)) {
63
- throw new Error(`shape '${shapeName}' staggerDelayMs must be a number >= 0 (got ${shape.staggerDelayMs})`);
163
+ if (metric.direction !== void 0 && metric.direction !== "higher-better" && metric.direction !== "lower-better") {
164
+ throw new Error(`display.metrics[${i}].direction must be 'higher-better' or 'lower-better'`);
165
+ }
166
+ if (metric.decimals !== void 0 && (!Number.isInteger(metric.decimals) || metric.decimals < 0)) {
167
+ throw new Error(`display.metrics[${i}].decimals must be a non-negative integer`);
168
+ }
169
+ if (metric.order !== void 0 && (!Number.isInteger(metric.order) || metric.order < 0)) {
170
+ throw new Error(`display.metrics[${i}].order must be a non-negative integer`);
64
171
  }
65
172
  }
66
173
  }
174
+ if (display.steps !== void 0) {
175
+ if (!Array.isArray(display.steps)) {
176
+ throw new Error("display.steps must be an array");
177
+ }
178
+ const seenStepKeys = /* @__PURE__ */ new Set();
179
+ for (let i = 0; i < display.steps.length; i++) {
180
+ const step = display.steps[i];
181
+ if (step === null || typeof step !== "object" || Array.isArray(step)) {
182
+ throw new Error(`display.steps[${i}] must be an object`);
183
+ }
184
+ assertOnlyAllowedKeys(step, ["key", "label", "order"], `display.steps[${i}]`);
185
+ const key = assertNonEmptyString(step.key, `display.steps[${i}].key`);
186
+ if (seenStepKeys.has(key)) {
187
+ throw new Error(`duplicate display step key: ${key}`);
188
+ }
189
+ seenStepKeys.add(key);
190
+ assertNonEmptyString(step.label, `display.steps[${i}].label`);
191
+ if (step.order !== void 0 && (!Number.isInteger(step.order) || step.order < 0)) {
192
+ throw new Error(`display.steps[${i}].order must be a non-negative integer`);
193
+ }
194
+ }
195
+ }
196
+ if (display.overview !== void 0) {
197
+ if (typeof display.overview !== "object" || display.overview === null || Array.isArray(display.overview)) {
198
+ throw new Error("display.overview must be an object");
199
+ }
200
+ assertOnlyAllowedKeys(display.overview, ["defaultMetric", "defaultLayout"], "display.overview");
201
+ const { defaultMetric, defaultLayout } = display.overview;
202
+ if (defaultMetric !== void 0) {
203
+ const metric = assertNonEmptyString(defaultMetric, "display.overview.defaultMetric");
204
+ const validDefaultMetrics = new Set(displayMetricKeys);
205
+ validDefaultMetrics.add("compositeScore");
206
+ validDefaultMetrics.add("task");
207
+ if (display.metrics !== void 0 && !validDefaultMetrics.has(metric)) {
208
+ throw new Error(`display.overview.defaultMetric '${metric}' is not declared in display.metrics and is not a known default (compositeScore, task)`);
209
+ }
210
+ }
211
+ if (defaultLayout !== void 0 && !["ranking", "cards", "chart", "leaderboard"].includes(defaultLayout)) {
212
+ throw new Error("display.overview.defaultLayout must be 'ranking', 'cards', 'chart', or 'leaderboard'");
213
+ }
214
+ }
215
+ }
216
+ function defineBenchmarkConfig(config) {
217
+ const issues = validateBenchmarkConfig(config);
218
+ if (config.scoring !== void 0) {
219
+ try {
220
+ validateBenchmarkScoringConfig(config.scoring, config.display);
221
+ } catch (error) {
222
+ issues.push({
223
+ field: "scoring",
224
+ message: error instanceof Error ? error.message : String(error)
225
+ });
226
+ }
227
+ }
228
+ if (issues.length > 0) {
229
+ throw new BenchmarkConfigError(issues);
230
+ }
67
231
  return config;
68
232
  }
69
233
  function defineTask(task) {
@@ -72,6 +236,136 @@ function defineTask(task) {
72
236
  }
73
237
  return task;
74
238
  }
239
+ function validateBenchmarkConfig(config) {
240
+ const issues = [];
241
+ if (!config.benchmarkSlug || typeof config.benchmarkSlug !== "string") {
242
+ issues.push({ field: "benchmarkSlug", message: "is required" });
243
+ }
244
+ if (!config.benchmarkName || typeof config.benchmarkName !== "string") {
245
+ issues.push({ field: "benchmarkName", message: "is required" });
246
+ }
247
+ if (!Array.isArray(config.participants) || config.participants.length === 0) {
248
+ issues.push({ field: "participants", message: "must be a non-empty array" });
249
+ } else {
250
+ const seenParticipants = /* @__PURE__ */ new Set();
251
+ for (let i = 0; i < config.participants.length; i++) {
252
+ const p = config.participants[i];
253
+ if (p === null || typeof p !== "object" || Array.isArray(p)) {
254
+ issues.push({ field: `participants[${i}]`, message: "must be an object" });
255
+ continue;
256
+ }
257
+ const participant = p;
258
+ if (typeof participant.name !== "string" || participant.name.trim() === "") {
259
+ issues.push({ field: `participants[${i}].name`, message: "must be a non-empty string" });
260
+ } else if (seenParticipants.has(participant.name)) {
261
+ issues.push({ field: `participants[${i}].name`, message: `duplicate participant name: ${participant.name}` });
262
+ } else {
263
+ seenParticipants.add(participant.name);
264
+ }
265
+ if (participant.requiredEnvVars !== void 0 && (!Array.isArray(participant.requiredEnvVars) || !participant.requiredEnvVars.every((v) => typeof v === "string"))) {
266
+ issues.push({ field: `participants[${i}].requiredEnvVars`, message: "must be an array of strings" });
267
+ }
268
+ }
269
+ }
270
+ if (config.phases !== void 0) {
271
+ if (config.iterations !== void 0) {
272
+ issues.push({ field: "iterations", message: "phases and iterations are mutually exclusive" });
273
+ }
274
+ if (!Array.isArray(config.phases) || config.phases.length === 0) {
275
+ issues.push({ field: "phases", message: "must be a non-empty array" });
276
+ } else {
277
+ const seen = /* @__PURE__ */ new Set();
278
+ for (let i = 0; i < config.phases.length; i++) {
279
+ const phase = config.phases[i];
280
+ if (phase === null || typeof phase !== "object" || Array.isArray(phase)) {
281
+ issues.push({ field: `phases[${i}]`, message: "must be an object" });
282
+ continue;
283
+ }
284
+ const phaseObj = phase;
285
+ if (typeof phaseObj.name !== "string" || phaseObj.name.trim() === "") {
286
+ issues.push({ field: `phases[${i}]`, message: "must have a non-empty string name" });
287
+ } else {
288
+ const name = phaseObj.name;
289
+ if (seen.has(name)) {
290
+ issues.push({ field: `phases['${name}']`, message: `duplicate phase name: ${name}` });
291
+ }
292
+ seen.add(name);
293
+ const iterations = phaseObj.iterations;
294
+ if (typeof iterations !== "number" || !Number.isInteger(iterations) || iterations < 1) {
295
+ issues.push({ field: `phases['${name}'].iterations`, message: `must be an integer >= 1 (got ${iterations})` });
296
+ }
297
+ }
298
+ }
299
+ }
300
+ }
301
+ if (config.iterations !== void 0 && (!Number.isInteger(config.iterations) || config.iterations < 1)) {
302
+ issues.push({ field: "iterations", message: `must be an integer >= 1 (got ${config.iterations})` });
303
+ }
304
+ if (config.concurrency !== void 0 && (!Number.isInteger(config.concurrency) || config.concurrency < 1)) {
305
+ issues.push({ field: "concurrency", message: `must be an integer >= 1 (got ${config.concurrency})` });
306
+ }
307
+ if (config.staggerDelayMs !== void 0 && (!Number.isFinite(config.staggerDelayMs) || config.staggerDelayMs < 0)) {
308
+ issues.push({ field: "staggerDelayMs", message: `must be a number >= 0 (got ${config.staggerDelayMs})` });
309
+ }
310
+ if (config.groupBy !== void 0 && config.groupBy !== "participant" && config.groupBy !== "round") {
311
+ issues.push({ field: "groupBy", message: `must be 'participant' or 'round' (got ${config.groupBy})` });
312
+ }
313
+ if (config.shapes !== void 0) {
314
+ if (typeof config.shapes !== "object" || config.shapes === null || Array.isArray(config.shapes)) {
315
+ issues.push({ field: "shapes", message: "must be a plain object" });
316
+ } else {
317
+ for (const [shapeName, shape] of Object.entries(config.shapes)) {
318
+ if (shape === null || typeof shape !== "object" || Array.isArray(shape)) {
319
+ issues.push({ field: `shapes['${shapeName}']`, message: "must be an object" });
320
+ continue;
321
+ }
322
+ const shapeObj = shape;
323
+ const slug = shapeObj.slug;
324
+ if (typeof slug !== "string" || slug === "" || !/^[a-z0-9][a-z0-9-]*$/.test(slug)) {
325
+ issues.push({ field: `shapes['${shapeName}'].slug`, message: `needs a lowercase slug (got ${JSON.stringify(slug)})` });
326
+ }
327
+ const name = shapeObj.name;
328
+ if (name !== void 0 && (typeof name !== "string" || name.trim() === "")) {
329
+ issues.push({ field: `shapes['${shapeName}'].name`, message: "must be a non-empty string" });
330
+ }
331
+ const staggerDelayMs = shapeObj.staggerDelayMs;
332
+ if (staggerDelayMs !== void 0 && (typeof staggerDelayMs !== "number" || !Number.isFinite(staggerDelayMs) || staggerDelayMs < 0)) {
333
+ issues.push({ field: `shapes['${shapeName}'].staggerDelayMs`, message: `must be a number >= 0 (got ${staggerDelayMs})` });
334
+ }
335
+ }
336
+ }
337
+ }
338
+ if (config.dimensions !== void 0) {
339
+ if (config.dimensions === null || typeof config.dimensions !== "object" || Array.isArray(config.dimensions)) {
340
+ issues.push({ field: "dimensions", message: "must be a plain object" });
341
+ }
342
+ }
343
+ if (config.customCliFlags !== void 0) {
344
+ if (!Array.isArray(config.customCliFlags) || !config.customCliFlags.every((f) => typeof f === "string" && f.startsWith("--"))) {
345
+ issues.push({ field: "customCliFlags", message: 'must be an array of strings starting with "--"' });
346
+ }
347
+ }
348
+ if (config.display !== void 0) {
349
+ try {
350
+ validateBenchmarkDisplayConfig(config.display);
351
+ } catch (error) {
352
+ issues.push({
353
+ field: "display",
354
+ message: error instanceof Error ? error.message : String(error)
355
+ });
356
+ }
357
+ }
358
+ if (config.display?.overview?.defaultMetric === "compositeScore" && config.scoring === void 0 && config.onScore === void 0) {
359
+ issues.push({
360
+ field: "display.overview.defaultMetric",
361
+ message: "cannot be 'compositeScore' without config.scoring or config.onScore"
362
+ });
363
+ }
364
+ return issues;
365
+ }
366
+ function defineOnComplete(onComplete) {
367
+ return onComplete;
368
+ }
75
369
 
76
370
  // src/no-available-participants.ts
77
371
  var NoAvailableParticipantsError = class extends Error {
@@ -88,12 +382,15 @@ var NoAvailableParticipantsError = class extends Error {
88
382
  // src/runner.ts
89
383
  import { execSync } from "child_process";
90
384
  import os from "os";
385
+ import { createBenchmarkClient, BenchmarkApiError } from "@benchsdk/api";
386
+ import { resolveAuth } from "@benchsdk/cli";
91
387
  import {
92
388
  BenchmarkReporter,
93
- createBenchmarkClient,
389
+ createSystemMetricsCollector,
94
390
  filterParticipantsByEnv,
391
+ runWorker,
95
392
  selectParticipants
96
- } from "@benchsdk/client";
393
+ } from "@benchsdk/worker";
97
394
 
98
395
  // src/scoring.ts
99
396
  function isFiniteNumber(v) {
@@ -149,40 +446,121 @@ var higherIsBetter = (name, opts) => ({
149
446
  ...opts,
150
447
  higherIsBetter: true
151
448
  });
152
- function score(outcome, spec) {
449
+ var WEIGHT_SUM_TOLERANCE = 0.01;
450
+ var ScoringSpecError = class extends Error {
451
+ constructor(message) {
452
+ super(message);
453
+ this.name = "ScoringSpecError";
454
+ }
455
+ };
456
+ function validateScoringSpec(spec) {
457
+ const totalWeight = spec.metrics.reduce(
458
+ (sum, m) => sum + m.weights.median + m.weights.p95 + m.weights.p99,
459
+ 0
460
+ );
461
+ if (Math.abs(totalWeight - 1) > WEIGHT_SUM_TOLERANCE) {
462
+ const breakdown = spec.metrics.map((m) => `${m.name}=${(m.weights.median + m.weights.p95 + m.weights.p99).toFixed(3)}`).join(", ");
463
+ throw new ScoringSpecError(
464
+ `Scoring spec weights sum to ${totalWeight.toFixed(3)}, expected 1.0 (\xB1${WEIGHT_SUM_TOLERANCE}). Each metric's weights.median + weights.p95 + weights.p99, summed across every declared metric, must total 1.0 for compositeScore to stay a meaningful 0-100 scale. Per-metric totals: ${breakdown || "(no metrics declared)"}`
465
+ );
466
+ }
467
+ }
468
+ function groupRecordsByKey(records, key) {
469
+ const groups = /* @__PURE__ */ new Map();
470
+ for (const record of records) {
471
+ const raw = record.data?.[key];
472
+ const value = raw === void 0 ? void 0 : raw;
473
+ const mapKey = value === void 0 ? "__undefined__" : JSON.stringify(value);
474
+ let group = groups.get(mapKey);
475
+ if (!group) {
476
+ group = { value, records: [] };
477
+ groups.set(mapKey, group);
478
+ }
479
+ group.records.push(record);
480
+ }
481
+ return Array.from(groups.values());
482
+ }
483
+ function scoreGroup(records, spec, baseDimensions, groupKey, groupValue, provider, unitByMetric) {
153
484
  const successFilter = spec.success ?? ((r) => r.status === "success");
154
- const dimensions = toJsonObject(spec.dimensions ?? {});
485
+ const passing = records.filter(successFilter);
486
+ const successRate = records.length === 0 ? 0 : passing.length / records.length;
487
+ const skipped = records.length === 0;
488
+ let metricScoresSum = 0;
489
+ const metrics = [];
490
+ for (const metric of spec.metrics) {
491
+ const samples = collectSamples(metric, passing);
492
+ if (samples.length === 0) {
493
+ continue;
494
+ }
495
+ const { median, p95, p99 } = computeStats(samples, metric.trim ?? 0.05);
496
+ const metricScore = metric.weights.median * scoreStat(median, metric) + metric.weights.p95 * scoreStat(p95, metric) + metric.weights.p99 * scoreStat(p99, metric);
497
+ metricScoresSum += metricScore;
498
+ const unitKey = typeof metric.value === "string" ? metric.value : metric.name;
499
+ const unit = metric.unit ?? unitByMetric.get(unitKey) ?? "";
500
+ metrics.push({ name: metric.name, unit, median, p95, p99 });
501
+ }
502
+ const compositeScore = successRate === 0 ? 0 : Math.round(metricScoresSum * successRate * 100) / 100;
503
+ const dimensions = toJsonObject({
504
+ ...baseDimensions,
505
+ ...groupKey !== void 0 && groupValue !== void 0 ? { [groupKey]: groupValue } : {}
506
+ });
507
+ return {
508
+ provider,
509
+ dimensions,
510
+ metrics,
511
+ compositeScore,
512
+ successRate,
513
+ skipped
514
+ };
515
+ }
516
+ function score(outcome, spec, displayMetrics) {
517
+ validateScoringSpec(spec);
518
+ const baseDimensions = toJsonObject(spec.dimensions ?? {});
519
+ const unitByMetric = new Map(displayMetrics?.map((m) => [m.key, m.unit]));
155
520
  const results = [];
156
521
  for (const { participant, records } of outcome.participants) {
157
- const passing = records.filter(successFilter);
158
- const successRate = records.length === 0 ? 0 : passing.length / records.length;
159
- const skipped = records.length === 0;
160
- let metricScoresSum = 0;
161
- const metrics = [];
162
- for (const metric of spec.metrics) {
163
- const samples = collectSamples(metric, passing);
164
- if (samples.length === 0) {
165
- continue;
166
- }
167
- const { median, p95, p99 } = computeStats(samples, metric.trim ?? 0.05);
168
- const metricScore = metric.weights.median * scoreStat(median, metric) + metric.weights.p95 * scoreStat(p95, metric) + metric.weights.p99 * scoreStat(p99, metric);
169
- metricScoresSum += metricScore;
170
- metrics.push({ name: metric.name, unit: metric.unit, median, p95, p99 });
171
- }
172
- const compositeScore = successRate === 0 ? 0 : Math.round(metricScoresSum * successRate * 100) / 100;
173
- results.push({
174
- provider: participant,
175
- dimensions,
176
- metrics,
177
- compositeScore,
178
- successRate,
179
- skipped
180
- });
522
+ const groups = spec.groupBy && records.length > 0 ? [{ value: void 0, records }, ...groupRecordsByKey(records, spec.groupBy)] : [{ value: void 0, records }];
523
+ for (const group of groups) {
524
+ results.push(scoreGroup(group.records, spec, baseDimensions, spec.groupBy, group.value, participant, unitByMetric));
525
+ }
181
526
  }
182
527
  return results;
183
528
  }
529
+ function scoringConfigToSpec(config, dimensions, display) {
530
+ const success = config.success;
531
+ const unitByMetric = new Map(display?.metrics?.map((m) => [m.key, m.unit]));
532
+ return {
533
+ ...dimensions ? { dimensions: toJsonObject(dimensions) } : {},
534
+ ...config.groupBy ? { groupBy: config.groupBy } : {},
535
+ ...success ? {
536
+ success: (record) => record.status === "success" && Object.entries(success.requireData).every(([key, value]) => record.data?.[key] === value)
537
+ } : {},
538
+ metrics: config.metrics.map((metric) => ({
539
+ name: metric.key,
540
+ value: metric.key,
541
+ unit: (display?.metrics === void 0 ? metric.unit : unitByMetric.get(metric.key) ?? metric.unit) ?? "",
542
+ ceiling: metric.ceiling,
543
+ floor: metric.floor,
544
+ higherIsBetter: metric.higherIsBetter,
545
+ weights: metric.weights,
546
+ trim: metric.trim
547
+ }))
548
+ };
549
+ }
184
550
 
185
551
  // src/log-buffer.ts
552
+ var LOG_LEVEL_ORDER = ["debug", "info", "warn", "error"];
553
+ function isLogOptions(value) {
554
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
555
+ const o = value;
556
+ const keys = Object.keys(o);
557
+ if (keys.length === 0) return false;
558
+ if (!keys.every((k) => k === "level" || k === "meta")) return false;
559
+ if (o.level !== void 0 && (typeof o.level !== "string" || !LOG_LEVEL_ORDER.includes(o.level))) {
560
+ return false;
561
+ }
562
+ return true;
563
+ }
186
564
  var LogBuffer = class {
187
565
  lines = [];
188
566
  step(taskIndex, stepName, outcome) {
@@ -199,9 +577,15 @@ var LogBuffer = class {
199
577
  }
200
578
  }
201
579
  /** Appends a free-form narration line (backs the task context's `log`). */
202
- line(message, meta) {
203
- const suffix = meta && Object.keys(meta).length > 0 ? ` ${JSON.stringify(meta)}` : "";
204
- this.lines.push(`${(/* @__PURE__ */ new Date()).toISOString()} ${message}${suffix}`);
580
+ line(message, metaOrOptions) {
581
+ const opts = isLogOptions(metaOrOptions) ? { level: metaOrOptions.level ?? "info", meta: metaOrOptions.meta } : { level: "info", meta: metaOrOptions };
582
+ const suffix = opts.meta && Object.keys(opts.meta).length > 0 ? ` ${JSON.stringify(opts.meta)}` : "";
583
+ const taskMatch = message.match(/^(\[task \d+\])\s*(.*)$/);
584
+ if (taskMatch) {
585
+ this.lines.push(`${(/* @__PURE__ */ new Date()).toISOString()} ${taskMatch[1]} [${opts.level}] ${taskMatch[2]}${suffix}`);
586
+ } else {
587
+ this.lines.push(`${(/* @__PURE__ */ new Date()).toISOString()} [${opts.level}] ${message}${suffix}`);
588
+ }
205
589
  }
206
590
  isEmpty() {
207
591
  return this.lines.length === 0;
@@ -215,7 +599,6 @@ function indent(text, prefix = "") {
215
599
  }
216
600
 
217
601
  // src/runner.ts
218
- var DEFAULT_PLATFORM_URL = "https://platform.computesdk.com";
219
602
  function isEnvNoIngest() {
220
603
  const v = process.env.BENCHSDK_NO_INGEST;
221
604
  return v === "1" || v?.toLowerCase() === "true";
@@ -223,15 +606,68 @@ function isEnvNoIngest() {
223
606
  function sleep(ms) {
224
607
  return new Promise((resolve2) => setTimeout(resolve2, ms));
225
608
  }
609
+ function runWithConcurrency(fns, limit) {
610
+ if (limit >= fns.length || fns.length === 0) {
611
+ return Promise.all(fns.map((fn) => fn()));
612
+ }
613
+ const results = new Array(fns.length);
614
+ let running = 0;
615
+ let completed = 0;
616
+ let nextIndex = 0;
617
+ return new Promise((resolve2, reject) => {
618
+ const runNext = () => {
619
+ if (completed === fns.length) {
620
+ resolve2(results);
621
+ return;
622
+ }
623
+ while (running < limit && nextIndex < fns.length) {
624
+ const index = nextIndex++;
625
+ running++;
626
+ fns[index]().then(
627
+ (value) => {
628
+ results[index] = value;
629
+ running--;
630
+ completed++;
631
+ runNext();
632
+ },
633
+ (error) => reject(error)
634
+ );
635
+ }
636
+ };
637
+ runNext();
638
+ });
639
+ }
226
640
  function getErrorCode(error) {
227
- return error instanceof Error && error.name ? error.name : "ERROR";
641
+ if (error instanceof Error && "code" in error && typeof error.code === "string" && error.code) {
642
+ return error.code;
643
+ }
644
+ if (error instanceof Error && error.name) return error.name;
645
+ return "ERROR";
646
+ }
647
+ function isTaskError(error) {
648
+ return error instanceof Error && (error instanceof TaskError || error.name === "TaskError");
228
649
  }
229
- function withTimeout(promise, ms, name) {
650
+ var STEP_OUTCOME_KEYS = /* @__PURE__ */ new Set(["stdout", "stderr", "error", "exitCode", "code", "signal", "pid"]);
651
+ function isStepOutcome(value) {
652
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
653
+ const o = value;
654
+ const keys = Object.keys(o);
655
+ if (keys.length === 0) return false;
656
+ if (!keys.every((k) => STEP_OUTCOME_KEYS.has(k))) return false;
657
+ return typeof o.stdout === "string" || typeof o.stderr === "string" || typeof o.error === "string";
658
+ }
659
+ function withTimeout(promise, { stepName, timeoutMs, participantSlug }) {
230
660
  return new Promise((resolve2, reject) => {
231
- const timer = setTimeout(
232
- () => reject(new TaskError(`Step "${name}" timed out after ${ms}ms`, { code: "step_timeout" })),
233
- ms
234
- );
661
+ const timer = setTimeout(() => {
662
+ const participant = participantSlug ? ` for participant "${participantSlug}"` : "";
663
+ reject(
664
+ new TaskError(`Step "${stepName}" timed out after ${timeoutMs}ms${participant}`, {
665
+ code: "step_timeout",
666
+ step: stepName,
667
+ timeoutMs
668
+ })
669
+ );
670
+ }, timeoutMs);
235
671
  promise.then(
236
672
  (value) => {
237
673
  clearTimeout(timer);
@@ -244,20 +680,23 @@ function withTimeout(promise, ms, name) {
244
680
  );
245
681
  });
246
682
  }
247
- async function runStepInvocations(name, fn, options) {
248
- const requestedConcurrency = options?.concurrency;
249
- if (requestedConcurrency !== void 0 && (!Number.isInteger(requestedConcurrency) || requestedConcurrency < 1)) {
250
- throw new Error(`step "${name}" concurrency must be an integer >= 1 (got ${requestedConcurrency})`);
683
+ async function runStepInvocations(name, fn, options, participantSlug) {
684
+ if (options?.concurrency !== void 0) {
685
+ console.warn(`[benchsdk] step "${name}" option "concurrency" is deprecated; use "parallelInvocations"`);
686
+ }
687
+ const requestedParallelism = options?.parallelInvocations ?? options?.concurrency;
688
+ if (requestedParallelism !== void 0 && (!Number.isInteger(requestedParallelism) || requestedParallelism < 1)) {
689
+ throw new Error(`step "${name}" parallelInvocations must be an integer >= 1 (got ${requestedParallelism})`);
251
690
  }
252
691
  const timeoutMs = options?.timeoutMs;
253
692
  if (timeoutMs !== void 0 && (!Number.isFinite(timeoutMs) || timeoutMs < 0)) {
254
693
  throw new Error(`step "${name}" timeoutMs must be a number >= 0 (got ${timeoutMs})`);
255
694
  }
256
- const count = requestedConcurrency ?? 1;
695
+ const count = requestedParallelism ?? 1;
257
696
  const invocations = Array.from({ length: count }, () => {
258
697
  const promise = Promise.resolve().then(() => fn());
259
698
  if (timeoutMs === void 0) return promise;
260
- return withTimeout(promise, timeoutMs, name);
699
+ return withTimeout(promise, { stepName: name, timeoutMs, participantSlug });
261
700
  });
262
701
  if (count === 1) {
263
702
  return invocations[0];
@@ -275,18 +714,20 @@ async function runStepInvocations(name, fn, options) {
275
714
  if (firstError !== void 0) throw firstError;
276
715
  return results;
277
716
  }
278
- async function runStepWithClient(clientStep, name, fn, options) {
279
- const { concurrency: runnerConcurrency, timeoutMs, ...clientOptions } = options ?? {};
717
+ async function runStepWithClient(clientStep, name, fn, options, participantSlug) {
718
+ const { parallelInvocations: runnerParallelism, concurrency: deprecatedConcurrency, timeoutMs, ...clientOptions } = options ?? {};
280
719
  const clientStepOptions = {
281
720
  ...clientOptions,
282
721
  timeoutMs,
283
- stepConcurrency: runnerConcurrency
722
+ stepConcurrency: runnerParallelism ?? deprecatedConcurrency
284
723
  };
285
- const result = await clientStep(name, () => runStepInvocations(name, fn, options), clientStepOptions);
724
+ const result = await clientStep(name, () => runStepInvocations(name, fn, options, participantSlug), clientStepOptions);
286
725
  return result;
287
726
  }
288
- function parseCliArgs(argv) {
727
+ function parseCliArgs(argv, allowedCustomFlags) {
289
728
  const args = {};
729
+ const unknown = [];
730
+ const allowed = new Set(allowedCustomFlags ?? []);
290
731
  const readValue = (raw, i) => {
291
732
  const eq = raw.indexOf("=");
292
733
  if (eq !== -1) return { value: raw.slice(eq + 1), nextIndex: i };
@@ -378,22 +819,46 @@ function parseCliArgs(argv) {
378
819
  case "--dry-run":
379
820
  args.noIngest = true;
380
821
  break;
381
- default:
822
+ default: {
823
+ if (allowed.has(name)) {
824
+ if (!arg.includes("=")) {
825
+ const next = argv[i + 1];
826
+ if (next && !next.startsWith("-")) {
827
+ i++;
828
+ }
829
+ }
830
+ } else {
831
+ unknown.push(name);
832
+ if (!arg.includes("=")) {
833
+ const next = argv[i + 1];
834
+ if (next && !next.startsWith("-")) {
835
+ i++;
836
+ }
837
+ }
838
+ }
382
839
  break;
840
+ }
383
841
  }
384
842
  }
843
+ if (unknown.length > 0) {
844
+ throw new Error(`Unknown flag(s): ${unknown.join(", ")}`);
845
+ }
385
846
  if (!args.noIngest && isEnvNoIngest()) {
386
847
  args.noIngest = true;
387
848
  }
388
849
  return args;
389
850
  }
390
851
  function mergeConfig(config, args) {
391
- const phaseTotal = config.phases?.reduce((sum, p) => sum + p.iterations, 0);
392
- if (phaseTotal !== void 0 && args.iterations !== void 0) {
393
- console.warn("--iterations is ignored because this benchmark declares phases.");
852
+ const phases = config.phases;
853
+ const unevenPhases = phases !== void 0 && phases.some((p) => p.iterations !== phases[0].iterations);
854
+ if (unevenPhases && args.iterations !== void 0) {
855
+ console.warn("--iterations is ignored because this benchmark sizes its phases individually.");
394
856
  }
857
+ const phaseIterations = phases !== void 0 && !unevenPhases ? args.iterations : void 0;
858
+ const phaseTotal = phases !== void 0 ? phaseIterations !== void 0 ? phaseIterations * phases.length : phases.reduce((sum, p) => sum + p.iterations, 0) : void 0;
395
859
  const resolved = {
396
860
  iterations: phaseTotal ?? args.iterations ?? config.iterations ?? 1,
861
+ phaseIterations,
397
862
  concurrency: args.concurrency ?? config.concurrency ?? 1,
398
863
  staggerDelayMs: args.staggerDelayMs ?? config.staggerDelayMs ?? 0,
399
864
  groupBy: args.groupBy ?? config.groupBy ?? "participant",
@@ -407,13 +872,16 @@ function mergeConfig(config, args) {
407
872
  }
408
873
  return resolved;
409
874
  }
410
- function buildSchedule(config, iterations, task) {
875
+ function buildSchedule(config, resolved, task) {
411
876
  if (config.phases?.length) {
412
877
  return config.phases.flatMap(
413
- (phase) => Array.from({ length: phase.iterations }, () => ({ phase: phase.name, task }))
878
+ (phase) => Array.from({ length: resolved.phaseIterations ?? phase.iterations }, () => ({
879
+ phase: phase.name,
880
+ task
881
+ }))
414
882
  );
415
883
  }
416
- return Array.from({ length: iterations }, () => ({ phase: void 0, task }));
884
+ return Array.from({ length: resolved.iterations }, () => ({ phase: void 0, task }));
417
885
  }
418
886
  function defaultOnResult(record, meta) {
419
887
  const n = record.taskIndex + 1;
@@ -421,21 +889,10 @@ function defaultOnResult(record, meta) {
421
889
  const data = record.data && Object.keys(record.data).length > 0 ? ` ${JSON.stringify(record.data)}` : "";
422
890
  console.log(` [${meta.participant}] Task ${n}/${meta.iterations}: success${data}`);
423
891
  } else {
424
- console.log(` [${meta.participant}] Task ${n}/${meta.iterations}: FAILED \u2014 ${record.errorCode ?? "unknown error"}`);
425
- }
426
- }
427
- function resolvePlatform() {
428
- const root = (process.env.BENCHMARKS_PLATFORM_URL || DEFAULT_PLATFORM_URL).replace(/\/+$/, "");
429
- const apiKey = process.env.BENCHMARKS_PLATFORM_API_KEY;
430
- if (!apiKey) {
431
- throw new Error(
432
- "BENCHMARKS_PLATFORM_API_KEY is required. Create an org-scoped API key in your organization settings on the platform and set it in your .env."
433
- );
892
+ const detail = record.data?.errorMessage ?? record.data?.error;
893
+ const suffix = typeof detail === "string" && detail.length > 0 ? `: ${detail}` : "";
894
+ console.log(` [${meta.participant}] Task ${n}/${meta.iterations}: FAILED \u2014 ${record.errorCode ?? "unknown error"}${suffix}`);
434
895
  }
435
- return {
436
- baseUrl: `${root}/api/v1`,
437
- apiKey
438
- };
439
896
  }
440
897
  function resolveShape(config, shapeName) {
441
898
  if (!shapeName) return void 0;
@@ -475,23 +932,74 @@ function resolveParticipants(config, resolved) {
475
932
  if (available.length === 0) throw new NoAvailableParticipantsError(skipped);
476
933
  return available;
477
934
  }
478
- async function runBenchmark(fileConfig, task, argv = []) {
479
- const args = parseCliArgs(argv);
935
+ function resolveTriggerSource(env = process.env) {
936
+ const source = env.BENCH_TRIGGER_SOURCE?.trim();
937
+ if (source) return source;
938
+ return env.GITHUB_EVENT_NAME?.trim() || "manual";
939
+ }
940
+ function triggerToJson(env = process.env) {
941
+ const requestedBy = env.BENCH_TRIGGER_REQUESTED_BY?.trim();
942
+ const requestId = env.BENCH_TRIGGER_REQUEST_ID?.trim();
943
+ const event = env.GITHUB_EVENT_NAME?.trim();
944
+ return {
945
+ source: resolveTriggerSource(env),
946
+ ...event ? { event } : {},
947
+ ...requestedBy ? { requestedBy } : {},
948
+ ...requestId ? { requestId } : {}
949
+ };
950
+ }
951
+ function runConfigToJson(config, resolved, participants, env = process.env) {
952
+ const phases = config.phases?.map((phase) => ({
953
+ name: phase.name,
954
+ iterations: resolved.phaseIterations ?? phase.iterations
955
+ }));
956
+ const runConfig = {
957
+ benchmarkSlug: config.benchmarkSlug,
958
+ benchmarkName: config.benchmarkName,
959
+ ...resolved.phaseIterations !== void 0 ? { phaseIterations: resolved.phaseIterations } : {},
960
+ ...phases ? { phases } : {},
961
+ ...!config.phases ? { iterations: resolved.iterations } : {},
962
+ concurrency: resolved.concurrency,
963
+ staggerDelayMs: resolved.staggerDelayMs,
964
+ groupBy: resolved.groupBy,
965
+ ...config.dimensions ? { dimensions: config.dimensions } : {},
966
+ ...config.scoring ? { scoring: config.scoring } : {},
967
+ participants,
968
+ trigger: triggerToJson(env)
969
+ };
970
+ return JSON.parse(JSON.stringify(runConfig));
971
+ }
972
+ async function runBenchmark(fileConfig, task, argv = [], options = {}) {
973
+ const args = parseCliArgs(argv, fileConfig.customCliFlags);
480
974
  const noIngest = args.noIngest ?? isEnvNoIngest();
481
975
  const shaped = applyShape(fileConfig, resolveShape(fileConfig, args.shape));
482
976
  const config = applyIdentityOverrides(shaped, args);
483
977
  const resolved = mergeConfig(config, args);
484
- const available = resolveParticipants(config, resolved);
485
978
  let baseUrl = "";
486
979
  let apiKey = "";
980
+ let token;
981
+ let orgSlug;
982
+ let orgId;
487
983
  let client = null;
488
- if (!noIngest) {
489
- ({ baseUrl, apiKey } = resolvePlatform());
490
- client = createBenchmarkClient({ baseUrl, apiKey });
984
+ const auth = noIngest ? await resolveAuth({ baseUrl: options.baseUrl, apiKey: options.apiKey }).catch(() => null) : await resolveAuth({ baseUrl: options.baseUrl, apiKey: options.apiKey });
985
+ if (auth) {
986
+ baseUrl = auth.apiBaseUrl;
987
+ apiKey = auth.apiKey ?? "";
988
+ token = auth.token;
989
+ orgSlug = auth.orgSlug;
990
+ orgId = auth.orgId;
991
+ client = createBenchmarkClient({
992
+ baseUrl: auth.apiBaseUrl,
993
+ apiKey: auth.apiKey,
994
+ token: auth.token,
995
+ orgSlug: auth.orgSlug,
996
+ orgId: auth.orgId
997
+ });
491
998
  }
492
- const schedule = buildSchedule(config, resolved.iterations, task);
999
+ const available = resolveParticipants(config, resolved);
1000
+ const schedule = buildSchedule(config, resolved, task);
493
1001
  const totalTasks = schedule.length;
494
- const concurrencyLabel = resolved.groupBy === "round" ? "n/a (round mode)" : String(resolved.concurrency);
1002
+ const concurrencyLabel = String(resolved.concurrency);
495
1003
  console.log(`${config.benchmarkName} (self-contained)`);
496
1004
  console.log(`Date: ${(/* @__PURE__ */ new Date()).toISOString()}`);
497
1005
  if (noIngest) {
@@ -508,14 +1016,32 @@ async function runBenchmark(fileConfig, task, argv = []) {
508
1016
  runId = "no-ingest";
509
1017
  dashboardUrl = "";
510
1018
  } else {
511
- if (identityIsOurs) {
512
- await client.upsertBenchmark(config.benchmarkSlug, {
513
- name: config.benchmarkName
514
- });
1019
+ {
1020
+ const benchmarkConfig = {
1021
+ ...config.scoring ? { scoring: config.scoring } : {},
1022
+ ...config.display ? { display: config.display } : {}
1023
+ };
1024
+ let initializeTarget = identityIsOurs;
1025
+ if (!initializeTarget) {
1026
+ try {
1027
+ await client.getBenchmark(config.benchmarkSlug);
1028
+ } catch (error) {
1029
+ if (!(error instanceof BenchmarkApiError && error.status === 404)) throw error;
1030
+ initializeTarget = true;
1031
+ }
1032
+ }
1033
+ const upsertInput = {};
1034
+ if (initializeTarget) {
1035
+ upsertInput.name = config.benchmarkName;
1036
+ if (Object.keys(benchmarkConfig).length > 0) upsertInput.config = benchmarkConfig;
1037
+ }
1038
+ await client.upsertBenchmark(config.benchmarkSlug, upsertInput);
515
1039
  }
1040
+ const runConfig = client ? runConfigToJson(config, resolved, available.map((p) => p.name)) : {};
516
1041
  if (args.runKey) {
517
1042
  const { run: run2, organizationSlug } = await client.createRun(config.benchmarkSlug, {
518
- runKey: args.runKey
1043
+ runKey: args.runKey,
1044
+ config: runConfig
519
1045
  });
520
1046
  runId = run2.id;
521
1047
  dashboardUrl = dashboardUrlFor(baseUrl, organizationSlug, config.benchmarkSlug, run2.id);
@@ -529,7 +1055,8 @@ async function runBenchmark(fileConfig, task, argv = []) {
529
1055
  const { run: run2, organizationSlug } = await client.createRun(config.benchmarkSlug, {
530
1056
  totalTasks,
531
1057
  workerCount: 1,
532
- participants: available.map((p) => p.name)
1058
+ participants: available.map((p) => p.name),
1059
+ config: runConfig
533
1060
  });
534
1061
  runId = run2.id;
535
1062
  dashboardUrl = dashboardUrlFor(baseUrl, organizationSlug, config.benchmarkSlug, run2.id);
@@ -541,9 +1068,9 @@ async function runBenchmark(fileConfig, task, argv = []) {
541
1068
  const onResult = defaultOnResult;
542
1069
  let participantRecords;
543
1070
  if (resolved.groupBy === "round") {
544
- participantRecords = await runGroupedByRound(config, schedule, available, resolved, client, runId, baseUrl, apiKey, onResult, noIngest);
1071
+ participantRecords = await runGroupedByRound(config, schedule, available, resolved, client, runId, baseUrl, apiKey, token, orgSlug, orgId, onResult, noIngest);
545
1072
  } else {
546
- participantRecords = await runGroupedByParticipant(config, schedule, available, resolved, client, runId, onResult);
1073
+ participantRecords = await runGroupedByParticipant(config, schedule, available, resolved, client, runId, onResult, noIngest);
547
1074
  }
548
1075
  console.log(`All done. ${noIngest ? "No platform run created." : `View at: ${dashboardUrl}`}`);
549
1076
  const outcome = {
@@ -552,20 +1079,25 @@ async function runBenchmark(fileConfig, task, argv = []) {
552
1079
  participants: participantRecords,
553
1080
  config: resolved
554
1081
  };
555
- if (client && config.onScore) {
1082
+ if (!noIngest && client && (config.onScore || config.scoring)) {
556
1083
  try {
557
- const spec = await config.onScore(lowerIsBetter, higherIsBetter);
558
- const scored = score(outcome, spec);
1084
+ const spec = config.onScore ? await config.onScore(lowerIsBetter, higherIsBetter) : scoringConfigToSpec(config.scoring, config.dimensions, config.display);
1085
+ const scored = score(outcome, spec, config.display?.metrics);
559
1086
  const run2 = {
560
1087
  gitSha: process.env.GITHUB_SHA ?? getGitSha(),
561
1088
  gitRef: process.env.GITHUB_REF_NAME ?? process.env.GITHUB_REF ?? getGitRef(),
562
- triggeredBy: process.env.GITHUB_EVENT_NAME ?? "manual",
1089
+ triggeredBy: resolveTriggerSource(),
563
1090
  nodeVersion: process.version,
564
1091
  platform: os.platform(),
565
1092
  arch: os.arch()
566
1093
  };
567
- await client.submitRunSummary(config.benchmarkSlug, runId, { run: run2, results: scored });
1094
+ await client.submitRunSummary(config.benchmarkSlug, runId, {
1095
+ run: run2,
1096
+ results: scored,
1097
+ ...config.scoring ? { scoring: config.scoring } : {}
1098
+ });
568
1099
  } catch (err) {
1100
+ if (err instanceof ScoringSpecError) throw err;
569
1101
  const message = err instanceof Error ? err.message : String(err);
570
1102
  console.warn(`[benchsdk-runner] failed to submit run summary: ${message}`);
571
1103
  }
@@ -573,6 +1105,22 @@ async function runBenchmark(fileConfig, task, argv = []) {
573
1105
  if (config.onComplete) await config.onComplete(outcome);
574
1106
  return outcome;
575
1107
  }
1108
+ async function runBenchmarkWorker(options) {
1109
+ const config = defineBenchmarkConfig({
1110
+ benchmarkSlug: options.benchmarkSlug,
1111
+ benchmarkName: options.benchmarkName ?? options.benchmarkSlug,
1112
+ participants: [options.participant],
1113
+ iterations: options.iterations ?? 1,
1114
+ concurrency: options.concurrency ?? 1,
1115
+ staggerDelayMs: options.staggerDelayMs ?? 0,
1116
+ groupBy: options.groupBy ?? "participant",
1117
+ defaultProviders: [options.participant.name]
1118
+ });
1119
+ const argv = [];
1120
+ if (options.runKey) argv.push("--run-key", options.runKey);
1121
+ if (options.noIngest) argv.push("--dry-run");
1122
+ return runBenchmark(config, options.task, argv);
1123
+ }
576
1124
  function getGitSha() {
577
1125
  if (process.env.GITHUB_SHA) return process.env.GITHUB_SHA;
578
1126
  try {
@@ -590,13 +1138,13 @@ function getGitRef() {
590
1138
  return void 0;
591
1139
  }
592
1140
  }
593
- async function runGroupedByParticipant(config, schedule, available, resolved, client, runId, onResult) {
1141
+ async function runGroupedByParticipant(config, schedule, available, resolved, client, runId, onResult, noIngest) {
594
1142
  const participantRecords = [];
595
1143
  for (const participant of available) {
596
1144
  console.log(`${"=".repeat(70)}`);
597
1145
  console.log(` Participant: ${participant.name}`);
598
1146
  console.log("=".repeat(70));
599
- if (!client) {
1147
+ if (noIngest || !client) {
600
1148
  const records = [];
601
1149
  let rampStartMs2;
602
1150
  let nextIndex = 0;
@@ -628,7 +1176,7 @@ async function runGroupedByParticipant(config, schedule, available, resolved, cl
628
1176
  }
629
1177
  let rampStartMs;
630
1178
  await client.planWorkers(config.benchmarkSlug, runId, participant.name);
631
- const result = await client.runWorker({
1179
+ const result = await runWorker(client, {
632
1180
  benchmarkSlug: config.benchmarkSlug,
633
1181
  runId,
634
1182
  participantSlug: participant.name,
@@ -641,16 +1189,21 @@ async function runGroupedByParticipant(config, schedule, available, resolved, cl
641
1189
  if (waitMs > 0) await sleep(waitMs);
642
1190
  }
643
1191
  const slot = schedule[scheduleIndex];
644
- const taskResult = await slot.task({
645
- participant,
646
- taskIndex: scheduleIndex,
647
- phase: slot.phase,
648
- step: (name, fn, options) => runStepWithClient(ctx.step, name, fn, options),
649
- measure: ctx.measure,
650
- log: ctx.log
651
- });
652
1192
  if (slot.phase) ctx.measure({ phase: slot.phase });
653
- return taskResult?.data;
1193
+ try {
1194
+ const taskResult = await slot.task({
1195
+ participant,
1196
+ taskIndex: scheduleIndex,
1197
+ phase: slot.phase,
1198
+ step: (name, fn, options) => runStepWithClient(ctx.step, name, fn, options, participant.name),
1199
+ measure: ctx.measure,
1200
+ log: ctx.log
1201
+ });
1202
+ return taskResult?.data;
1203
+ } catch (error) {
1204
+ if (isTaskError(error) && error.data) ctx.measure(error.data);
1205
+ throw error;
1206
+ }
654
1207
  },
655
1208
  onResult: (record) => onResult(record, { iterations: schedule.length, participant: participant.name })
656
1209
  });
@@ -666,14 +1219,17 @@ async function runGroupedByParticipant(config, schedule, available, resolved, cl
666
1219
  }
667
1220
  return participantRecords;
668
1221
  }
669
- async function runGroupedByRound(config, schedule, available, resolved, client, runId, baseUrl, apiKey, onResult, noIngest = false) {
1222
+ async function runGroupedByRound(config, schedule, available, resolved, client, runId, baseUrl, apiKey, token, orgSlug, orgId, onResult, noIngest = false) {
670
1223
  const reporters = /* @__PURE__ */ new Map();
671
1224
  const logBuffers = /* @__PURE__ */ new Map();
672
1225
  const failed = /* @__PURE__ */ new Map();
673
1226
  const recordsByParticipant = /* @__PURE__ */ new Map();
1227
+ let metricsCollector;
1228
+ const metricsSamples = [];
674
1229
  for (const participant of available) {
675
1230
  logBuffers.set(participant.name, new LogBuffer());
676
1231
  failed.set(participant.name, false);
1232
+ recordsByParticipant.set(participant.name, []);
677
1233
  if (noIngest || !client) {
678
1234
  reporters.set(participant.name, null);
679
1235
  continue;
@@ -687,6 +1243,9 @@ async function runGroupedByRound(config, schedule, available, resolved, client,
687
1243
  reporter = await BenchmarkReporter.claim({
688
1244
  baseUrl,
689
1245
  apiKey,
1246
+ token,
1247
+ orgSlug,
1248
+ orgId,
690
1249
  benchmarkSlug: config.benchmarkSlug,
691
1250
  runId,
692
1251
  participantSlug: participant.name,
@@ -700,6 +1259,10 @@ async function runGroupedByRound(config, schedule, available, resolved, client,
700
1259
  console.warn(` ${participant.name}: could not claim a platform worker \u2014 running without platform reporting.`);
701
1260
  }
702
1261
  reporters.set(participant.name, reporter);
1262
+ if (reporter && !metricsCollector) {
1263
+ metricsCollector = createSystemMetricsCollector();
1264
+ metricsSamples.push(await metricsCollector.sample());
1265
+ }
703
1266
  }
704
1267
  console.log(`Interleaving ${available.length} participant(s), ${schedule.length} round(s) each.
705
1268
  `);
@@ -708,7 +1271,7 @@ async function runGroupedByRound(config, schedule, available, resolved, client,
708
1271
  if (resolved.staggerDelayMs > 0 && i > 0) {
709
1272
  await sleep(resolved.staggerDelayMs);
710
1273
  }
711
- for (const participant of available) {
1274
+ const roundFns = available.map((participant) => async () => {
712
1275
  const reporter = reporters.get(participant.name) ?? null;
713
1276
  const logBuffer = logBuffers.get(participant.name);
714
1277
  const record = await runTaskRecord(
@@ -722,9 +1285,6 @@ async function runGroupedByRound(config, schedule, available, resolved, client,
722
1285
  if (record.status !== "success") failed.set(participant.name, true);
723
1286
  onResult(record, { iterations: schedule.length, participant: participant.name });
724
1287
  reporter?.recordResult(record);
725
- if (!recordsByParticipant.has(participant.name)) {
726
- recordsByParticipant.set(participant.name, []);
727
- }
728
1288
  const participantRecords = recordsByParticipant.get(participant.name);
729
1289
  participantRecords.push(record);
730
1290
  if (reporter) {
@@ -736,7 +1296,22 @@ async function runGroupedByRound(config, schedule, available, resolved, client,
736
1296
  });
737
1297
  await reporter.heartbeat();
738
1298
  }
739
- }
1299
+ });
1300
+ await runWithConcurrency(roundFns, resolved.concurrency);
1301
+ if (metricsCollector) metricsSamples.push(await metricsCollector.sample());
1302
+ }
1303
+ if (metricsCollector) metricsSamples.push(await metricsCollector.sample());
1304
+ metricsCollector?.stop();
1305
+ const metricsReporter = available.map((p) => reporters.get(p.name)).find((r) => Boolean(r));
1306
+ if (metricsReporter && metricsSamples.length > 0) {
1307
+ await metricsReporter.uploadArtifact({
1308
+ kind: "system-metrics",
1309
+ contentType: "application/x-ndjson",
1310
+ name: "metrics.jsonl",
1311
+ metadata: { scope: "shared-process", participants: available.map((p) => p.name) },
1312
+ body: metricsSamples.map((sample) => JSON.stringify(sample)).join("\n") + "\n"
1313
+ }).catch(() => {
1314
+ });
740
1315
  }
741
1316
  for (const participant of available) {
742
1317
  const reporter = reporters.get(participant.name) ?? null;
@@ -777,17 +1352,19 @@ async function runTaskRecord(task, participant, scheduleIndex, taskIndex, phase,
777
1352
  completedAt: new Date(stepStartedAtMs).toISOString(),
778
1353
  latencyMs: 0
779
1354
  };
780
- if (options?.concurrency !== void 0) stepRecord.concurrency = options.concurrency;
1355
+ const requestedParallelism = options?.parallelInvocations ?? options?.concurrency;
1356
+ if (requestedParallelism !== void 0) stepRecord.concurrency = requestedParallelism;
781
1357
  if (options?.timeoutMs !== void 0) stepRecord.timeoutMs = options.timeoutMs;
782
1358
  const previousStep = activeStep;
783
1359
  activeStep = stepRecord;
784
1360
  try {
785
- const result2 = await runStepInvocations(name, fn, options);
786
- logBuffer.step(taskIndex, name, {});
1361
+ const result2 = await runStepInvocations(name, fn, options, participant.name);
1362
+ const outcome = options?.captureOutput !== false && !Array.isArray(result2) && isStepOutcome(result2) ? result2 : {};
1363
+ logBuffer.step(taskIndex, name, outcome);
787
1364
  return result2;
788
1365
  } catch (error) {
789
1366
  stepRecord.status = "error";
790
- stepRecord.errorCode = error instanceof TaskError ? error.code ?? error.name : getErrorCode(error);
1367
+ stepRecord.errorCode = isTaskError(error) ? error.code ?? error.name : getErrorCode(error);
791
1368
  logBuffer.step(taskIndex, name, { error: error instanceof Error ? error.message : String(error) });
792
1369
  throw error;
793
1370
  } finally {
@@ -804,8 +1381,8 @@ async function runTaskRecord(task, participant, scheduleIndex, taskIndex, phase,
804
1381
  Object.assign(taskMeasures, data);
805
1382
  }
806
1383
  },
807
- log(message, meta) {
808
- logBuffer.line(`[task ${taskIndex}] ${message}`, meta);
1384
+ log(message, metaOrOptions) {
1385
+ logBuffer.line(`[task ${taskIndex}] ${message}`, metaOrOptions);
809
1386
  }
810
1387
  };
811
1388
  let result = void 0;
@@ -814,7 +1391,7 @@ async function runTaskRecord(task, participant, scheduleIndex, taskIndex, phase,
814
1391
  record.data = mergeData({ ...taskMeasures, ...result?.data ?? {} }, phase);
815
1392
  } catch (error) {
816
1393
  record.status = "error";
817
- if (error instanceof TaskError) {
1394
+ if (isTaskError(error)) {
818
1395
  record.errorCode = error.code ?? error.name;
819
1396
  record.data = mergeData({ ...taskMeasures, ...error.data ?? {} }, phase);
820
1397
  if (error.steps?.length) frameworkSteps.push(...error.steps);
@@ -850,16 +1427,145 @@ async function runTaskRecord(task, participant, scheduleIndex, taskIndex, phase,
850
1427
  // src/cli.ts
851
1428
  import { resolve } from "path";
852
1429
  import { pathToFileURL } from "url";
853
- 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]';
1430
+ import { run as runPlatformCli, resolveAuth as resolveAuth2 } from "@benchsdk/cli";
1431
+ import { createBenchmarkClient as createBenchmarkClient2 } from "@benchsdk/api";
1432
+ import { filterParticipantsByEnv as filterParticipantsByEnv2, selectParticipants as selectParticipants2 } from "@benchsdk/worker";
1433
+ 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>]';
854
1434
  function isBenchmarkConfig(value) {
855
1435
  if (typeof value !== "object" || value === null) return false;
856
1436
  const candidate = value;
857
1437
  return typeof candidate.benchmarkSlug === "string" && Array.isArray(candidate.participants);
858
1438
  }
1439
+ function shiftFlag(argv, name) {
1440
+ const prefix = `--${name}`;
1441
+ const prefixEq = `${prefix}=`;
1442
+ const result = [];
1443
+ let value;
1444
+ for (let i = 0; i < argv.length; i++) {
1445
+ const arg = argv[i];
1446
+ if (arg === prefix) {
1447
+ const next = argv[++i];
1448
+ if (!next || next.startsWith("--")) throw new Error(USAGE);
1449
+ value = next;
1450
+ continue;
1451
+ }
1452
+ if (arg.startsWith(prefixEq)) {
1453
+ const eqValue = arg.slice(prefixEq.length);
1454
+ if (!eqValue) throw new Error(USAGE);
1455
+ value = eqValue;
1456
+ continue;
1457
+ }
1458
+ result.push(arg);
1459
+ }
1460
+ return { value, argv: result };
1461
+ }
1462
+ function shiftPlatformFlags(flags) {
1463
+ const { value: baseUrl, argv: withoutBaseUrl } = shiftFlag(flags, "base-url");
1464
+ const { value: apiKey, argv: rest } = shiftFlag(withoutBaseUrl, "api-key");
1465
+ return { baseUrl, apiKey, flags: rest };
1466
+ }
1467
+ async function runCheck(argv) {
1468
+ const [command, ...rest] = argv;
1469
+ const [file, ...flags] = rest;
1470
+ if (command !== "check" || !file || file.startsWith("-")) throw new Error(USAGE);
1471
+ const mod = await import(pathToFileURL(resolve(process.cwd(), file)).href);
1472
+ const config = mod.config;
1473
+ const task = mod.task ?? mod.default;
1474
+ if (!isBenchmarkConfig(config)) {
1475
+ throw new Error(`${file} must export a \`config\` created with defineBenchmarkConfig (with participants).`);
1476
+ }
1477
+ if (typeof task !== "function") {
1478
+ throw new Error(`${file} must export a \`task\` created with defineTask.`);
1479
+ }
1480
+ const cfg = config;
1481
+ const configIssues = validateBenchmarkConfig(cfg);
1482
+ if (configIssues.length > 0) {
1483
+ throw new BenchmarkConfigError(configIssues);
1484
+ }
1485
+ const { baseUrl, apiKey, flags: runnerFlags } = shiftPlatformFlags(flags);
1486
+ const parsed = parseCliArgs(runnerFlags, cfg.customCliFlags ?? []);
1487
+ resolveShape(cfg, parsed.shape);
1488
+ const dryRun = parsed.noIngest ?? false;
1489
+ let client;
1490
+ let apiOk = dryRun;
1491
+ let auth = null;
1492
+ if (!dryRun) {
1493
+ try {
1494
+ auth = await resolveAuth2({ baseUrl, apiKey });
1495
+ client = createBenchmarkClient2({
1496
+ baseUrl: auth.apiBaseUrl,
1497
+ apiKey: auth.apiKey,
1498
+ token: auth.token,
1499
+ orgSlug: auth.orgSlug,
1500
+ orgId: auth.orgId
1501
+ });
1502
+ await client.listBenchmarks({ limit: 1 });
1503
+ apiOk = true;
1504
+ } catch (err) {
1505
+ const message = err instanceof Error ? err.message : String(err);
1506
+ console.warn(`[benchsdk] API connectivity check failed: ${message}`);
1507
+ }
1508
+ }
1509
+ const effectiveProviderNames = parsed.providers ?? cfg.defaultProviders;
1510
+ let selected;
1511
+ try {
1512
+ selected = selectParticipants2(cfg.participants, effectiveProviderNames);
1513
+ } catch (err) {
1514
+ throw new Error(`Participant selection failed: ${err instanceof Error ? err.message : err}`);
1515
+ }
1516
+ const { available, skipped } = filterParticipantsByEnv2(selected);
1517
+ let scoringOk = true;
1518
+ if (cfg.onScore) {
1519
+ try {
1520
+ const spec = await cfg.onScore(lowerIsBetter, higherIsBetter);
1521
+ validateScoringSpec(spec);
1522
+ } catch (err) {
1523
+ scoringOk = false;
1524
+ const message = err instanceof Error ? err.message : String(err);
1525
+ console.warn(`[benchsdk] Scoring validation failed: ${message}`);
1526
+ }
1527
+ } else if (cfg.scoring) {
1528
+ try {
1529
+ const spec = scoringConfigToSpec(cfg.scoring, cfg.dimensions);
1530
+ validateScoringSpec(spec);
1531
+ } catch (err) {
1532
+ scoringOk = false;
1533
+ const message = err instanceof Error ? err.message : String(err);
1534
+ console.warn(`[benchsdk] Scoring validation failed: ${message}`);
1535
+ }
1536
+ }
1537
+ const missingPlatformAuth = dryRun ? [] : auth ? [] : [["BENCHMARKS_PLATFORM_API_KEY or BENCHMARKS_PLATFORM_TOKEN", void 0]];
1538
+ for (const [name] of missingPlatformAuth) {
1539
+ console.warn(`[benchsdk] ${name} is not set`);
1540
+ }
1541
+ const report = {
1542
+ file,
1543
+ benchmarkSlug: cfg.benchmarkSlug,
1544
+ apiOk,
1545
+ envOk: dryRun || missingPlatformAuth.length === 0,
1546
+ participants: {
1547
+ requested: selected.map((p) => p.name),
1548
+ available: available.map((p) => p.name),
1549
+ skipped: skipped.map((s) => ({ name: s.name, missing: s.missing }))
1550
+ },
1551
+ scoringOk: cfg.scoring || cfg.onScore ? scoringOk : void 0
1552
+ };
1553
+ console.log(JSON.stringify(report, null, 2));
1554
+ const authFailure = !dryRun && missingPlatformAuth.length > 0;
1555
+ if (!apiOk || available.length === 0 || scoringOk === false || authFailure) {
1556
+ throw new Error("Benchmark check failed. See warnings above for details.");
1557
+ }
1558
+ }
859
1559
  async function runBenchmarkFile(argv) {
860
1560
  const [command, ...rest] = argv;
861
1561
  const [file, ...flags] = rest;
862
1562
  if (command !== "run" || !file || file.startsWith("-")) throw new Error(USAGE);
1563
+ const check = flags.includes("--check") || flags.includes("--validate");
1564
+ if (check) {
1565
+ const checkFlags = flags.filter((f) => f !== "--check" && f !== "--validate");
1566
+ return runCheck(["check", file, ...checkFlags]);
1567
+ }
1568
+ const { baseUrl, apiKey, flags: runnerFlags } = shiftPlatformFlags(flags);
863
1569
  const mod = await import(pathToFileURL(resolve(process.cwd(), file)).href);
864
1570
  const config = mod.config;
865
1571
  const task = mod.task ?? mod.default;
@@ -869,33 +1575,84 @@ async function runBenchmarkFile(argv) {
869
1575
  if (typeof task !== "function") {
870
1576
  throw new Error(`${file} must export a \`task\` created with defineTask.`);
871
1577
  }
872
- await runBenchmark(config, task, flags);
1578
+ const envFlags = [];
1579
+ if (process.env.BENCHMARK_SLUG) {
1580
+ envFlags.push("--benchmark", process.env.BENCHMARK_SLUG);
1581
+ }
1582
+ if (process.env.BENCHMARK_NAME) {
1583
+ envFlags.push("--name", process.env.BENCHMARK_NAME);
1584
+ }
1585
+ await runBenchmark(
1586
+ config,
1587
+ task,
1588
+ [...envFlags, ...runnerFlags],
1589
+ { baseUrl, apiKey }
1590
+ );
873
1591
  }
874
1592
  async function run(argv) {
1593
+ const [command, ...rest] = argv;
875
1594
  try {
876
- await runBenchmarkFile(argv);
1595
+ if (command === "run") {
1596
+ await runBenchmarkFile(argv);
1597
+ } else if (command === "check") {
1598
+ await runCheck(argv);
1599
+ } else {
1600
+ return runPlatformCli(argv);
1601
+ }
877
1602
  process.exit(0);
878
1603
  } catch (err) {
879
1604
  if (err instanceof NoAvailableParticipantsError) {
880
1605
  console.log(err.message);
881
1606
  process.exit(0);
882
1607
  }
883
- console.error("Benchmark failed:", err instanceof Error ? err.message : err);
1608
+ console.error("Benchmark failed:", String(err));
884
1609
  process.exit(1);
885
1610
  }
886
1611
  }
1612
+
1613
+ // src/index.ts
1614
+ import { resolveAuth as resolveAuth3, createApiClient, AuthError } from "@benchsdk/cli";
1615
+ import {
1616
+ runWorker as runWorker2,
1617
+ BenchmarkReporter as BenchmarkReporter2,
1618
+ claimBenchmarkReporter,
1619
+ createSystemMetricsCollector as createSystemMetricsCollector2,
1620
+ filterParticipantsByEnv as filterParticipantsByEnv3,
1621
+ selectParticipants as selectParticipants3
1622
+ } from "@benchsdk/worker";
1623
+ import { BenchmarkApiError as BenchmarkApiError2, createBenchmarkClient as createBenchmarkClient3 } from "@benchsdk/api";
1624
+ var BENCHSDK_RUNNER_VERSION = "0.5.2";
887
1625
  export {
1626
+ AuthError,
1627
+ BENCHSDK_RUNNER_VERSION,
1628
+ BenchmarkApiError2 as BenchmarkApiError,
1629
+ BenchmarkConfigError,
1630
+ BenchmarkReporter2 as BenchmarkReporter,
888
1631
  NoAvailableParticipantsError,
1632
+ ScoringSpecError,
889
1633
  TaskError,
1634
+ claimBenchmarkReporter,
1635
+ createApiClient,
1636
+ createBenchmarkClient3 as createBenchmarkClient,
1637
+ createSystemMetricsCollector2 as createSystemMetricsCollector,
890
1638
  defineBenchmarkConfig,
1639
+ defineOnComplete,
891
1640
  defineTask,
1641
+ filterParticipantsByEnv3 as filterParticipantsByEnv,
892
1642
  higherIsBetter,
893
1643
  lowerIsBetter,
894
1644
  mergeConfig,
895
1645
  parseCliArgs,
1646
+ resolveAuth3 as resolveAuth,
896
1647
  run,
897
1648
  runBenchmark,
898
1649
  runBenchmarkFile,
899
- score
1650
+ runBenchmarkWorker,
1651
+ runWorker2 as runWorker,
1652
+ score,
1653
+ scoringConfigToSpec,
1654
+ selectParticipants3 as selectParticipants,
1655
+ validateBenchmarkConfig,
1656
+ validateScoringSpec
900
1657
  };
901
1658
  //# sourceMappingURL=index.js.map