@benchsdk/runner 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/bench-config.ts","../src/no-available-participants.ts","../src/runner.ts","../src/scoring.ts","../src/log-buffer.ts","../src/cli.ts"],"sourcesContent":["export { defineBenchmarkConfig, defineTask, TaskError } from './bench-config.js';\nexport type {\n BenchmarkConfig,\n BenchmarkTask,\n TaskContext,\n TaskResult,\n TaskStepOptions,\n Phase,\n GroupBy,\n ParticipantRecords,\n ResolvedRunConfig,\n BenchmarkRunOutcome,\n} from './bench-config.js';\nexport { NoAvailableParticipantsError } from './no-available-participants.js';\nexport { runBenchmark, parseCliArgs, mergeConfig } from './runner.js';\nexport type { CliArgs } from './runner.js';\nexport { run, runBenchmarkFile } from './cli.js';\nexport { score, lowerIsBetter, higherIsBetter } from './scoring.js';\nexport type { ScoringSpec, MetricScoring, BenchmarkScoreResult } from './scoring.js';\n","/**\n * A `*.bench.ts` file is the composition of a **config** and a **task**:\n *\n * export const config = defineBenchmarkConfig({ benchmarkSlug, participants, ... });\n * export const task = defineTask(async (ctx) => { await ctx.step('work', () => ...); });\n *\n * `defineBenchmarkConfig` holds the orchestration knobs (including the\n * participants and an optional `onComplete` hook); `defineTask` holds the\n * workload. The `bench run <file>` binary imports the module, reads those two\n * exports, and drives the run. There is no \"mode\": all orchestration shapes\n * emerge from the knobs.\n *\n * iterations total tasks to run (default 1)\n * concurrency max tasks in flight at once — 1 = sequential, N = burst (default 1)\n * staggerDelayMs delay each task's start by taskIndex * staggerDelayMs (default 0)\n *\n * Common shapes:\n * sequential { iterations: N, concurrency: 1 }\n * burst { iterations: N, concurrency: N }\n * staggered { iterations: N, concurrency: N, staggerDelayMs: 200 }\n *\n * A benchmark can name these variants up front via `shapes`, so one file backs\n * several platform benchmarks (`bench run <file> --shape burst`) without\n * restating each one's slug/name in scripts and CI.\n *\n * A task is comprised of steps, declared via `ctx.step` inside a task function\n * — it supports closures, conditionals and try/finally, so values (a created\n * sandbox, say) flow naturally between steps. A task that declares no steps is\n * recorded as a single implicit `task` step. Measurements reach the platform\n * via `ctx.measure(...)`; step return values are control flow and never\n * recorded.\n */\nimport type {\n BaseParticipant,\n DefineStepOptions,\n JsonObject,\n TaskResultRecord,\n TaskStepRecord,\n} from '@benchsdk/client';\nimport type { HigherIsBetter, LowerIsBetter, ScoringSpec } from './scoring.js';\n\n/** How tasks are ordered across participants. */\nexport type GroupBy = 'participant' | 'round';\n\n/**\n * A named variant of a benchmark, selected with `--shape <name>`. A shape\n * carries only the parts that make it a distinct *benchmark* — its platform\n * identity plus any stable distinguishing knob (e.g. staggered's delay). The\n * scale knobs that vary per environment (`--iterations`, `--concurrency`) stay\n * on the invocation, so a shape never sets a value only to have the CLI\n * override it.\n */\nexport interface BenchmarkShape {\n /** Platform slug this shape reports under (e.g. 'sandbox-tti'). */\n slug: string;\n /** Display name shown on the platform; defaults to the slug. */\n name?: string;\n /** Default stagger delay (ms) for this shape; overridable with `--stagger-delay-ms`. */\n staggerDelayMs?: number;\n}\n\n/**\n * What a task returns: whatever it measured itself. This replaces the\n * assumption that the framework owns all timing. A plain data payload is\n * written explicitly as `{ data: {...} }`.\n */\nexport interface TaskResult {\n /** Free-form domain payload attached to the record (tokens, receipts, ...). */\n data?: JsonObject;\n /**\n * Pre-measured steps the task timed itself (e.g. socket phases).\n * Only honored in `groupBy: 'round'` runs, where the runner builds records\n * manually. In `groupBy: 'participant'` runs the platform worker\n * (`client.runWorker`) owns steps, so `steps` and `latencyMs` are ignored.\n */\n steps?: TaskStepRecord[];\n /** Task-owned overall latency; overrides framework wall-clock (round mode only). */\n latencyMs?: number;\n}\n\n/** Options for a single `ctx.step` invocation. */\nexport interface TaskStepOptions extends Omit<DefineStepOptions, 'concurrency' | 'stepConcurrency'> {\n /** Per-iteration timeout in milliseconds. If an invocation exceeds this, it is aborted and a `step_timeout` TaskError is thrown. */\n timeoutMs?: number;\n /** Number of times to invoke `fn` in parallel. Defaults to 1. When greater than 1, the step returns an array of results. */\n concurrency?: number;\n}\n\n/**\n * Throw this from a task to record a failure while preserving domain data and\n * any pre-measured steps (a plain thrown Error loses them).\n */\nexport class TaskError extends Error {\n readonly code?: string;\n readonly data?: JsonObject;\n readonly steps?: TaskStepRecord[];\n constructor(message: string, opts?: { code?: string; data?: JsonObject; steps?: TaskStepRecord[] }) {\n super(message);\n this.name = 'TaskError';\n this.code = opts?.code;\n this.data = opts?.data;\n this.steps = opts?.steps;\n }\n}\n\n/** Context handed to a benchmark `task` for a single iteration. */\nexport interface TaskContext<T extends BaseParticipant = BaseParticipant> {\n /** The participant this task is running for. */\n participant: T;\n /** Zero-based global task ordinal (matches the platform record's taskIndex). */\n taskIndex: number;\n /** Current phase name, when the benchmark declares `phases`. */\n phase?: string;\n /**\n * Runs `fn` as a named platform step. Mirrors `@benchsdk/client`'s\n * `RunWorkerContext.step`; supports closures and try/finally. A `concurrency`\n * greater than 1 invokes `fn` that many times in parallel and returns an array.\n * `timeoutMs` aborts any invocation that exceeds it with a `step_timeout` TaskError.\n */\n step<R, C extends number = 1>(\n name: string,\n fn: () => Promise<R> | R,\n options?: TaskStepOptions & { concurrency?: C },\n ): Promise<C extends 1 ? R : R[]>;\n /**\n * Attaches a JSON measurement to the platform. Inside a `step` it lands on\n * that step's data; at task top-level it lands on the task record's data.\n */\n measure(data: JsonObject): void;\n /** Appends a line to the worker log, uploaded as an artifact when the worker finishes. */\n log(message: string, meta?: JsonObject): void;\n}\n\nexport type BenchmarkTask<T extends BaseParticipant = BaseParticipant> = (\n ctx: TaskContext<T>,\n) => Promise<TaskResult | void> | TaskResult | void;\n\n/**\n * A named run segment with its own iteration count. Phases run in order; each\n * record is tagged with the phase name via `data.phase`, and `ctx.phase` lets\n * the task branch on identity instead of index arithmetic.\n */\nexport interface Phase {\n /** Phase name, tagged onto every record produced in this phase. */\n name: string;\n /** Iterations to run in this phase. */\n iterations: number;\n}\n\n/** One participant's collected task records from a run. */\nexport interface ParticipantRecords {\n participant: string;\n records: TaskResultRecord[];\n}\n\n/** The orchestration knobs a run actually used, after CLI overrides. */\nexport interface ResolvedRunConfig {\n iterations: number;\n concurrency: number;\n staggerDelayMs: number;\n groupBy: GroupBy;\n providers?: string[];\n}\n\n/**\n * Result of a benchmark run, passed to `config.onComplete`. Exposes the raw\n * per-participant records so completion hooks can write legacy local results.\n */\nexport interface BenchmarkRunOutcome {\n runId: string;\n /** Link to this run on the platform dashboard. */\n dashboardUrl: string;\n participants: ParticipantRecords[];\n config: ResolvedRunConfig;\n}\n\n/**\n * Orchestration config for a benchmark. Holds identity, the knobs, the\n * participants, and the optional completion hook — the workload lives in a\n * separate `defineTask`. `bench run <file>` reads the `config` and `task`\n * exports from the module and drives the run.\n */\nexport interface BenchmarkConfig<T extends BaseParticipant = BaseParticipant> {\n /**\n * Stable platform slug for this benchmark (e.g. 'sandbox-tti-local').\n * Selectable per run with `--shape` (or overridable with `--benchmark`), so\n * one entrypoint can report under several benchmarks.\n */\n benchmarkSlug: string;\n /** Human-readable name shown on the platform. Overridable with `--name`. */\n benchmarkName: string;\n /**\n * Named variants of this benchmark, selected with `--shape <name>`. Each\n * shape swaps in its own platform identity (and optional stable knob) while\n * reusing the same task and participants, so one bench file can back several\n * platform benchmarks without duplicating the slug/name triple across\n * package scripts and CI.\n */\n shapes?: Record<string, BenchmarkShape>;\n /**\n * Total tasks to run per participant. Default: 1. Mutually exclusive with\n * `phases` — when `phases` is set, total iterations = sum of phase iterations.\n */\n iterations?: number;\n /**\n * Named run segments (e.g. cold/warm). Runs in order; each record is tagged\n * with the phase name via `data.phase`. Mutually exclusive with `iterations`.\n */\n phases?: Phase[];\n /** Max tasks in flight at once. 1 = sequential, N = burst. Default: 1. */\n concurrency?: number;\n /** Delay each task's start by `taskIndex * staggerDelayMs`. Default: 0. */\n staggerDelayMs?: number;\n /**\n * Task ordering across participants. Default: 'participant' (run each\n * participant's tasks to completion, then the next). 'round' takes turns:\n * every participant runs its Nth task before anyone runs their (N+1)th, so\n * all participants' Nth tasks happen back-to-back under the same conditions.\n */\n groupBy?: GroupBy;\n /**\n * Default participant names to run when `--provider` is not passed. Omit to\n * run all env-available participants. `--provider` always overrides this.\n */\n defaultProviders?: string[];\n /** The participants this benchmark can run against. `--provider` selects a subset by name. */\n participants: T[];\n /**\n * Run-level scoring hook, called once with `lowerIsBetter` and `higherIsBetter`\n * primitives after the outcome is assembled but before `onComplete`. Use it to\n * define how the run should be scored and reported to the platform.\n */\n onScore?: (lowerIsBetter: LowerIsBetter, higherIsBetter: HigherIsBetter) => ScoringSpec | Promise<ScoringSpec>;\n /**\n * Run-level completion hook, called once with the full outcome after every\n * participant finishes. Use it for aggregate output (legacy JSON/SVG\n * writers). This is the run-level counterpart to per-step `ctx.measure`.\n */\n onComplete?: (outcome: BenchmarkRunOutcome) => void | Promise<void>;\n}\n\nfunction assertPositiveInt(value: number | undefined, field: string): void {\n if (value === undefined) return;\n if (!Number.isInteger(value) || value < 1) {\n throw new Error(`${field} must be an integer >= 1 (got ${value})`);\n }\n}\n\n/** Validates `config` at file-evaluation time so mistakes surface immediately. */\nexport function defineBenchmarkConfig<T extends BaseParticipant = BaseParticipant>(\n config: BenchmarkConfig<T>,\n): BenchmarkConfig<T> {\n if (!config.benchmarkSlug || typeof config.benchmarkSlug !== 'string') {\n throw new Error('benchmarkSlug is required');\n }\n if (!config.benchmarkName || typeof config.benchmarkName !== 'string') {\n throw new Error('benchmarkName is required');\n }\n if (config.phases !== undefined) {\n if (config.iterations !== undefined) {\n throw new Error('phases and iterations are mutually exclusive');\n }\n if (!Array.isArray(config.phases) || config.phases.length === 0) {\n throw new Error('phases must be a non-empty array');\n }\n const seen = new Set<string>();\n for (const phase of config.phases) {\n if (!phase.name || typeof phase.name !== 'string') {\n throw new Error('each phase requires a non-empty name');\n }\n if (seen.has(phase.name)) {\n throw new Error(`duplicate phase name: ${phase.name}`);\n }\n seen.add(phase.name);\n assertPositiveInt(phase.iterations, `phase '${phase.name}' iterations`);\n }\n }\n assertPositiveInt(config.iterations, 'iterations');\n assertPositiveInt(config.concurrency, 'concurrency');\n if (config.staggerDelayMs !== undefined && (!Number.isFinite(config.staggerDelayMs) || config.staggerDelayMs < 0)) {\n throw new Error(`staggerDelayMs must be a number >= 0 (got ${config.staggerDelayMs})`);\n }\n if (config.groupBy !== undefined && config.groupBy !== 'participant' && config.groupBy !== 'round') {\n throw new Error(`groupBy must be 'participant' or 'round' (got ${config.groupBy})`);\n }\n if (config.shapes !== undefined) {\n for (const [shapeName, shape] of Object.entries(config.shapes)) {\n if (!shape.slug || !/^[a-z0-9][a-z0-9-]*$/.test(shape.slug)) {\n throw new Error(`shape '${shapeName}' needs a lowercase slug (got ${JSON.stringify(shape.slug)})`);\n }\n if (shape.name !== undefined && (typeof shape.name !== 'string' || shape.name.trim() === '')) {\n throw new Error(`shape '${shapeName}' name must be a non-empty string`);\n }\n if (shape.staggerDelayMs !== undefined && (!Number.isFinite(shape.staggerDelayMs) || shape.staggerDelayMs < 0)) {\n throw new Error(`shape '${shapeName}' staggerDelayMs must be a number >= 0 (got ${shape.staggerDelayMs})`);\n }\n }\n }\n return config;\n}\n\n/**\n * Declares the workload for a benchmark: a function invoked once per iteration.\n * Steps are named via `ctx.step`, which supports closures and try/finally so\n * values flow naturally between steps.\n *\n * export const task = defineTask(async (ctx) => {\n * const sandbox = await ctx.step('create', () => provider.create());\n * try { await ctx.step('exec', () => sandbox.run('node -v')); }\n * finally { await ctx.step('destroy', () => sandbox.destroy()); }\n * });\n */\nexport function defineTask<T extends BaseParticipant = BaseParticipant>(\n task: BenchmarkTask<T>,\n): BenchmarkTask<T> {\n if (typeof task !== 'function') {\n throw new Error('defineTask requires a task function.');\n }\n return task;\n}\n","/**\n * Thrown when every selected participant was env-gated out, i.e. none of their\n * `requiredEnvVars` are set. This is a \"nothing to do\" outcome rather than a\n * failure — a benchmark job for a provider whose credentials aren't provisioned\n * should skip, not go red — so callers are expected to exit 0 on it.\n */\nexport class NoAvailableParticipantsError extends Error {\n readonly skipped: { name: string; missing: string[] }[];\n\n constructor(skipped: { name: string; missing: string[] }[]) {\n super(\n `No participants have their required env vars set — nothing to run${\n skipped.length > 0 ? ` (skipped: ${skipped.map((s) => s.name).join(', ')})` : ''\n }.`,\n );\n this.name = 'NoAvailableParticipantsError';\n this.skipped = skipped;\n }\n}\n","/**\n * CLI runner for `defineBenchmark` configs. Owns all platform orchestration\n * (upsert benchmark, create run, plan + drive workers per participant) so a\n * `*.bench.ts` file only has to declare its config and task. The orchestration\n * knobs (iterations / concurrency / staggerDelayMs / groupBy) can be overridden\n * per-invocation via CLI flags.\n *\n * Two execution orderings, chosen by `groupBy`:\n * 'participant' (default) — each participant's tasks run to completion via\n * `client.runWorker` (with its pooled concurrency + heartbeat reporting)\n * before the next participant starts.\n * 'round' — participants take turns: every participant runs its Nth task\n * before anyone starts their (N+1)th, so all Nth tasks happen back-to-back\n * under the same conditions. Driven manually via one `BenchmarkReporter`\n * per participant.\n */\nimport { execSync } from 'node:child_process';\nimport os from 'node:os';\nimport {\n BenchmarkReporter,\n createBenchmarkClient,\n filterParticipantsByEnv,\n selectParticipants,\n} from '@benchsdk/client';\nimport { NoAvailableParticipantsError } from './no-available-participants.js';\nimport { higherIsBetter, lowerIsBetter, score } from './scoring.js';\nimport type {\n BaseParticipant,\n BenchmarkClient,\n DefineStepOptions,\n JsonObject,\n RunWorkerContext,\n TaskResultRecord,\n TaskStepRecord,\n} from '@benchsdk/client';\nimport { TaskError } from './bench-config.js';\nimport type {\n BenchmarkConfig,\n BenchmarkRunOutcome,\n BenchmarkShape,\n BenchmarkTask,\n GroupBy,\n ParticipantRecords,\n ResolvedRunConfig,\n TaskContext,\n TaskResult,\n TaskStepOptions,\n} from './bench-config.js';\nimport { LogBuffer } from './log-buffer.js';\n\nexport interface CliArgs {\n /** Which platform benchmark to report as (`--benchmark`, aka the benchmark slug). */\n benchmark?: string;\n name?: string;\n /** Named variant from the bench file's `shapes` (`--shape`), swapping in its identity. */\n shape?: string;\n /**\n * Idempotency key (`--run-key`): sibling processes passing the same key share\n * one run (get-or-created), instead of each opening its own.\n */\n runKey?: string;\n iterations?: number;\n concurrency?: number;\n staggerDelayMs?: number;\n groupBy?: GroupBy;\n /** Participant names from `--provider a,b` (repeatable). */\n providers?: string[];\n /** When true, run locally and do not ingest/report to the platform. */\n noIngest?: boolean;\n}\n\n// Matches @benchsdk/client's DEFAULT_BASE_URL origin. `resolvePlatform()`\n// appends `/api/v1`. Override for local development via BENCHMARKS_PLATFORM_URL.\nconst DEFAULT_PLATFORM_URL = 'https://platform.computesdk.com';\n\nfunction isEnvNoIngest(): boolean {\n const v = process.env.BENCHSDK_NO_INGEST;\n return v === '1' || v?.toLowerCase() === 'true';\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction getErrorCode(error: unknown): string {\n return error instanceof Error && error.name ? error.name : 'ERROR';\n}\n\nfunction withTimeout<T>(promise: Promise<T>, ms: number, name: string): Promise<T> {\n return new Promise((resolve, reject) => {\n const timer = setTimeout(\n () => reject(new TaskError(`Step \"${name}\" timed out after ${ms}ms`, { code: 'step_timeout' })),\n ms,\n );\n promise.then(\n (value) => {\n clearTimeout(timer);\n resolve(value);\n },\n (error) => {\n clearTimeout(timer);\n reject(error);\n },\n );\n });\n}\n\nasync function runStepInvocations<R>(\n name: string,\n fn: () => Promise<R> | R,\n options: TaskStepOptions | undefined,\n): Promise<R | R[]> {\n const requestedConcurrency = options?.concurrency;\n if (requestedConcurrency !== undefined && (!Number.isInteger(requestedConcurrency) || requestedConcurrency < 1)) {\n throw new Error(`step \"${name}\" concurrency must be an integer >= 1 (got ${requestedConcurrency})`);\n }\n const timeoutMs = options?.timeoutMs;\n if (timeoutMs !== undefined && (!Number.isFinite(timeoutMs) || timeoutMs < 0)) {\n throw new Error(`step \"${name}\" timeoutMs must be a number >= 0 (got ${timeoutMs})`);\n }\n\n const count = requestedConcurrency ?? 1;\n const invocations = Array.from({ length: count }, () => {\n const promise = Promise.resolve().then(() => fn());\n if (timeoutMs === undefined) return promise;\n return withTimeout(promise, timeoutMs, name);\n });\n\n if (count === 1) {\n return invocations[0];\n }\n\n const outcomes = await Promise.allSettled(invocations);\n const results: R[] = [];\n let firstError: unknown;\n for (const outcome of outcomes) {\n if (outcome.status === 'fulfilled') {\n results.push(outcome.value);\n } else if (firstError === undefined) {\n firstError = outcome.reason;\n }\n }\n if (firstError !== undefined) throw firstError;\n return results;\n}\n\nasync function runStepWithClient<R, C extends number = 1>(\n clientStep: RunWorkerContext['step'],\n name: string,\n fn: () => Promise<R> | R,\n options?: TaskStepOptions & { concurrency?: C },\n): Promise<C extends 1 ? R : R[]> {\n const { concurrency: runnerConcurrency, timeoutMs, ...clientOptions } = options ?? {};\n const clientStepOptions: DefineStepOptions = {\n ...clientOptions,\n timeoutMs,\n stepConcurrency: runnerConcurrency,\n };\n const result = await clientStep(name, () => runStepInvocations(name, fn, options), clientStepOptions);\n return result as C extends 1 ? R : R[];\n}\n\n/**\n * Parses the orchestration flags this runner understands, ignoring anything\n * else. Supports both `--flag value` and `--flag=value`; `--provider` accepts\n * a comma-separated list and may be repeated.\n */\nexport function parseCliArgs(argv: string[]): CliArgs {\n const args: CliArgs = {};\n\n const readValue = (raw: string, i: number): { value: string; nextIndex: number } => {\n const eq = raw.indexOf('=');\n if (eq !== -1) return { value: raw.slice(eq + 1), nextIndex: i };\n return { value: argv[i + 1] ?? '', nextIndex: i + 1 };\n };\n\n const intFlag = (raw: string, flag: string): number => {\n if (raw.trim() === '') throw new Error(`${flag} expects a value`);\n const n = Number(raw);\n if (!Number.isInteger(n) || n < 1) throw new Error(`${flag} expects an integer >= 1 (got \"${raw}\")`);\n return n;\n };\n\n const nonNegFlag = (raw: string, flag: string): number => {\n if (raw.trim() === '') throw new Error(`${flag} expects a value`);\n const n = Number(raw);\n if (!Number.isFinite(n) || n < 0) throw new Error(`${flag} expects a number >= 0 (got \"${raw}\")`);\n return n;\n };\n\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n const name = arg.includes('=') ? arg.slice(0, arg.indexOf('=')) : arg;\n switch (name) {\n // `--slug` is the pre-`--benchmark` spelling, kept working for existing scripts.\n case '--slug':\n case '--benchmark': {\n const { value, nextIndex } = readValue(arg, i);\n if (!/^[a-z0-9][a-z0-9-]*$/.test(value)) {\n throw new Error(`${name} expects a lowercase benchmark slug (got \"${value}\")`);\n }\n args.benchmark = value;\n i = nextIndex;\n break;\n }\n case '--name': {\n const { value, nextIndex } = readValue(arg, i);\n if (value.trim() === '') throw new Error('--name expects a value');\n args.name = value;\n i = nextIndex;\n break;\n }\n case '--shape': {\n const { value, nextIndex } = readValue(arg, i);\n if (value.trim() === '') throw new Error('--shape expects a value');\n args.shape = value;\n i = nextIndex;\n break;\n }\n case '--run-key': {\n const { value, nextIndex } = readValue(arg, i);\n if (value.trim() === '') throw new Error('--run-key expects a value');\n args.runKey = value;\n i = nextIndex;\n break;\n }\n case '--iterations': {\n const { value, nextIndex } = readValue(arg, i);\n args.iterations = intFlag(value, '--iterations');\n i = nextIndex;\n break;\n }\n case '--concurrency': {\n const { value, nextIndex } = readValue(arg, i);\n args.concurrency = intFlag(value, '--concurrency');\n i = nextIndex;\n break;\n }\n case '--stagger-delay-ms': {\n const { value, nextIndex } = readValue(arg, i);\n args.staggerDelayMs = nonNegFlag(value, '--stagger-delay-ms');\n i = nextIndex;\n break;\n }\n case '--group-by': {\n const { value, nextIndex } = readValue(arg, i);\n if (value !== 'participant' && value !== 'round') {\n throw new Error(`--group-by expects 'participant' or 'round' (got \"${value}\")`);\n }\n args.groupBy = value;\n i = nextIndex;\n break;\n }\n case '--provider': {\n const { value, nextIndex } = readValue(arg, i);\n const names = value.split(',').map((s) => s.trim()).filter(Boolean);\n args.providers = [...(args.providers ?? []), ...names];\n i = nextIndex;\n break;\n }\n case '--no-ingest':\n case '--dry-run':\n args.noIngest = true;\n break;\n default:\n break;\n }\n }\n\n if (!args.noIngest && isEnvNoIngest()) {\n args.noIngest = true;\n }\n return args;\n}\n\n/** Merges CLI overrides over config defaults, filling in knob fallbacks. */\nexport function mergeConfig<T extends BaseParticipant>(\n config: BenchmarkConfig<T>,\n args: CliArgs,\n): ResolvedRunConfig {\n const phaseTotal = config.phases?.reduce((sum, p) => sum + p.iterations, 0);\n if (phaseTotal !== undefined && args.iterations !== undefined) {\n console.warn('--iterations is ignored because this benchmark declares phases.');\n }\n const resolved: ResolvedRunConfig = {\n iterations: phaseTotal ?? args.iterations ?? config.iterations ?? 1,\n concurrency: args.concurrency ?? config.concurrency ?? 1,\n staggerDelayMs: args.staggerDelayMs ?? config.staggerDelayMs ?? 0,\n groupBy: args.groupBy ?? config.groupBy ?? 'participant',\n providers: args.providers ?? config.defaultProviders,\n };\n if (!Number.isInteger(resolved.iterations) || resolved.iterations < 1) {\n throw new Error(`iterations must be an integer >= 1 (got ${resolved.iterations})`);\n }\n if (!Number.isInteger(resolved.concurrency) || resolved.concurrency < 1) {\n throw new Error(`concurrency must be an integer >= 1 (got ${resolved.concurrency})`);\n }\n return resolved;\n}\n\n/** One scheduled task slot: which task to run and (optionally) under which phase. */\ninterface Slot<T extends BaseParticipant = BaseParticipant> {\n phase?: string;\n task: BenchmarkTask<T>;\n}\n\n/**\n * Flattens a config into an ordered list of task slots. With `phases`, each\n * phase contributes `iterations` slots tagged with its name (framework owns\n * the phase boundary — no index arithmetic in the task). Without phases, the\n * task is repeated `iterations` times.\n */\nfunction buildSchedule<T extends BaseParticipant>(\n config: BenchmarkConfig<T>,\n iterations: number,\n task: BenchmarkTask<T>,\n): Slot<T>[] {\n if (config.phases?.length) {\n return config.phases.flatMap((phase) =>\n Array.from({ length: phase.iterations }, () => ({ phase: phase.name, task })),\n );\n }\n return Array.from({ length: iterations }, () => ({ phase: undefined, task }));\n}\n\ntype OnResult = (record: TaskResultRecord, meta: { iterations: number; participant: string }) => void;\n\nfunction defaultOnResult(record: TaskResultRecord, meta: { iterations: number; participant: string }): void {\n const n = record.taskIndex + 1;\n if (record.status === 'success') {\n const data = record.data && Object.keys(record.data).length > 0 ? ` ${JSON.stringify(record.data)}` : '';\n console.log(` [${meta.participant}] Task ${n}/${meta.iterations}: success${data}`);\n } else {\n console.log(` [${meta.participant}] Task ${n}/${meta.iterations}: FAILED — ${record.errorCode ?? 'unknown error'}`);\n }\n}\n\nfunction resolvePlatform(): { baseUrl: string; apiKey: string } {\n const root = (process.env.BENCHMARKS_PLATFORM_URL || DEFAULT_PLATFORM_URL).replace(/\\/+$/, '');\n const apiKey = process.env.BENCHMARKS_PLATFORM_API_KEY;\n if (!apiKey) {\n throw new Error(\n 'BENCHMARKS_PLATFORM_API_KEY is required. Create an org-scoped API key ' +\n 'in your organization settings on the platform and set it in your .env.'\n );\n }\n return {\n baseUrl: `${root}/api/v1`,\n apiKey,\n };\n}\n\n/**\n * Resolves `--shape <name>` against the config's declared `shapes`. Throws with\n * the known names if the shape is unknown, so a typo fails loudly instead of\n * silently running the base benchmark.\n */\nfunction resolveShape<T extends BaseParticipant>(\n config: BenchmarkConfig<T>,\n shapeName: string | undefined,\n): BenchmarkShape | undefined {\n if (!shapeName) return undefined;\n const shape = config.shapes?.[shapeName];\n if (!shape) {\n const known = Object.keys(config.shapes ?? {});\n throw new Error(\n known.length > 0\n ? `Unknown --shape \"${shapeName}\". Known shapes: ${known.join(', ')}.`\n : `Unknown --shape \"${shapeName}\": this benchmark declares no shapes.`,\n );\n }\n return shape;\n}\n\n/**\n * Swaps a shape's identity (and its stable knob) into the config. Only the\n * parts that make it a distinct benchmark move here; scale knobs stay on the\n * CLI, so `mergeConfig` still lets `--concurrency`/`--iterations` win.\n */\nfunction applyShape<T extends BaseParticipant>(\n config: BenchmarkConfig<T>,\n shape: BenchmarkShape | undefined,\n): BenchmarkConfig<T> {\n if (!shape) return config;\n return {\n ...config,\n benchmarkSlug: shape.slug,\n benchmarkName: shape.name ?? shape.slug,\n ...(shape.staggerDelayMs !== undefined ? { staggerDelayMs: shape.staggerDelayMs } : {}),\n };\n}\n\n/** Applies the `--benchmark`/`--name` overrides, so one entrypoint can report under several benchmarks. */\nfunction applyIdentityOverrides<T extends BaseParticipant>(\n fileConfig: BenchmarkConfig<T>,\n args: CliArgs,\n): BenchmarkConfig<T> {\n return {\n ...fileConfig,\n ...(args.benchmark ? { benchmarkSlug: args.benchmark } : {}),\n ...(args.name ? { benchmarkName: args.name } : {}),\n };\n}\n\nfunction dashboardUrlFor(baseUrl: string, organizationSlug: string, benchmarkSlug: string, runId: string): string {\n return `${baseUrl.replace(/\\/api\\/v1\\/?$/, '')}/${organizationSlug}/benchmarks/${benchmarkSlug}/runs/${runId}`;\n}\n\n/** The participants a run covers: `--provider` selection, minus any whose env vars are unset. */\nfunction resolveParticipants<T extends BaseParticipant>(config: BenchmarkConfig<T>, resolved: ResolvedRunConfig): T[] {\n const { available, skipped } = filterParticipantsByEnv(selectParticipants(config.participants, resolved.providers));\n for (const s of skipped) {\n console.log(`Skipping ${s.name}: missing ${s.missing.join(', ')}`);\n }\n if (available.length === 0) throw new NoAvailableParticipantsError(skipped);\n return available;\n}\n\n/**\n * Runs `config`'s `task` against its participants. Selects participants by\n * `--provider` (if given), env-gates them, then drives them per the resolved\n * `groupBy`. `--shape` swaps in a declared variant's identity; `--benchmark`/\n * `--name` retarget the run at a different platform benchmark, so one entrypoint\n * can report under several slugs. With `--run-key`, sibling processes (e.g. one\n * CI job per provider) get-or-create one shared run and each registers only its\n * own participants.\n */\nexport async function runBenchmark<T extends BaseParticipant>(\n fileConfig: BenchmarkConfig<T>,\n task: BenchmarkTask<T>,\n argv: string[] = [],\n): Promise<BenchmarkRunOutcome> {\n const args = parseCliArgs(argv);\n const noIngest = args.noIngest ?? isEnvNoIngest();\n const shaped = applyShape(fileConfig, resolveShape(fileConfig, args.shape));\n const config = applyIdentityOverrides(shaped, args);\n const resolved = mergeConfig(config, args);\n const available = resolveParticipants(config, resolved);\n\n let baseUrl = '';\n let apiKey = '';\n let client: BenchmarkClient | null = null;\n if (!noIngest) {\n ({ baseUrl, apiKey } = resolvePlatform());\n client = createBenchmarkClient({ baseUrl, apiKey });\n }\n\n const schedule = buildSchedule(config, resolved.iterations, task);\n const totalTasks = schedule.length;\n\n const concurrencyLabel = resolved.groupBy === 'round' ? 'n/a (round mode)' : String(resolved.concurrency);\n console.log(`${config.benchmarkName} (self-contained)`);\n console.log(`Date: ${new Date().toISOString()}`);\n if (noIngest) {\n console.log('Dry run: no platform ingest or reporting.\\n');\n }\n console.log(\n `Knobs: iterations=${totalTasks}, concurrency=${concurrencyLabel}, ` +\n `staggerDelayMs=${resolved.staggerDelayMs}, groupBy=${resolved.groupBy}\\n`,\n );\n\n // Declaratively materialize the benchmark from the file/shape identity, which\n // is authoritative (its name lives in the file). A bare `--benchmark X` only\n // *retargets* reporting at a benchmark this file doesn't name, so we don't\n // upsert it — that would rename it to the file's own name.\n const identityIsOurs =\n args.shape !== undefined ||\n args.name !== undefined ||\n !args.benchmark ||\n args.benchmark === fileConfig.benchmarkSlug;\n\n let runId: string;\n let dashboardUrl: string;\n if (noIngest) {\n runId = 'no-ingest';\n dashboardUrl = '';\n } else {\n if (identityIsOurs) {\n await client!.upsertBenchmark(config.benchmarkSlug, {\n name: config.benchmarkName,\n });\n }\n\n if (args.runKey) {\n // Shared run: get-or-created by key, so sibling processes (one per provider)\n // converge on one run. Opened participant-sized — register only the\n // providers this process runs and let each sibling register its own, so the\n // run lists exactly who's benchmarked and each brings its own task count.\n const { run, organizationSlug } = await client!.createRun(config.benchmarkSlug, {\n runKey: args.runKey,\n });\n runId = run.id;\n dashboardUrl = dashboardUrlFor(baseUrl, organizationSlug, config.benchmarkSlug, run.id);\n for (const participant of available) {\n await client!.upsertParticipant(config.benchmarkSlug, runId, participant.name, { totalTasks });\n }\n console.log(`Shared run (key \"${args.runKey}\"): ${run.name} (${runId})`);\n console.log(`View at: ${dashboardUrl}\\n`);\n } else {\n const { run, organizationSlug } = await client!.createRun(config.benchmarkSlug, {\n totalTasks,\n workerCount: 1,\n participants: available.map((p) => p.name),\n });\n runId = run.id;\n dashboardUrl = dashboardUrlFor(baseUrl, organizationSlug, config.benchmarkSlug, run.id);\n console.log(`Run created: ${run.name} (${runId})`);\n console.log(`View at: ${dashboardUrl}\\n`);\n }\n }\n\n const onResult = defaultOnResult;\n\n let participantRecords: ParticipantRecords[];\n if (resolved.groupBy === 'round') {\n participantRecords = await runGroupedByRound(config, schedule, available, resolved, client, runId, baseUrl, apiKey, onResult, noIngest);\n } else {\n participantRecords = await runGroupedByParticipant(config, schedule, available, resolved, client, runId, onResult);\n }\n\n console.log(`All done. ${noIngest ? 'No platform run created.' : `View at: ${dashboardUrl}`}`);\n const outcome: BenchmarkRunOutcome = {\n runId,\n dashboardUrl,\n participants: participantRecords,\n config: resolved,\n };\n if (client && config.onScore) {\n try {\n const spec = await config.onScore(lowerIsBetter, higherIsBetter);\n const scored = score(outcome, spec);\n const run = {\n gitSha: process.env.GITHUB_SHA ?? getGitSha(),\n gitRef: process.env.GITHUB_REF_NAME ?? process.env.GITHUB_REF ?? getGitRef(),\n triggeredBy: process.env.GITHUB_EVENT_NAME ?? 'manual',\n nodeVersion: process.version,\n platform: os.platform(),\n arch: os.arch(),\n };\n await client.submitRunSummary(config.benchmarkSlug, runId, { run, results: scored });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.warn(`[benchsdk-runner] failed to submit run summary: ${message}`);\n }\n }\n if (config.onComplete) await config.onComplete(outcome);\n return outcome;\n}\n\nfunction getGitSha(): string | undefined {\n if (process.env.GITHUB_SHA) return process.env.GITHUB_SHA;\n try {\n return execSync('git rev-parse HEAD', { encoding: 'utf8', stdio: 'pipe' }).trim();\n } catch {\n return undefined;\n }\n}\n\nfunction getGitRef(): string | undefined {\n if (process.env.GITHUB_REF_NAME) return process.env.GITHUB_REF_NAME;\n if (process.env.GITHUB_REF) return process.env.GITHUB_REF;\n try {\n return execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf8', stdio: 'pipe' }).trim();\n } catch {\n return undefined;\n }\n}\n\n/**\n * 'participant' ordering: one `client.runWorker` per participant, in turn.\n * `staggerDelayMs` here launches task N at `workerStart + N * staggerDelayMs`\n * (vs. round mode's fixed delay between rounds — intentionally different).\n * `TaskResult.steps`/`latencyMs` are ignored in this path: the platform\n * worker owns step timing and latency.\n */\nasync function runGroupedByParticipant<T extends BaseParticipant>(\n config: BenchmarkConfig<T>,\n schedule: Slot<T>[],\n available: T[],\n resolved: ResolvedRunConfig,\n client: BenchmarkClient | null,\n runId: string,\n onResult: OnResult,\n): Promise<ParticipantRecords[]> {\n const participantRecords: ParticipantRecords[] = [];\n for (const participant of available) {\n console.log(`${'='.repeat(70)}`);\n console.log(` Participant: ${participant.name}`);\n console.log('='.repeat(70));\n\n // When running without platform ingest, execute the schedule locally.\n if (!client) {\n const records: TaskResultRecord[] = [];\n let rampStartMs: number | undefined;\n let nextIndex = 0;\n const logBuffer = new LogBuffer();\n\n const runSlot = async (scheduleIndex: number) => {\n if (resolved.staggerDelayMs > 0) {\n rampStartMs ??= Date.now();\n const waitMs = rampStartMs + scheduleIndex * resolved.staggerDelayMs - Date.now();\n if (waitMs > 0) await sleep(waitMs);\n }\n const slot = schedule[scheduleIndex];\n const record = await runTaskRecord(slot.task, participant, scheduleIndex, scheduleIndex, slot.phase, logBuffer);\n onResult(record, { iterations: schedule.length, participant: participant.name });\n records.push(record);\n };\n\n const worker = async () => {\n while (nextIndex < schedule.length) {\n const index = nextIndex++;\n await runSlot(index);\n }\n };\n\n await Promise.all(Array.from({ length: resolved.concurrency }, () => worker()));\n records.sort((a, b) => a.taskIndex - b.taskIndex);\n\n const ok = records.filter((r) => r.status === 'success').length;\n console.log(` Done: ${ok}/${records.length} succeeded.\\n`);\n participantRecords.push({ participant: participant.name, records });\n continue;\n }\n\n // Anchors the ramp to the worker's start, so a pool narrower than the task\n // count can't inflate launch offsets: a task whose slot frees after its\n // scheduled launch time starts immediately instead of sleeping index*delay.\n let rampStartMs: number | undefined;\n await client.planWorkers(config.benchmarkSlug, runId, participant.name);\n\n const result = await client.runWorker({\n benchmarkSlug: config.benchmarkSlug,\n runId: runId,\n participantSlug: participant.name,\n concurrency: resolved.concurrency,\n task: async (ctx: RunWorkerContext) => {\n // `ctx.taskIndex` is the platform's global index; the schedule is\n // indexed from the worker's own task range start.\n const scheduleIndex = ctx.taskIndex - ctx.assignment.taskRange.start;\n if (resolved.staggerDelayMs > 0) {\n rampStartMs ??= Date.now();\n const waitMs = rampStartMs + scheduleIndex * resolved.staggerDelayMs - Date.now();\n if (waitMs > 0) await sleep(waitMs);\n }\n const slot = schedule[scheduleIndex];\n // The client owns step timing, `measure` attribution, and worker-log\n // upload; the runner just threads them onto the task context. The runner\n // wraps `ctx.step` so per-step `timeoutMs` and `concurrency` work in\n // participant mode as well.\n const taskResult = await slot.task({\n participant,\n taskIndex: scheduleIndex,\n phase: slot.phase,\n step: (name, fn, options) => runStepWithClient(ctx.step, name, fn, options),\n measure: ctx.measure,\n log: ctx.log,\n });\n if (slot.phase) ctx.measure({ phase: slot.phase });\n return taskResult?.data;\n },\n onResult: (record) => onResult(record, { iterations: schedule.length, participant: participant.name }),\n });\n\n if (!result.assignment) {\n console.error(` No pending worker to claim for run ${runId} — it may already be fully claimed.`);\n participantRecords.push({ participant: participant.name, records: result.records ?? [] });\n continue;\n }\n\n const ok = result.records.filter((r) => r.status === 'success').length;\n console.log(` Done: ${ok}/${result.records.length} succeeded.\\n`);\n participantRecords.push({ participant: participant.name, records: result.records });\n }\n\n return participantRecords;\n}\n\n/**\n * 'round' ordering: claim one `BenchmarkReporter` per participant up front,\n * then loop rounds, running one task per participant per round and streaming\n * each result to its reporter. Steps are built manually (no `client.runWorker`\n * to own them) via a shim that mirrors the platform's record shape.\n */\nasync function runGroupedByRound<T extends BaseParticipant>(\n config: BenchmarkConfig<T>,\n schedule: Slot<T>[],\n available: T[],\n resolved: ResolvedRunConfig,\n client: BenchmarkClient | null,\n runId: string,\n baseUrl: string,\n apiKey: string,\n onResult: OnResult,\n noIngest: boolean = false,\n): Promise<ParticipantRecords[]> {\n const reporters = new Map<string, BenchmarkReporter | null>();\n const logBuffers = new Map<string, LogBuffer>();\n const failed = new Map<string, boolean>();\n const recordsByParticipant = new Map<string, TaskResultRecord[]>();\n\n for (const participant of available) {\n logBuffers.set(participant.name, new LogBuffer());\n failed.set(participant.name, false);\n if (noIngest || !client) {\n reporters.set(participant.name, null);\n continue;\n }\n // One worker per participant drives every round sequentially. The platform\n // reads `targetConcurrency` as tasks-per-worker, so it must be the full\n // schedule length — otherwise only one task is planned and every record\n // past the first falls outside the worker's task range.\n await client.planWorkers(config.benchmarkSlug, runId, participant.name, {\n workerCount: 1,\n targetConcurrency: schedule.length,\n });\n let reporter: BenchmarkReporter | null = null;\n try {\n reporter = await BenchmarkReporter.claim({\n baseUrl,\n apiKey,\n benchmarkSlug: config.benchmarkSlug,\n runId: runId,\n participantSlug: participant.name,\n processKind: 'process',\n processKey: process.env.HOSTNAME ?? 'local',\n });\n } catch (error) {\n console.warn(` ${participant.name}: reporter claim failed (${error instanceof Error ? error.message : String(error)}) — running without platform reporting.`);\n }\n if (!reporter) {\n console.warn(` ${participant.name}: could not claim a platform worker — running without platform reporting.`);\n }\n reporters.set(participant.name, reporter);\n }\n\n console.log(`Interleaving ${available.length} participant(s), ${schedule.length} round(s) each.\\n`);\n\n for (let i = 0; i < schedule.length; i++) {\n const slot = schedule[i];\n // Round mode staggers a fixed delay between rounds (vs. participant mode's per-task stagger).\n if (resolved.staggerDelayMs > 0 && i > 0) {\n await sleep(resolved.staggerDelayMs);\n }\n for (const participant of available) {\n const reporter = reporters.get(participant.name) ?? null;\n const logBuffer = logBuffers.get(participant.name)!;\n const record = await runTaskRecord(\n slot.task,\n participant,\n i,\n (reporter?.taskIndexStart ?? 0) + i,\n slot.phase,\n logBuffer,\n );\n if (record.status !== 'success') failed.set(participant.name, true);\n onResult(record, { iterations: schedule.length, participant: participant.name });\n reporter?.recordResult(record);\n if (!recordsByParticipant.has(participant.name)) {\n recordsByParticipant.set(participant.name, []);\n }\n const participantRecords = recordsByParticipant.get(participant.name)!;\n participantRecords.push(record);\n // Round mode drives the worker by hand, so nothing reports progress\n // unless we do: without this the platform shows 0 done for the whole run.\n if (reporter) {\n reporter.setProgress({\n done: participantRecords.length,\n inFlight: 0,\n errors: participantRecords.filter((item) => item.status !== 'success').length,\n total: schedule.length,\n });\n await reporter.heartbeat();\n }\n }\n }\n\n for (const participant of available) {\n const reporter = reporters.get(participant.name) ?? null;\n const logBuffer = logBuffers.get(participant.name)!;\n if (reporter && !logBuffer.isEmpty()) {\n await reporter\n .uploadArtifact({ kind: 'coordinator.log', contentType: 'text/plain', name: 'worker.log', body: logBuffer.toText() })\n .catch(() => {});\n }\n await reporter?.finish(failed.get(participant.name) ?? false);\n console.log(` ${participant.name}: done${failed.get(participant.name) ? ' (with errors)' : ''}.`);\n }\n\n return available.map((p) => ({ participant: p.name, records: recordsByParticipant.get(p.name) ?? [] }));\n}\n\n/** Merges a task's data payload with the current phase tag (if any). */\nfunction mergeData(data: JsonObject | undefined, phase: string | undefined): JsonObject | undefined {\n const merged = { ...(data ?? {}), ...(phase ? { phase } : {}) };\n return Object.keys(merged).length > 0 ? merged : undefined;\n}\n\n/**\n * Runs one task for the manual 'round' path, building its `TaskResultRecord`.\n * Honors the full `TaskResult` (task-owned data/steps/latency) and `TaskError`\n * (preserves domain data + steps on failure). Framework-timed `ctx.step` calls\n * and task-owned `result.steps` are both recorded.\n */\nasync function runTaskRecord<T extends BaseParticipant>(\n task: BenchmarkTask<T>,\n participant: T,\n scheduleIndex: number,\n taskIndex: number,\n phase: string | undefined,\n logBuffer: LogBuffer,\n): Promise<TaskResultRecord> {\n const startedAtMs = Date.now();\n const record: TaskResultRecord = {\n taskIndex,\n status: 'success',\n startedAt: new Date(startedAtMs).toISOString(),\n };\n const frameworkSteps: TaskStepRecord[] = [];\n const taskMeasures: JsonObject = {};\n // Mirrors the client worker: `measure` lands on the active step (if any),\n // else on task-level measurements folded into `record.data`.\n let activeStep: TaskStepRecord | null = null;\n\n const ctx: TaskContext<T> = {\n participant,\n taskIndex: scheduleIndex,\n phase,\n async step<R, C extends number = 1>(\n name: string,\n fn: () => Promise<R> | R,\n options?: TaskStepOptions & { concurrency?: C },\n ): Promise<C extends 1 ? R : R[]> {\n const stepStartedAtMs = Date.now();\n const stepRecord: TaskStepRecord = {\n name,\n status: 'success',\n startedAt: new Date(stepStartedAtMs).toISOString(),\n completedAt: new Date(stepStartedAtMs).toISOString(),\n latencyMs: 0,\n };\n if (options?.concurrency !== undefined) stepRecord.concurrency = options.concurrency;\n if (options?.timeoutMs !== undefined) stepRecord.timeoutMs = options.timeoutMs;\n const previousStep = activeStep;\n activeStep = stepRecord;\n try {\n const result = await runStepInvocations<R>(name, fn, options);\n logBuffer.step(taskIndex, name, {});\n return result as C extends 1 ? R : R[];\n } catch (error) {\n stepRecord.status = 'error';\n stepRecord.errorCode = error instanceof TaskError ? error.code ?? error.name : getErrorCode(error);\n logBuffer.step(taskIndex, name, { error: error instanceof Error ? error.message : String(error) });\n throw error;\n } finally {\n activeStep = previousStep;\n stepRecord.completedAt = new Date().toISOString();\n stepRecord.latencyMs = Date.now() - stepStartedAtMs;\n frameworkSteps.push(stepRecord);\n }\n },\n measure(data) {\n if (activeStep) {\n activeStep.data = { ...(activeStep.data ?? {}), ...data };\n } else {\n Object.assign(taskMeasures, data);\n }\n },\n log(message, meta) {\n logBuffer.line(`[task ${taskIndex}] ${message}`, meta);\n },\n };\n\n let result: TaskResult | void = undefined;\n try {\n result = await task(ctx);\n record.data = mergeData({ ...taskMeasures, ...(result?.data ?? {}) }, phase);\n } catch (error) {\n record.status = 'error';\n if (error instanceof TaskError) {\n record.errorCode = error.code ?? error.name;\n record.data = mergeData({ ...taskMeasures, ...(error.data ?? {}) }, phase);\n if (error.steps?.length) frameworkSteps.push(...error.steps);\n } else {\n record.errorCode = getErrorCode(error);\n record.data = mergeData(\n { ...taskMeasures, errorMessage: error instanceof Error ? error.message : String(error) },\n phase,\n );\n }\n } finally {\n const endMs = Date.now();\n record.completedAt = new Date(endMs).toISOString();\n record.latencyMs =\n record.status === 'success' && result && typeof result.latencyMs === 'number'\n ? result.latencyMs\n : endMs - startedAtMs;\n const taskSteps = record.status === 'success' && result?.steps ? result.steps : [];\n const allSteps = [...frameworkSteps, ...taskSteps];\n // A task that declared no steps is recorded as a single implicit 'task'\n // step, matching the client worker's behavior.\n if (allSteps.length === 0) {\n allSteps.push({\n name: 'task',\n status: record.status === 'success' ? 'success' : 'error',\n startedAt: record.startedAt,\n completedAt: record.completedAt,\n latencyMs: record.latencyMs,\n errorCode: record.errorCode ?? null,\n data: Object.keys(taskMeasures).length > 0 ? { ...taskMeasures } : undefined,\n });\n }\n record.steps = allSteps;\n }\n\n return record;\n}\n","import type { JsonObject, TaskResultRecord } from '@benchsdk/client';\nimport type { BenchmarkRunOutcome } from './bench-config.js';\n\nexport type MetricValue = string | ((record: TaskResultRecord) => number | number[] | undefined);\n\nexport interface MetricScoring {\n name: string;\n value?: MetricValue;\n unit: string;\n ceiling: number;\n floor?: number;\n higherIsBetter?: boolean;\n weights: { median: number; p95: number; p99: number };\n trim?: number;\n}\n\nexport interface ScoringSpec {\n dimensions?: Record<string, unknown>;\n success?: (record: TaskResultRecord) => boolean;\n metrics: MetricScoring[];\n}\n\nexport interface BenchmarkScoreResult {\n provider: string;\n dimensions: JsonObject;\n metrics: { name: string; unit: string; median: number; p95: number; p99: number }[];\n scalars?: { name: string; value: number; unit: string }[];\n compositeScore: number;\n successRate: number;\n scoringVersion?: string;\n skipped: boolean;\n skipReason?: string;\n}\n\nexport type LowerIsBetter = (\n name: string,\n opts: {\n unit: string;\n ceiling: number;\n value?: MetricValue;\n weights: { median: number; p95: number; p99: number };\n trim?: number;\n },\n) => MetricScoring;\n\nexport type HigherIsBetter = (\n name: string,\n opts: {\n unit: string;\n floor?: number;\n ceiling: number;\n value?: MetricValue;\n weights: { median: number; p95: number; p99: number };\n trim?: number;\n },\n) => MetricScoring;\n\nfunction isFiniteNumber(v: unknown): v is number {\n return typeof v === 'number' && Number.isFinite(v);\n}\n\nfunction percentile(sorted: number[], p: number): number {\n if (sorted.length === 0) return 0;\n const idx = Math.max(0, Math.ceil((p / 100) * sorted.length) - 1);\n return sorted[Math.min(idx, sorted.length - 1)];\n}\n\nfunction computeStats(values: number[], trimPercent: number = 0.05): { median: number; p95: number; p99: number } {\n if (values.length === 0) return { median: 0, p95: 0, p99: 0 };\n\n const sorted = [...values].sort((a, b) => a - b);\n\n const trimCount = Math.floor(sorted.length * trimPercent);\n const trimmed = trimCount > 0 && sorted.length - 2 * trimCount > 0\n ? sorted.slice(trimCount, sorted.length - trimCount)\n : sorted;\n\n const mid = Math.floor(trimmed.length / 2);\n const median = trimmed.length % 2 === 0\n ? (trimmed[mid - 1] + trimmed[mid]) / 2\n : trimmed[mid];\n\n return { median, p95: percentile(trimmed, 95), p99: percentile(trimmed, 99) };\n}\n\nfunction toJsonObject(value: Record<string, unknown>): JsonObject {\n return JSON.parse(JSON.stringify(value ?? {})) as JsonObject;\n}\n\nfunction collectSamples(metric: MetricScoring, records: TaskResultRecord[]): number[] {\n const samples: number[] = [];\n for (const record of records) {\n const raw = typeof metric.value === 'function'\n ? metric.value(record)\n : record.data?.[metric.value ?? metric.name];\n\n if (Array.isArray(raw)) {\n for (const item of raw) {\n if (isFiniteNumber(item)) samples.push(item);\n }\n } else if (isFiniteNumber(raw)) {\n samples.push(raw);\n }\n }\n return samples;\n}\n\nfunction scoreStat(stat: number, metric: MetricScoring): number {\n if (metric.higherIsBetter) {\n const floor = metric.floor ?? 0;\n if (stat <= floor) return 0;\n if (stat >= metric.ceiling) return 100;\n return ((stat - floor) / (metric.ceiling - floor)) * 100;\n }\n return Math.max(0, 100 * (1 - stat / metric.ceiling));\n}\n\nexport const lowerIsBetter: LowerIsBetter = (name, opts) => ({\n name,\n ...opts,\n higherIsBetter: false,\n});\n\nexport const higherIsBetter: HigherIsBetter = (name, opts) => ({\n name,\n ...opts,\n higherIsBetter: true,\n});\n\nexport function score(outcome: BenchmarkRunOutcome, spec: ScoringSpec): BenchmarkScoreResult[] {\n const successFilter = spec.success ?? ((r: TaskResultRecord) => r.status === 'success');\n const dimensions = toJsonObject(spec.dimensions ?? {});\n const results: BenchmarkScoreResult[] = [];\n\n for (const { participant, records } of outcome.participants) {\n const passing = records.filter(successFilter);\n const successRate = records.length === 0 ? 0 : passing.length / records.length;\n const skipped = records.length === 0;\n\n let metricScoresSum = 0;\n const metrics: BenchmarkScoreResult['metrics'] = [];\n\n for (const metric of spec.metrics) {\n const samples = collectSamples(metric, passing);\n // A metric with no data points should not contribute to the composite\n // score; otherwise an empty lower-is-better metric would be scored as 100.\n if (samples.length === 0) {\n continue;\n }\n const { median, p95, p99 } = computeStats(samples, metric.trim ?? 0.05);\n const metricScore =\n metric.weights.median * scoreStat(median, metric) +\n metric.weights.p95 * scoreStat(p95, metric) +\n metric.weights.p99 * scoreStat(p99, metric);\n metricScoresSum += metricScore;\n\n metrics.push({ name: metric.name, unit: metric.unit, median, p95, p99 });\n }\n\n const compositeScore = successRate === 0 ? 0 : Math.round(metricScoresSum * successRate * 100) / 100;\n\n results.push({\n provider: participant,\n dimensions,\n metrics,\n compositeScore,\n successRate,\n skipped,\n });\n }\n\n return results;\n}\n","/**\n * Accumulates one text log per worker across a task's steps, uploaded once as a\n * `coordinator.log` artifact. Used by the runner's manual `round` mode, where\n * `client.runWorker` (which owns log upload in `participant` mode) is not in\n * play.\n */\nexport interface StepOutcome {\n stdout?: string;\n stderr?: string;\n error?: string;\n}\n\nexport class LogBuffer {\n private readonly lines: string[] = [];\n\n step(taskIndex: number, stepName: string, outcome: StepOutcome): void {\n const header = `[task ${taskIndex}] ${stepName}`;\n this.lines.push(`${new Date().toISOString()} ${header}`);\n if (outcome.stdout?.trim()) {\n this.lines.push(indent(outcome.stdout));\n }\n if (outcome.stderr?.trim()) {\n this.lines.push(indent(outcome.stderr, 'stderr: '));\n }\n if (outcome.error) {\n this.lines.push(indent(outcome.error, 'error: '));\n }\n }\n\n /** Appends a free-form narration line (backs the task context's `log`). */\n line(message: string, meta?: Record<string, unknown>): void {\n const suffix = meta && Object.keys(meta).length > 0 ? ` ${JSON.stringify(meta)}` : '';\n this.lines.push(`${new Date().toISOString()} ${message}${suffix}`);\n }\n\n isEmpty(): boolean {\n return this.lines.length === 0;\n }\n\n toText(): string {\n return this.lines.join('\\n') + '\\n';\n }\n}\n\nfunction indent(text: string, prefix = ''): string {\n return text\n .trimEnd()\n .split('\\n')\n .map((line) => ` ${prefix}${line}`)\n .join('\\n');\n}\n","/**\n * The author-facing entrypoint. `bench` is verbs-only — the benchmark and its\n * runs are implicit, never nouns you type:\n *\n * bench run <file.bench.ts> [--flags] execute a benchmark\n *\n * `run` imports a benchmark module, reads its `config` and `task` exports and\n * drives `runBenchmark`; CLI flags override the config's knobs and\n * `config.onComplete` (if any) fires once the run finishes. The benchmark is\n * declared in the file (`--shape` picks a named variant) and materialized on\n * run; a run is opened as a side effect, shared across sibling processes when\n * they pass the same `--run-key`. There are no imperative `create` commands.\n *\n * The executable wrapper lives in `bin.ts`; this module has no side effects so\n * it can be unit-tested by calling `runBenchmarkFile` directly.\n */\nimport { resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { parseCliArgs, runBenchmark } from './runner.js';\nimport { NoAvailableParticipantsError } from './no-available-participants.js';\nimport type { BaseParticipant } from '@benchsdk/client';\nimport type { BenchmarkConfig, BenchmarkTask } from './bench-config.js';\n\nconst USAGE =\n 'Usage:\\n' +\n ' bench run <file.bench.ts> [--shape name] [--provider a,b] [--run-key key]\\n' +\n ' [--benchmark slug] [--name \"My benchmark\"]\\n' +\n ' [--iterations N] [--concurrency N] [--stagger-delay-ms N] [--group-by participant|round]\\n' +\n ' [--no-ingest | --dry-run]';\n\n/** A benchmark module is expected to export `config` and `task`. */\ninterface BenchmarkModule {\n config?: unknown;\n task?: unknown;\n default?: unknown;\n}\n\nfunction isBenchmarkConfig(value: unknown): value is BenchmarkConfig {\n if (typeof value !== 'object' || value === null) return false;\n const candidate = value as { benchmarkSlug?: unknown; participants?: unknown };\n return typeof candidate.benchmarkSlug === 'string' && Array.isArray(candidate.participants);\n}\n\n/**\n * Dispatches one CLI invocation. Throws on bad usage / invalid exports and lets\n * `NoAvailableParticipantsError` propagate so the caller can map it to a clean\n * exit. Does not call `process.exit`.\n */\nexport async function runBenchmarkFile(argv: string[]): Promise<void> {\n const [command, ...rest] = argv;\n\n const [file, ...flags] = rest;\n if (command !== 'run' || !file || file.startsWith('-')) throw new Error(USAGE);\n\n const mod = (await import(pathToFileURL(resolve(process.cwd(), file)).href)) as BenchmarkModule;\n const config = mod.config;\n const task = mod.task ?? mod.default;\n\n if (!isBenchmarkConfig(config)) {\n throw new Error(`${file} must export a \\`config\\` created with defineBenchmarkConfig (with participants).`);\n }\n if (typeof task !== 'function') {\n throw new Error(`${file} must export a \\`task\\` created with defineTask.`);\n }\n\n await runBenchmark(config as BenchmarkConfig<BaseParticipant>, task as BenchmarkTask<BaseParticipant>, flags);\n}\n\n/** Executable entry: runs the file and maps outcomes to process exit codes. */\nexport async function run(argv: string[]): Promise<void> {\n try {\n await runBenchmarkFile(argv);\n // Provider SDKs can leave sockets/timers open; exit explicitly so a\n // finished run doesn't hang.\n process.exit(0);\n } catch (err) {\n if (err instanceof NoAvailableParticipantsError) {\n console.log(err.message);\n process.exit(0);\n }\n console.error('Benchmark failed:', err instanceof Error ? err.message : err);\n process.exit(1);\n }\n}\n"],"mappings":"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","names":["resolve","run","os","rampStartMs","ok","result"]}
@@ -0,0 +1,366 @@
1
+ import { TaskResultRecord, JsonObject, BaseParticipant, DefineStepOptions, TaskStepRecord } from '@benchsdk/client';
2
+
3
+ type MetricValue = string | ((record: TaskResultRecord) => number | number[] | undefined);
4
+ interface MetricScoring {
5
+ name: string;
6
+ value?: MetricValue;
7
+ unit: string;
8
+ ceiling: number;
9
+ floor?: number;
10
+ higherIsBetter?: boolean;
11
+ weights: {
12
+ median: number;
13
+ p95: number;
14
+ p99: number;
15
+ };
16
+ trim?: number;
17
+ }
18
+ interface ScoringSpec {
19
+ dimensions?: Record<string, unknown>;
20
+ success?: (record: TaskResultRecord) => boolean;
21
+ metrics: MetricScoring[];
22
+ }
23
+ interface BenchmarkScoreResult {
24
+ provider: string;
25
+ dimensions: JsonObject;
26
+ metrics: {
27
+ name: string;
28
+ unit: string;
29
+ median: number;
30
+ p95: number;
31
+ p99: number;
32
+ }[];
33
+ scalars?: {
34
+ name: string;
35
+ value: number;
36
+ unit: string;
37
+ }[];
38
+ compositeScore: number;
39
+ successRate: number;
40
+ scoringVersion?: string;
41
+ skipped: boolean;
42
+ skipReason?: string;
43
+ }
44
+ type LowerIsBetter = (name: string, opts: {
45
+ unit: string;
46
+ ceiling: number;
47
+ value?: MetricValue;
48
+ weights: {
49
+ median: number;
50
+ p95: number;
51
+ p99: number;
52
+ };
53
+ trim?: number;
54
+ }) => MetricScoring;
55
+ type HigherIsBetter = (name: string, opts: {
56
+ unit: string;
57
+ floor?: number;
58
+ ceiling: number;
59
+ value?: MetricValue;
60
+ weights: {
61
+ median: number;
62
+ p95: number;
63
+ p99: number;
64
+ };
65
+ trim?: number;
66
+ }) => MetricScoring;
67
+ declare const lowerIsBetter: LowerIsBetter;
68
+ declare const higherIsBetter: HigherIsBetter;
69
+ declare function score(outcome: BenchmarkRunOutcome, spec: ScoringSpec): BenchmarkScoreResult[];
70
+
71
+ /**
72
+ * A `*.bench.ts` file is the composition of a **config** and a **task**:
73
+ *
74
+ * export const config = defineBenchmarkConfig({ benchmarkSlug, participants, ... });
75
+ * export const task = defineTask(async (ctx) => { await ctx.step('work', () => ...); });
76
+ *
77
+ * `defineBenchmarkConfig` holds the orchestration knobs (including the
78
+ * participants and an optional `onComplete` hook); `defineTask` holds the
79
+ * workload. The `bench run <file>` binary imports the module, reads those two
80
+ * exports, and drives the run. There is no "mode": all orchestration shapes
81
+ * emerge from the knobs.
82
+ *
83
+ * iterations total tasks to run (default 1)
84
+ * concurrency max tasks in flight at once — 1 = sequential, N = burst (default 1)
85
+ * staggerDelayMs delay each task's start by taskIndex * staggerDelayMs (default 0)
86
+ *
87
+ * Common shapes:
88
+ * sequential { iterations: N, concurrency: 1 }
89
+ * burst { iterations: N, concurrency: N }
90
+ * staggered { iterations: N, concurrency: N, staggerDelayMs: 200 }
91
+ *
92
+ * A benchmark can name these variants up front via `shapes`, so one file backs
93
+ * several platform benchmarks (`bench run <file> --shape burst`) without
94
+ * restating each one's slug/name in scripts and CI.
95
+ *
96
+ * A task is comprised of steps, declared via `ctx.step` inside a task function
97
+ * — it supports closures, conditionals and try/finally, so values (a created
98
+ * sandbox, say) flow naturally between steps. A task that declares no steps is
99
+ * recorded as a single implicit `task` step. Measurements reach the platform
100
+ * via `ctx.measure(...)`; step return values are control flow and never
101
+ * recorded.
102
+ */
103
+
104
+ /** How tasks are ordered across participants. */
105
+ type GroupBy = 'participant' | 'round';
106
+ /**
107
+ * A named variant of a benchmark, selected with `--shape <name>`. A shape
108
+ * carries only the parts that make it a distinct *benchmark* — its platform
109
+ * identity plus any stable distinguishing knob (e.g. staggered's delay). The
110
+ * scale knobs that vary per environment (`--iterations`, `--concurrency`) stay
111
+ * on the invocation, so a shape never sets a value only to have the CLI
112
+ * override it.
113
+ */
114
+ interface BenchmarkShape {
115
+ /** Platform slug this shape reports under (e.g. 'sandbox-tti'). */
116
+ slug: string;
117
+ /** Display name shown on the platform; defaults to the slug. */
118
+ name?: string;
119
+ /** Default stagger delay (ms) for this shape; overridable with `--stagger-delay-ms`. */
120
+ staggerDelayMs?: number;
121
+ }
122
+ /**
123
+ * What a task returns: whatever it measured itself. This replaces the
124
+ * assumption that the framework owns all timing. A plain data payload is
125
+ * written explicitly as `{ data: {...} }`.
126
+ */
127
+ interface TaskResult {
128
+ /** Free-form domain payload attached to the record (tokens, receipts, ...). */
129
+ data?: JsonObject;
130
+ /**
131
+ * Pre-measured steps the task timed itself (e.g. socket phases).
132
+ * Only honored in `groupBy: 'round'` runs, where the runner builds records
133
+ * manually. In `groupBy: 'participant'` runs the platform worker
134
+ * (`client.runWorker`) owns steps, so `steps` and `latencyMs` are ignored.
135
+ */
136
+ steps?: TaskStepRecord[];
137
+ /** Task-owned overall latency; overrides framework wall-clock (round mode only). */
138
+ latencyMs?: number;
139
+ }
140
+ /** Options for a single `ctx.step` invocation. */
141
+ interface TaskStepOptions extends Omit<DefineStepOptions, 'concurrency' | 'stepConcurrency'> {
142
+ /** Per-iteration timeout in milliseconds. If an invocation exceeds this, it is aborted and a `step_timeout` TaskError is thrown. */
143
+ timeoutMs?: number;
144
+ /** Number of times to invoke `fn` in parallel. Defaults to 1. When greater than 1, the step returns an array of results. */
145
+ concurrency?: number;
146
+ }
147
+ /**
148
+ * Throw this from a task to record a failure while preserving domain data and
149
+ * any pre-measured steps (a plain thrown Error loses them).
150
+ */
151
+ declare class TaskError extends Error {
152
+ readonly code?: string;
153
+ readonly data?: JsonObject;
154
+ readonly steps?: TaskStepRecord[];
155
+ constructor(message: string, opts?: {
156
+ code?: string;
157
+ data?: JsonObject;
158
+ steps?: TaskStepRecord[];
159
+ });
160
+ }
161
+ /** Context handed to a benchmark `task` for a single iteration. */
162
+ interface TaskContext<T extends BaseParticipant = BaseParticipant> {
163
+ /** The participant this task is running for. */
164
+ participant: T;
165
+ /** Zero-based global task ordinal (matches the platform record's taskIndex). */
166
+ taskIndex: number;
167
+ /** Current phase name, when the benchmark declares `phases`. */
168
+ phase?: string;
169
+ /**
170
+ * Runs `fn` as a named platform step. Mirrors `@benchsdk/client`'s
171
+ * `RunWorkerContext.step`; supports closures and try/finally. A `concurrency`
172
+ * greater than 1 invokes `fn` that many times in parallel and returns an array.
173
+ * `timeoutMs` aborts any invocation that exceeds it with a `step_timeout` TaskError.
174
+ */
175
+ step<R, C extends number = 1>(name: string, fn: () => Promise<R> | R, options?: TaskStepOptions & {
176
+ concurrency?: C;
177
+ }): Promise<C extends 1 ? R : R[]>;
178
+ /**
179
+ * Attaches a JSON measurement to the platform. Inside a `step` it lands on
180
+ * that step's data; at task top-level it lands on the task record's data.
181
+ */
182
+ measure(data: JsonObject): void;
183
+ /** Appends a line to the worker log, uploaded as an artifact when the worker finishes. */
184
+ log(message: string, meta?: JsonObject): void;
185
+ }
186
+ type BenchmarkTask<T extends BaseParticipant = BaseParticipant> = (ctx: TaskContext<T>) => Promise<TaskResult | void> | TaskResult | void;
187
+ /**
188
+ * A named run segment with its own iteration count. Phases run in order; each
189
+ * record is tagged with the phase name via `data.phase`, and `ctx.phase` lets
190
+ * the task branch on identity instead of index arithmetic.
191
+ */
192
+ interface Phase {
193
+ /** Phase name, tagged onto every record produced in this phase. */
194
+ name: string;
195
+ /** Iterations to run in this phase. */
196
+ iterations: number;
197
+ }
198
+ /** One participant's collected task records from a run. */
199
+ interface ParticipantRecords {
200
+ participant: string;
201
+ records: TaskResultRecord[];
202
+ }
203
+ /** The orchestration knobs a run actually used, after CLI overrides. */
204
+ interface ResolvedRunConfig {
205
+ iterations: number;
206
+ concurrency: number;
207
+ staggerDelayMs: number;
208
+ groupBy: GroupBy;
209
+ providers?: string[];
210
+ }
211
+ /**
212
+ * Result of a benchmark run, passed to `config.onComplete`. Exposes the raw
213
+ * per-participant records so completion hooks can write legacy local results.
214
+ */
215
+ interface BenchmarkRunOutcome {
216
+ runId: string;
217
+ /** Link to this run on the platform dashboard. */
218
+ dashboardUrl: string;
219
+ participants: ParticipantRecords[];
220
+ config: ResolvedRunConfig;
221
+ }
222
+ /**
223
+ * Orchestration config for a benchmark. Holds identity, the knobs, the
224
+ * participants, and the optional completion hook — the workload lives in a
225
+ * separate `defineTask`. `bench run <file>` reads the `config` and `task`
226
+ * exports from the module and drives the run.
227
+ */
228
+ interface BenchmarkConfig<T extends BaseParticipant = BaseParticipant> {
229
+ /**
230
+ * Stable platform slug for this benchmark (e.g. 'sandbox-tti-local').
231
+ * Selectable per run with `--shape` (or overridable with `--benchmark`), so
232
+ * one entrypoint can report under several benchmarks.
233
+ */
234
+ benchmarkSlug: string;
235
+ /** Human-readable name shown on the platform. Overridable with `--name`. */
236
+ benchmarkName: string;
237
+ /**
238
+ * Named variants of this benchmark, selected with `--shape <name>`. Each
239
+ * shape swaps in its own platform identity (and optional stable knob) while
240
+ * reusing the same task and participants, so one bench file can back several
241
+ * platform benchmarks without duplicating the slug/name triple across
242
+ * package scripts and CI.
243
+ */
244
+ shapes?: Record<string, BenchmarkShape>;
245
+ /**
246
+ * Total tasks to run per participant. Default: 1. Mutually exclusive with
247
+ * `phases` — when `phases` is set, total iterations = sum of phase iterations.
248
+ */
249
+ iterations?: number;
250
+ /**
251
+ * Named run segments (e.g. cold/warm). Runs in order; each record is tagged
252
+ * with the phase name via `data.phase`. Mutually exclusive with `iterations`.
253
+ */
254
+ phases?: Phase[];
255
+ /** Max tasks in flight at once. 1 = sequential, N = burst. Default: 1. */
256
+ concurrency?: number;
257
+ /** Delay each task's start by `taskIndex * staggerDelayMs`. Default: 0. */
258
+ staggerDelayMs?: number;
259
+ /**
260
+ * Task ordering across participants. Default: 'participant' (run each
261
+ * participant's tasks to completion, then the next). 'round' takes turns:
262
+ * every participant runs its Nth task before anyone runs their (N+1)th, so
263
+ * all participants' Nth tasks happen back-to-back under the same conditions.
264
+ */
265
+ groupBy?: GroupBy;
266
+ /**
267
+ * Default participant names to run when `--provider` is not passed. Omit to
268
+ * run all env-available participants. `--provider` always overrides this.
269
+ */
270
+ defaultProviders?: string[];
271
+ /** The participants this benchmark can run against. `--provider` selects a subset by name. */
272
+ participants: T[];
273
+ /**
274
+ * Run-level scoring hook, called once with `lowerIsBetter` and `higherIsBetter`
275
+ * primitives after the outcome is assembled but before `onComplete`. Use it to
276
+ * define how the run should be scored and reported to the platform.
277
+ */
278
+ onScore?: (lowerIsBetter: LowerIsBetter, higherIsBetter: HigherIsBetter) => ScoringSpec | Promise<ScoringSpec>;
279
+ /**
280
+ * Run-level completion hook, called once with the full outcome after every
281
+ * participant finishes. Use it for aggregate output (legacy JSON/SVG
282
+ * writers). This is the run-level counterpart to per-step `ctx.measure`.
283
+ */
284
+ onComplete?: (outcome: BenchmarkRunOutcome) => void | Promise<void>;
285
+ }
286
+ /** Validates `config` at file-evaluation time so mistakes surface immediately. */
287
+ declare function defineBenchmarkConfig<T extends BaseParticipant = BaseParticipant>(config: BenchmarkConfig<T>): BenchmarkConfig<T>;
288
+ /**
289
+ * Declares the workload for a benchmark: a function invoked once per iteration.
290
+ * Steps are named via `ctx.step`, which supports closures and try/finally so
291
+ * values flow naturally between steps.
292
+ *
293
+ * export const task = defineTask(async (ctx) => {
294
+ * const sandbox = await ctx.step('create', () => provider.create());
295
+ * try { await ctx.step('exec', () => sandbox.run('node -v')); }
296
+ * finally { await ctx.step('destroy', () => sandbox.destroy()); }
297
+ * });
298
+ */
299
+ declare function defineTask<T extends BaseParticipant = BaseParticipant>(task: BenchmarkTask<T>): BenchmarkTask<T>;
300
+
301
+ /**
302
+ * Thrown when every selected participant was env-gated out, i.e. none of their
303
+ * `requiredEnvVars` are set. This is a "nothing to do" outcome rather than a
304
+ * failure — a benchmark job for a provider whose credentials aren't provisioned
305
+ * should skip, not go red — so callers are expected to exit 0 on it.
306
+ */
307
+ declare class NoAvailableParticipantsError extends Error {
308
+ readonly skipped: {
309
+ name: string;
310
+ missing: string[];
311
+ }[];
312
+ constructor(skipped: {
313
+ name: string;
314
+ missing: string[];
315
+ }[]);
316
+ }
317
+
318
+ interface CliArgs {
319
+ /** Which platform benchmark to report as (`--benchmark`, aka the benchmark slug). */
320
+ benchmark?: string;
321
+ name?: string;
322
+ /** Named variant from the bench file's `shapes` (`--shape`), swapping in its identity. */
323
+ shape?: string;
324
+ /**
325
+ * Idempotency key (`--run-key`): sibling processes passing the same key share
326
+ * one run (get-or-created), instead of each opening its own.
327
+ */
328
+ runKey?: string;
329
+ iterations?: number;
330
+ concurrency?: number;
331
+ staggerDelayMs?: number;
332
+ groupBy?: GroupBy;
333
+ /** Participant names from `--provider a,b` (repeatable). */
334
+ providers?: string[];
335
+ /** When true, run locally and do not ingest/report to the platform. */
336
+ noIngest?: boolean;
337
+ }
338
+ /**
339
+ * Parses the orchestration flags this runner understands, ignoring anything
340
+ * else. Supports both `--flag value` and `--flag=value`; `--provider` accepts
341
+ * a comma-separated list and may be repeated.
342
+ */
343
+ declare function parseCliArgs(argv: string[]): CliArgs;
344
+ /** Merges CLI overrides over config defaults, filling in knob fallbacks. */
345
+ declare function mergeConfig<T extends BaseParticipant>(config: BenchmarkConfig<T>, args: CliArgs): ResolvedRunConfig;
346
+ /**
347
+ * Runs `config`'s `task` against its participants. Selects participants by
348
+ * `--provider` (if given), env-gates them, then drives them per the resolved
349
+ * `groupBy`. `--shape` swaps in a declared variant's identity; `--benchmark`/
350
+ * `--name` retarget the run at a different platform benchmark, so one entrypoint
351
+ * can report under several slugs. With `--run-key`, sibling processes (e.g. one
352
+ * CI job per provider) get-or-create one shared run and each registers only its
353
+ * own participants.
354
+ */
355
+ declare function runBenchmark<T extends BaseParticipant>(fileConfig: BenchmarkConfig<T>, task: BenchmarkTask<T>, argv?: string[]): Promise<BenchmarkRunOutcome>;
356
+
357
+ /**
358
+ * Dispatches one CLI invocation. Throws on bad usage / invalid exports and lets
359
+ * `NoAvailableParticipantsError` propagate so the caller can map it to a clean
360
+ * exit. Does not call `process.exit`.
361
+ */
362
+ declare function runBenchmarkFile(argv: string[]): Promise<void>;
363
+ /** Executable entry: runs the file and maps outcomes to process exit codes. */
364
+ declare function run(argv: string[]): Promise<void>;
365
+
366
+ export { type BenchmarkConfig, type BenchmarkRunOutcome, type BenchmarkScoreResult, type BenchmarkTask, type CliArgs, type GroupBy, type MetricScoring, NoAvailableParticipantsError, type ParticipantRecords, type Phase, type ResolvedRunConfig, type ScoringSpec, type TaskContext, TaskError, type TaskResult, type TaskStepOptions, defineBenchmarkConfig, defineTask, higherIsBetter, lowerIsBetter, mergeConfig, parseCliArgs, run, runBenchmark, runBenchmarkFile, score };