@tangle-network/agent-bench 0.8.32 → 0.9.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.34
4
+
5
+ The dependency range requires Knowledge 14.0.3 for the compatible Eval cohort.
6
+
7
+ ## 0.8.33
8
+
9
+ Requires Eval `>=0.175.0 <0.176.0`, following Runtime 0.197.0.
10
+ No behavior change.
11
+
3
12
  ## 0.8.32
4
13
 
5
14
  The dependency ranges now require Runtime 0.195.0, Eval 0.174, and Knowledge 14.
package/HARNESS.md CHANGED
@@ -18,7 +18,7 @@ Use these labels literally. Do not promote one level into another in prose.
18
18
 
19
19
  | Level | What it establishes | Canonical path |
20
20
  |---|---|---|
21
- | **contract proof** | packages install; identities, budgets, callbacks, resume, and receipts have the expected shape | root `pnpm verify:official-optimizers`, `pnpm verify:primeintellect`, `pnpm verify:bench` |
21
+ | **contract proof** | packages install; identities, budgets, callbacks, resume, and receipts have the expected shape | root `pnpm verify:official-optimizers`, `pnpm verify:bench` |
22
22
  | **evaluator proof** | the benchmark's own evaluator can distinguish known fail/pass artifacts in the exact environment | adapter preflight and gold/self-check |
23
23
  | **reproduction proof** | an upstream method is run at a pinned revision on its claimed benchmark under a matched protocol | Discovery Lab reproduction manifest and runner |
24
24
  | **value proof** | the integrated method beats the preregistered baseline on frozen evidence with uncertainty and complete cost accounting | Discovery Lab result receipt |
@@ -35,7 +35,6 @@ From the repository root:
35
35
  ```bash
36
36
  pnpm verify:bench
37
37
  pnpm verify:official-optimizers
38
- pnpm verify:primeintellect
39
38
  ```
40
39
 
41
40
  `verify:official-optimizers` exercises the official Optimize Anything bridge, engine identities, equal input budgets, resume compatibility, candidate callbacks, accounting, and package provenance. Its deterministic candidate improvement is deliberately a fixture. It does **not** reproduce the published GEPA or Omni benchmark numbers.
@@ -52,6 +51,14 @@ pnpm run run-benchmarks
52
51
 
53
52
  Use `LOOP_ATTEMPTS=N` only when the benchmark's own visible feedback is allowed to enter later attempts. Hidden or gold material must remain outside the agent context.
54
53
 
54
+ `runBenchmarks()` returns each judged artifact, worker events, and observed usage in `perTask`.
55
+ Retry usage includes every attempt; missing receipts leave the measured subtotal explicitly incomplete.
56
+ Judge failures retain completed worker evidence.
57
+ Errors propagated by `close()` remain in `detail` beside the settled task outcome.
58
+ The current Runtime lineage suppresses sandbox deletion errors, so a returned result does not confirm resource deletion.
59
+ The caller's abort signal stops queued shots and reaches active sandbox turns.
60
+ `modelApiKey` supplies sandbox inference authorization separately from the `routerKey` used for sandbox control.
61
+
55
62
  ### Full-fidelity improvement fixture
56
63
 
57
64
  ```bash
package/dist/index.d.ts CHANGED
@@ -8,8 +8,9 @@ import { FINAL_ANSWER_SENTINEL, RagAnswerScore, RagContext, answerScoreToBenchSc
8
8
  import { createRagBenchAdapter } from "./benchmarks/ragbench.js";
9
9
  import { SweBenchAdapterOptions, SweBenchArtifactCaptureContext, SweBenchCacheLevel, createSweBenchAdapter, scoreSweReport, sweEvaluationArgv, swePatchOutput } from "./benchmarks/swe-bench.js";
10
10
  import { createT2RagBenchAdapter } from "./benchmarks/t2-ragbench.js";
11
- import { AgentProfile, SandboxClient } from "@tangle-network/agent-runtime/kernel";
12
- import { AgentCandidateBenchmarkGraderPort, AgentCandidateExecutionClaimStore, AgentCandidateExecutorPort, AgentCandidateExecutorRequest, AgentCandidateExecutorStopRequest, AgentCandidateOutputArtifactPort, AgentCandidateRunFinalization, PreparedAgentCandidateExecution } from "@tangle-network/agent-runtime";
11
+ import { AgentProfile, SandboxClient, sumSandboxUsage } from "@tangle-network/agent-runtime/kernel";
12
+ import { SandboxEvent } from "@tangle-network/sandbox";
13
+ import { AgentCandidateBenchmarkGraderPort, AgentCandidateExecutionClaimStore, AgentCandidateExecutorPort, AgentCandidateExecutorRequest, AgentCandidateExecutorStopRequest, AgentCandidateOutputArtifactPort, AgentCandidateRunFinalization, PreparedAgentCandidateExecution } from "@tangle-network/agent-runtime/candidate-execution";
13
14
  import { TraceStore } from "@tangle-network/agent-eval";
14
15
  //#region src/resolve-client.d.ts
15
16
  interface ResolveBenchClientOptions {
@@ -47,8 +48,16 @@ interface BenchCell {
47
48
  /** The agent under test. Defaults to a minimal `{ name, metadata.backendType }` profile. */
48
49
  readonly profile?: AgentProfile;
49
50
  }
50
- /** Runs one (adapter, task, cell) shot and returns the deliverable text. The default uses
51
- * `openSandboxRun`; tests inject a deterministic stub so the matrix runs offline. */
51
+ /** A worker's artifact and observed execution evidence, before external grading. */
52
+ interface BenchShotResult {
53
+ readonly artifact: string;
54
+ readonly ok: boolean;
55
+ readonly detail?: string;
56
+ /** Provider observations, including explicit unknown counters. Omitted when the shot reports none. */
57
+ readonly usage?: ReturnType<typeof sumSandboxUsage>;
58
+ readonly events?: readonly SandboxEvent[];
59
+ }
60
+ /** Runs one (adapter, task, cell) shot. Defaults to `openSandboxRun`. */
52
61
  type BenchShot = (input: {
53
62
  readonly adapter: BenchmarkAdapter;
54
63
  readonly task: BenchTask;
@@ -59,16 +68,15 @@ type BenchShot = (input: {
59
68
  readonly attempt?: number;
60
69
  readonly routerBaseUrl: string;
61
70
  readonly routerKey: string;
71
+ /** Optional inference credential for the box; routerKey continues to authorize sandbox control. */
72
+ readonly modelApiKey?: string;
62
73
  readonly bridgeUrl?: string;
63
74
  readonly bridgeBearer?: string;
64
75
  readonly sandboxBaseUrl?: string;
65
76
  readonly timeoutMs?: number;
77
+ readonly signal?: AbortSignal;
66
78
  readonly resolveClient?: typeof resolveBenchClient;
67
- }) => Promise<{
68
- artifact: string;
69
- ok: boolean;
70
- detail?: string;
71
- }>;
79
+ }) => Promise<BenchShotResult>;
72
80
  interface RunBenchmarksOptions {
73
81
  /** Registry keys (`resolveAdapter`) — the benchmark subset to run. */
74
82
  readonly benchmarks: readonly string[];
@@ -76,6 +84,8 @@ interface RunBenchmarksOptions {
76
84
  readonly cells: readonly BenchCell[];
77
85
  readonly routerBaseUrl: string;
78
86
  readonly routerKey: string;
87
+ /** Optional inference credential for the box; never used for sandbox creation or deletion. */
88
+ readonly modelApiKey?: string;
79
89
  readonly bridgeUrl?: string;
80
90
  readonly bridgeBearer?: string;
81
91
  readonly sandboxBaseUrl?: string;
@@ -89,6 +99,8 @@ interface RunBenchmarksOptions {
89
99
  readonly concurrency?: number;
90
100
  /** Per-shot wall-clock (ms). */
91
101
  readonly timeoutMs?: number;
102
+ /** Cancels active shots and prevents queued shots from starting. */
103
+ readonly signal?: AbortSignal;
92
104
  /** Test seam: resolve the runtime transport. Defaults to `resolveBenchClient`. */
93
105
  readonly resolveClient?: typeof resolveBenchClient;
94
106
  /** Max attempts per (benchmark × cell × task). Default 1. Attempts after the first receive
@@ -115,6 +127,11 @@ interface BenchCellTaskResult {
115
127
  readonly ok: boolean;
116
128
  readonly detail?: string;
117
129
  readonly wallMs: number;
130
+ /** Exact bytes given to the benchmark judge, retained even when judging fails. */
131
+ readonly artifact?: string;
132
+ readonly usage?: ReturnType<typeof sumSandboxUsage>;
133
+ /** Worker events only; benchmark grading remains outside this trace. */
134
+ readonly events?: readonly SandboxEvent[];
118
135
  }
119
136
  interface BenchLeaderboardRow {
120
137
  readonly benchmark: string;
@@ -302,5 +319,5 @@ declare class FilePierCandidateTrialController implements PierCandidateTrialCont
302
319
  //#region src/pier-result-grader.d.ts
303
320
  declare function createPierResultGrader(descriptor: Pick<PierCandidateGraderPort, 'name' | 'version' | 'artifact'>): PierCandidateGraderPort;
304
321
  //#endregion
305
- export { ADAPTERS, type BenchCell, type BenchCellTaskResult, type BenchLeaderboardRow, type BenchScore, type BenchShot, type BenchTask, type BenchmarkAdapter, type ExecutePreparedPierCandidateOptions, FINAL_ANSWER_SENTINEL, FilePierCandidateTrialController, type FilePierCandidateTrialControllerOptions, type JudgeArtifactFileReceipt, type JudgeArtifactReceipt, type LoadOptions, type PierCandidateGraderPort, type PierCandidateOfficialResult, type PierCandidateProcessSpec, type PierCandidateTerminationAcknowledgement, type PierCandidateTrialController, type PierCandidateTrialHandle, type PierCandidateTrialIdentity, type PierCandidateTrialResult, type PierDockerConnection, type RagAnswerScore, type RagContext, type RunBenchmarksOptions, type RunBenchmarksReport, StagedJudgeError, type StagedPierCandidateExecution, type StagedRunCaptureSpec, type StagedRunSpec, type SweBenchAdapterOptions, type SweBenchArtifactCaptureContext, type SweBenchCacheLevel, answerScoreToBenchScore, contextBlock, contextsFrom, createCragAdapter, createNoMiraclAdapter, createOpenRagBenchAdapter, createPierCandidateRecoveryExecutor, createPierResultGrader, createRagBenchAdapter, createSweBenchAdapter, createT2RagBenchAdapter, executePreparedPierCandidate, normalizeAnswer, parseCitations, parseFinalAnswer, printBenchmarksReport, ragAnswerOutput, resolveAdapter, runBenchmarks, runStagedJudge, scoreAnswerArtifact, scoreSweReport, sweEvaluationArgv, swePatchOutput, tokenF1 };
322
+ export { ADAPTERS, type BenchCell, type BenchCellTaskResult, type BenchLeaderboardRow, type BenchScore, type BenchShot, type BenchShotResult, type BenchTask, type BenchmarkAdapter, type ExecutePreparedPierCandidateOptions, FINAL_ANSWER_SENTINEL, FilePierCandidateTrialController, type FilePierCandidateTrialControllerOptions, type JudgeArtifactFileReceipt, type JudgeArtifactReceipt, type LoadOptions, type PierCandidateGraderPort, type PierCandidateOfficialResult, type PierCandidateProcessSpec, type PierCandidateTerminationAcknowledgement, type PierCandidateTrialController, type PierCandidateTrialHandle, type PierCandidateTrialIdentity, type PierCandidateTrialResult, type PierDockerConnection, type RagAnswerScore, type RagContext, type RunBenchmarksOptions, type RunBenchmarksReport, StagedJudgeError, type StagedPierCandidateExecution, type StagedRunCaptureSpec, type StagedRunSpec, type SweBenchAdapterOptions, type SweBenchArtifactCaptureContext, type SweBenchCacheLevel, answerScoreToBenchScore, contextBlock, contextsFrom, createCragAdapter, createNoMiraclAdapter, createOpenRagBenchAdapter, createPierCandidateRecoveryExecutor, createPierResultGrader, createRagBenchAdapter, createSweBenchAdapter, createT2RagBenchAdapter, executePreparedPierCandidate, normalizeAnswer, parseCitations, parseFinalAnswer, printBenchmarksReport, ragAnswerOutput, resolveAdapter, runBenchmarks, runStagedJudge, scoreAnswerArtifact, scoreSweReport, sweEvaluationArgv, swePatchOutput, tokenF1 };
306
323
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -14,12 +14,11 @@ import { createHash } from "node:crypto";
14
14
  import { accessSync, closeSync, constants, existsSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
15
15
  import { tmpdir } from "node:os";
16
16
  import { fileURLToPath } from "node:url";
17
- import { createExecutor, inlineSandboxClient, openSandboxRun, resolveSandboxClient } from "@tangle-network/agent-runtime/kernel";
17
+ import { SandboxRunAbortError, createExecutor, inlineSandboxClient, openSandboxRun, resolveSandboxClient, sumSandboxUsage } from "@tangle-network/agent-runtime/kernel";
18
18
  import { Sandbox } from "@tangle-network/sandbox";
19
19
  import assert from "node:assert/strict";
20
- import { executePreparedAgentCandidate } from "@tangle-network/agent-runtime";
20
+ import { captureAgentCandidateWorkspaceFiles, executePreparedAgentCandidate } from "@tangle-network/agent-runtime/candidate-execution";
21
21
  import { canonicalJson } from "@tangle-network/agent-eval";
22
- import { captureAgentCandidateWorkspaceFiles } from "@tangle-network/agent-runtime/candidate-execution";
23
22
  //#region src/refine-loop.ts
24
23
  const defaultDecide = (history) => history[history.length - 1]?.verdict?.valid === true;
25
24
  async function runRefineLoop(spec) {
@@ -253,7 +252,8 @@ function finalText(events) {
253
252
  }
254
253
  /** The default real-agent shot: one `openSandboxRun` over the cell's harness+model, deliverable
255
254
  * extracted by the adapter's parser (or final text), abortable on `timeoutMs`. */
256
- const openSandboxShot = async ({ adapter, task, cell, prompt, routerBaseUrl, routerKey, bridgeUrl, bridgeBearer, sandboxBaseUrl, timeoutMs, resolveClient }) => {
255
+ const openSandboxShot = async ({ adapter, task, cell, prompt, routerBaseUrl, routerKey, modelApiKey, bridgeUrl, bridgeBearer, sandboxBaseUrl, timeoutMs, signal, resolveClient }) => {
256
+ signal?.throwIfAborted();
257
257
  const client = (resolveClient ?? resolveBenchClient)({
258
258
  backend: cell.backend ?? "router",
259
259
  routerBaseUrl,
@@ -292,7 +292,8 @@ const openSandboxShot = async ({ adapter, task, cell, prompt, routerBaseUrl, rou
292
292
  model: {
293
293
  provider: profileProvider,
294
294
  model: cell.model,
295
- baseUrl: routerBaseUrl
295
+ baseUrl: routerBaseUrl,
296
+ ...modelApiKey === void 0 ? {} : { apiKey: modelApiKey }
296
297
  }
297
298
  }
298
299
  }
@@ -305,7 +306,7 @@ const openSandboxShot = async ({ adapter, task, cell, prompt, routerBaseUrl, rou
305
306
  const timer = timeoutMs ? setTimeout(() => controller.abort(), timeoutMs) : void 0;
306
307
  const runOptions = {
307
308
  agentRun,
308
- signal: controller.signal,
309
+ signal: signal ? AbortSignal.any([controller.signal, signal]) : controller.signal,
309
310
  runId: `bench:${adapter.name}:${task.id}:${uniq}`,
310
311
  scenarioId: task.id
311
312
  };
@@ -319,9 +320,20 @@ const openSandboxShot = async ({ adapter, task, cell, prompt, routerBaseUrl, rou
319
320
  });
320
321
  if (sres.exitCode !== 0) throw new Error(`boxSetup failed (exit ${sres.exitCode}): ${(sres.stderr ?? "").slice(0, 200)}`);
321
322
  };
322
- const run = await openSandboxRun(client, runOptions, deliverable);
323
+ let run;
324
+ let result = {
325
+ artifact: "",
326
+ ok: false
327
+ };
323
328
  try {
329
+ run = await openSandboxRun(client, runOptions, deliverable);
324
330
  const turn = await run.start(prompt ?? task.prompt);
331
+ result = {
332
+ artifact: "",
333
+ ok: false,
334
+ usage: sumSandboxUsage(turn.events),
335
+ events: turn.events
336
+ };
325
337
  let artifact = (turn.out ?? "").trim();
326
338
  let boxExtractError;
327
339
  if (adapter.boxExtract) try {
@@ -358,21 +370,42 @@ const openSandboxShot = async ({ adapter, task, cell, prompt, routerBaseUrl, rou
358
370
  } catch (err) {
359
371
  boxExtractError = err instanceof Error ? err.message.slice(0, 160) : String(err);
360
372
  }
361
- const detail = turn.readError !== void 0 ? `read: ${turn.readError.slice(0, 160)}` : boxExtractError !== void 0 ? `boxExtract: ${boxExtractError}` : void 0;
373
+ const detail = !turn.outcome.success ? turn.outcome.error ?? `agent ended with status ${turn.outcome.status}` : turn.readError !== void 0 ? `read: ${turn.readError.slice(0, 160)}` : boxExtractError !== void 0 ? `boxExtract: ${boxExtractError}` : void 0;
362
374
  if (process.env.BENCH_ARTIFACT_DIR) try {
363
375
  mkdirSync(process.env.BENCH_ARTIFACT_DIR, { recursive: true });
364
376
  const safe = `${adapter.name}_${task.id}_${uniq}`.replace(/[^a-zA-Z0-9_.-]/g, "_");
365
377
  writeFileSync(`${process.env.BENCH_ARTIFACT_DIR}/${safe}.patch`, artifact);
366
378
  } catch {}
367
- return {
379
+ result = {
368
380
  artifact,
369
- ok: artifact.length > 0,
381
+ ok: turn.outcome.success && artifact.length > 0 && turn.readError === void 0 && boxExtractError === void 0,
382
+ usage: result.usage,
383
+ events: turn.events,
370
384
  ...detail ? { detail } : {}
371
385
  };
386
+ } catch (err) {
387
+ result = {
388
+ ...result,
389
+ ok: false,
390
+ detail: err instanceof Error ? err.message : String(err),
391
+ ...err instanceof SandboxRunAbortError ? {
392
+ usage: sumSandboxUsage(err.events),
393
+ events: err.events
394
+ } : {}
395
+ };
372
396
  } finally {
373
397
  if (timer) clearTimeout(timer);
374
- await run.close();
398
+ try {
399
+ await run?.close();
400
+ } catch (err) {
401
+ const cleanup = `cleanup: ${err instanceof Error ? err.message : String(err)}`;
402
+ result = {
403
+ ...result,
404
+ detail: combineDetails(result.detail, cleanup)
405
+ };
406
+ }
375
407
  }
408
+ return result;
376
409
  };
377
410
  function parseMaybeJson(value) {
378
411
  try {
@@ -426,30 +459,54 @@ function retryPrompt(task, history, scores) {
426
459
  }
427
460
  async function loopedShot(input, shot, attempts) {
428
461
  const scores = /* @__PURE__ */ new Map();
429
- const result = await runRefineLoop({
430
- rounds: attempts,
431
- prompt: (round, history) => round === 1 ? input.task.prompt : retryPrompt(input.task, history, scores),
432
- runShot: async (prompt, round) => {
433
- const out = await shot({
434
- ...input,
435
- prompt,
436
- attempt: round
437
- });
438
- return {
439
- artifact: out.artifact,
440
- note: out.detail
441
- };
442
- },
443
- judge: async (artifact, round) => {
444
- const score = await input.adapter.judge(input.task, artifact);
445
- scores.set(round, score);
446
- return {
447
- valid: score.resolved,
448
- score: score.score
449
- };
450
- }
451
- });
462
+ const shots = /* @__PURE__ */ new Map();
463
+ let pendingShot = false;
464
+ let result;
465
+ try {
466
+ result = await runRefineLoop({
467
+ rounds: attempts,
468
+ prompt: (round, history) => round === 1 ? input.task.prompt : retryPrompt(input.task, history, scores),
469
+ runShot: async (prompt, round) => {
470
+ input.signal?.throwIfAborted();
471
+ pendingShot = true;
472
+ const out = await shot({
473
+ ...input,
474
+ prompt,
475
+ attempt: round
476
+ });
477
+ shots.set(round, out);
478
+ pendingShot = false;
479
+ return {
480
+ artifact: out.artifact,
481
+ note: out.detail
482
+ };
483
+ },
484
+ judge: async (artifact, round) => {
485
+ const score = await input.adapter.judge(input.task, artifact);
486
+ scores.set(round, score);
487
+ const succeeded = shots.get(round)?.ok === true;
488
+ return {
489
+ valid: succeeded && score.resolved,
490
+ score: succeeded ? score.score : 0
491
+ };
492
+ }
493
+ });
494
+ } catch (err) {
495
+ const completed = [...shots.values()];
496
+ return {
497
+ artifact: completed.at(-1)?.artifact ?? "",
498
+ ok: false,
499
+ usage: combinedUsage(pendingShot ? [...completed, {
500
+ artifact: "",
501
+ ok: false
502
+ }] : completed),
503
+ events: completed.flatMap((shot) => shot.events ?? []),
504
+ detail: err instanceof Error ? err.message : String(err)
505
+ };
506
+ }
452
507
  const best = result.rounds.reduce((winner, candidate) => {
508
+ if (shots.get(candidate.round)?.ok !== true) return winner;
509
+ if (shots.get(winner.round)?.ok !== true) return candidate;
453
510
  const a = scores.get(winner.round);
454
511
  const b = scores.get(candidate.round);
455
512
  if (!a) return candidate;
@@ -461,7 +518,9 @@ async function loopedShot(input, shot, attempts) {
461
518
  const bestScore = scores.get(best.round);
462
519
  return {
463
520
  artifact: best.artifact,
464
- ok: best.artifact.trim().length > 0,
521
+ ok: shots.get(best.round)?.ok === true && best.artifact.trim().length > 0,
522
+ usage: combinedUsage([...shots.values()]),
523
+ events: [...shots.values()].flatMap((shot) => shot.events ?? []),
465
524
  detail: JSON.stringify({
466
525
  mode: "refine-loop",
467
526
  attempts: result.rounds.length,
@@ -477,6 +536,33 @@ async function loopedShot(input, shot, attempts) {
477
536
  })
478
537
  };
479
538
  }
539
+ function combinedUsage(shots) {
540
+ const usage = {
541
+ input: 0,
542
+ output: 0,
543
+ costUsd: 0
544
+ };
545
+ let tokensKnown = shots.length > 0;
546
+ let usdKnown = shots.length > 0;
547
+ let estimate;
548
+ let unknownReason;
549
+ for (const shot of shots) {
550
+ usage.input += shot.usage?.input ?? 0;
551
+ usage.output += shot.usage?.output ?? 0;
552
+ usage.costUsd += shot.usage?.costUsd ?? 0;
553
+ tokensKnown &&= shot.usage !== void 0 && shot.usage.tokensKnown !== false;
554
+ usdKnown &&= shot.usage !== void 0 && shot.usage.usdKnown !== false;
555
+ unknownReason ??= shot.usage?.tokensUnknownReason;
556
+ if (shot.usage?.estimatedCostUsd !== void 0) estimate = (estimate ?? 0) + shot.usage.estimatedCostUsd;
557
+ }
558
+ return {
559
+ ...usage,
560
+ ...tokensKnown ? {} : { tokensKnown: false },
561
+ ...usdKnown ? {} : { usdKnown: false },
562
+ ...estimate === void 0 ? {} : { estimatedCostUsd: estimate },
563
+ ...unknownReason === void 0 ? {} : { tokensUnknownReason: unknownReason }
564
+ };
565
+ }
480
566
  function combineDetails(runDetail, scoreDetail) {
481
567
  if (runDetail && scoreDetail) return JSON.stringify({
482
568
  run: parseMaybeJson(runDetail),
@@ -535,6 +621,7 @@ async function prepareBenchmarks(benchmarks, resolve, opts) {
535
621
  };
536
622
  }
537
623
  async function runBenchmarks(opts) {
624
+ opts.signal?.throwIfAborted();
538
625
  if (opts.benchmarks.length === 0) throw new Error("runBenchmarks: no benchmarks selected");
539
626
  if (opts.cells.length === 0) throw new Error("runBenchmarks: no cells to run");
540
627
  const reps = Math.max(1, opts.reps ?? 1);
@@ -553,20 +640,24 @@ async function runBenchmarks(opts) {
553
640
  await runPool(jobs, Math.max(1, opts.concurrency ?? 4), async (job, index) => {
554
641
  const startedAt = Date.now();
555
642
  let result;
643
+ let out;
556
644
  try {
645
+ opts.signal?.throwIfAborted();
557
646
  const shotInput = {
558
647
  adapter: job.adapter,
559
648
  task: job.task,
560
649
  cell: job.cell,
561
650
  routerBaseUrl: opts.routerBaseUrl,
562
651
  routerKey: opts.routerKey,
652
+ ...opts.modelApiKey === void 0 ? {} : { modelApiKey: opts.modelApiKey },
563
653
  ...opts.bridgeUrl ? { bridgeUrl: opts.bridgeUrl } : {},
564
654
  ...opts.bridgeBearer ? { bridgeBearer: opts.bridgeBearer } : {},
565
655
  ...opts.sandboxBaseUrl ? { sandboxBaseUrl: opts.sandboxBaseUrl } : {},
566
656
  ...opts.timeoutMs ? { timeoutMs: opts.timeoutMs } : {},
657
+ ...opts.signal ? { signal: opts.signal } : {},
567
658
  ...opts.resolveClient ? { resolveClient: opts.resolveClient } : {}
568
659
  };
569
- const out = loopAttempts > 1 ? await loopedShot(shotInput, shot, loopAttempts) : await shot(shotInput);
660
+ out = loopAttempts > 1 ? await loopedShot(shotInput, shot, loopAttempts) : await shot(shotInput);
570
661
  const score = await job.adapter.judge(job.task, out.artifact);
571
662
  result = {
572
663
  benchmark: job.benchmark,
@@ -577,7 +668,10 @@ async function runBenchmarks(opts) {
577
668
  score: out.ok ? score.score : 0,
578
669
  ok: out.ok,
579
670
  ...out.detail ?? score.detail ? { detail: combineDetails(out.detail, score.detail) } : {},
580
- wallMs: Date.now() - startedAt
671
+ wallMs: Date.now() - startedAt,
672
+ artifact: out.artifact,
673
+ ...out.usage === void 0 ? {} : { usage: out.usage },
674
+ ...out.events === void 0 ? {} : { events: out.events }
581
675
  };
582
676
  } catch (err) {
583
677
  result = {
@@ -589,7 +683,10 @@ async function runBenchmarks(opts) {
589
683
  score: 0,
590
684
  ok: false,
591
685
  detail: err instanceof Error ? err.message.slice(0, 200) : String(err),
592
- wallMs: Date.now() - startedAt
686
+ wallMs: Date.now() - startedAt,
687
+ ...out === void 0 ? {} : { artifact: out.artifact },
688
+ ...out?.usage === void 0 ? {} : { usage: out.usage },
689
+ ...out?.events === void 0 ? {} : { events: out.events }
593
690
  };
594
691
  }
595
692
  perTask.push(result);