@leostera/evalkit 0.6.0 → 0.7.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.
@@ -1,4 +1,4 @@
1
- import { A as TrialManifestSchema, C as RunManifestSchema, D as RunSummarySchema, F as normalizeAutEvent, G as relativeDatasetPath, H as toJsonObject, I as turnResultEvents, M as TrialScoringSchema, N as TrialSummarySchema, O as ScoreResultSchema, Q as resourceUri, R as canonicalParameters, V as toJson, b as JsonValueSchema, d as recordError, h as parseTrajectoryJsonl, i as authoringId, j as TrialMetadataSchema, k as TrajectoryEventSchema, r as agentRuntimes, w as RunMetadataSchema, y as CheckpointResultSchema } from "./src-D4tCwxNb.mjs";
1
+ import { A as RunSummarySchema, D as RunMetadataSchema, E as RunManifestSchema, F as TrialScoringSchema, H as DEFAULT_ARTIFACT_EXCLUDE, I as TrialSummarySchema, K as toJson, M as TrajectoryEventSchema, N as TrialManifestSchema, P as TrialMetadataSchema, R as normalizeAutEvent, S as JsonValueSchema, V as canonicalParameters, X as relativeDatasetPath, _ as parseTrajectoryJsonl, a as authoringId, i as agentRuntimes, j as ScoreResultSchema, p as recordError, q as toJsonObject, rt as resourceUri, x as CheckpointResultSchema, z as turnResultEvents } from "./src-b2Wnmzbb.mjs";
2
2
  import * as Schema from "effect/Schema";
3
3
  import { Data, Effect, Either, Exit, Scope } from "effect";
4
4
  import os from "node:os";
@@ -32,21 +32,63 @@ async function unlessAborted(work, signal) {
32
32
  }
33
33
  }
34
34
  //#endregion
35
+ //#region ../core/src/glob.ts
36
+ /**
37
+ * A glob matcher for discovery: `*` and `?` stay within a path segment, `**`
38
+ * spans segments, and `{a,b}` is an alternation.
39
+ */
40
+ function globMatcher(pattern) {
41
+ let source = "";
42
+ for (let index = 0; index < pattern.length; index++) {
43
+ const char = pattern[index];
44
+ if (char === "*" && pattern[index + 1] === "*") {
45
+ index++;
46
+ if (pattern[index + 1] === "/") {
47
+ index++;
48
+ source += "(?:.*/)?";
49
+ } else source += ".*";
50
+ } else if (char === "*") source += "[^/]*";
51
+ else if (char === "?") source += "[^/]";
52
+ else if (char === "{") {
53
+ const end = pattern.indexOf("}", index);
54
+ if (end === -1) source += "\\{";
55
+ else {
56
+ source += `(?:${pattern.slice(index + 1, end).split(",").map((part) => part.replace(/[.+^$()|[\]\\]/g, "\\$&").replace(/\*/g, "[^/]*")).join("|")})`;
57
+ index = end;
58
+ }
59
+ } else source += char.replace(/[.+^$()|[\]\\]/g, "\\$&");
60
+ }
61
+ const expression = new RegExp(`^${source}$`);
62
+ return (path) => expression.test(path);
63
+ }
64
+ //#endregion
35
65
  //#region ../runner/src/snapshot.ts
66
+ /** A pattern without `/` matches a name at any depth, like `.gitignore`. */
67
+ function excludeMatcher(patterns) {
68
+ const matchers = patterns.map((pattern) => {
69
+ const trimmed = pattern.replace(/^\.\//, "").replace(/\/$/, "");
70
+ const match = globMatcher(trimmed);
71
+ return trimmed.includes("/") ? match : (relativePath) => match(path.posix.basename(relativePath));
72
+ });
73
+ return (relativePath) => matchers.some((match) => match(relativePath));
74
+ }
36
75
  /**
37
- * Copies every regular file in the candidate workspace into the trial report.
38
- * Symbolic links are rejected so snapshots cannot follow paths outside the trial.
76
+ * Copies the candidate workspace's regular files into the trial report,
77
+ * leaving out excluded paths. Symbolic links and special files are skipped,
78
+ * never followed, so a snapshot cannot read outside the trial.
39
79
  */
40
- async function snapshotCandidateWorkspace(candidateRoot, writer) {
80
+ async function snapshotCandidateWorkspace(candidateRoot, writer, exclude = DEFAULT_ARTIFACT_EXCLUDE) {
41
81
  const artifacts = [];
82
+ const excluded = excludeMatcher(exclude);
42
83
  async function visit(directory, relativeDirectory) {
43
84
  const entries = await readdir(directory, { withFileTypes: true });
44
85
  entries.sort((left, right) => left.name.localeCompare(right.name));
45
86
  for (const entry of entries) {
46
87
  const absolutePath = path.join(directory, entry.name);
47
88
  const relativePath = path.join(relativeDirectory, entry.name);
89
+ if (excluded(relativePath.split(path.sep).join("/"))) continue;
48
90
  const stat = await lstat(absolutePath);
49
- if (stat.isSymbolicLink()) throw new Error(`Candidate workspace contains a symbolic link: ${relativePath}`);
91
+ if (stat.isSymbolicLink()) continue;
50
92
  if (stat.isDirectory()) {
51
93
  artifacts.push({
52
94
  path: path.join("candidate", relativePath),
@@ -55,7 +97,7 @@ async function snapshotCandidateWorkspace(candidateRoot, writer) {
55
97
  await visit(absolutePath, relativePath);
56
98
  continue;
57
99
  }
58
- if (!stat.isFile()) throw new Error(`Candidate workspace contains an unsupported entry: ${relativePath}`);
100
+ if (!stat.isFile()) continue;
59
101
  const data = await readFile(absolutePath);
60
102
  const artifactPath = path.join("candidate", relativePath);
61
103
  await writer.writeArtifact(artifactPath, data);
@@ -247,8 +289,9 @@ async function staticDatasetFile(dataset, root, file) {
247
289
  }
248
290
  //#endregion
249
291
  //#region ../runner/src/workspace.ts
250
- function trialDirectoryName(trialId) {
251
- return trialId.replace(/[^a-zA-Z0-9._-]/g, "_");
292
+ /** Where a trial's persistent workspace lives below `root`. */
293
+ function trialWorkspacePath(root, trialId) {
294
+ return path.join(path.resolve(root), trialId.replace(/[^a-zA-Z0-9._-]/g, "_"));
252
295
  }
253
296
  /**
254
297
  * Creates isolated candidate and evaluator directories and writes each
@@ -256,7 +299,7 @@ function trialDirectoryName(trialId) {
256
299
  * Two datasets writing the same file is an error, never an overwrite.
257
300
  */
258
301
  async function createTrialWorkspace(trialId, datasets = [], persistentRoot) {
259
- const root = persistentRoot ? path.join(path.resolve(persistentRoot), trialDirectoryName(trialId)) : await mkdtemp(path.join(os.tmpdir(), "evalkit-trial-"));
302
+ const root = persistentRoot ? trialWorkspacePath(persistentRoot, trialId) : await mkdtemp(path.join(os.tmpdir(), "evalkit-trial-"));
260
303
  const persistent = persistentRoot !== void 0;
261
304
  const artifacts = {
262
305
  candidate: { root: path.join(root, "candidate") },
@@ -270,10 +313,13 @@ async function createTrialWorkspace(trialId, datasets = [], persistentRoot) {
270
313
  }
271
314
  return {
272
315
  artifacts,
273
- cleanup: persistent ? async () => {} : () => rm(root, {
274
- recursive: true,
275
- force: true
276
- })
316
+ cleanup: async (keep = false) => {
317
+ if (persistent && keep) return;
318
+ await rm(root, {
319
+ recursive: true,
320
+ force: true
321
+ });
322
+ }
277
323
  };
278
324
  } catch (error) {
279
325
  await rm(root, {
@@ -545,15 +591,17 @@ async function executeTranscript(definition, session, input) {
545
591
  let turn = 0;
546
592
  for (const [index, step] of definition.transcript.entries()) {
547
593
  if (input.signal.aborted) throw cancelled();
594
+ const message = step.kind === "user" ? userMessage(step, index, input) : void 0;
548
595
  await emitRunner({
549
596
  kind: "transcript-step-started",
550
597
  step: index,
598
+ ...message === void 0 ? {} : { message },
551
599
  timestamp: now().toISOString()
552
600
  });
553
- if (step.kind === "user") {
601
+ if (message !== void 0) {
554
602
  const cursor = input.events.length;
555
603
  turn++;
556
- const result = await sendUserTurn(session, step.message.replaceAll("{{randomSeed}}", String(input.randomSeed)), input.signal);
604
+ const result = await sendUserTurn(session, message, input.signal);
557
605
  if (result !== void 0) for (const event of turnResultEvents(result, turn, now)) await input.emitAut(event);
558
606
  progress.lastTurn = turnView(input.events, cursor, input.events.length, index);
559
607
  } else if (step.kind === "predicate" || step.kind === "judge" || step.kind === "expect-tool-call") {
@@ -584,6 +632,18 @@ async function executeTranscript(definition, session, input) {
584
632
  });
585
633
  }
586
634
  }
635
+ /** A computed message sees the parameters and candidate data the agent also sees. */
636
+ function userMessage(step, index, { context, randomSeed }) {
637
+ if (typeof step.message === "string") return step.message;
638
+ const message = step.message({
639
+ parameters: context.parameters ?? {},
640
+ data: context.data,
641
+ seed: randomSeed,
642
+ trialIndex: context.trialIndex
643
+ });
644
+ if (typeof message !== "string" || !message.trim()) throw new Error(`The user message at step ${index + 1} must compute a non-empty string`);
645
+ return message;
646
+ }
587
647
  async function sendUserTurn(session, message, signal) {
588
648
  return runBoundary(Effect.tryPromise({
589
649
  try: () => unlessAborted(() => session.send(message), signal),
@@ -609,7 +669,8 @@ async function runCheckpoint(definition, step, index, input) {
609
669
  trajectory: { events: input.events },
610
670
  turn: progress.lastTurn,
611
671
  ...turnOutput(progress.lastTurn),
612
- data: input.data
672
+ data: input.data,
673
+ checkpoints: [...progress.checkpoints]
613
674
  }, now, definition.judge)).catch(async (error) => {
614
675
  const recorded = recordError(error);
615
676
  progress.checkpoints.push({
@@ -654,20 +715,33 @@ function turnOutput(turn) {
654
715
  }
655
716
  //#endregion
656
717
  //#region ../runner/src/final-scoring.ts
657
- function scoreSummary(results, checkpoints, skippedScorers) {
718
+ function scoreSummary(results, checkpoints, skippedScorers, metrics = []) {
658
719
  const valid = results.filter((result) => result.value !== void 0 && !result.skipped);
659
720
  const hasError = results.some((result) => result.error !== void 0);
660
721
  return {
661
722
  results,
723
+ ...metrics.length ? { metrics } : {},
662
724
  ...checkpoints.length ? { checkpoints } : {},
663
725
  ...skippedScorers.length ? { skippedScorers } : {},
664
726
  ...valid.length === 0 ? {} : { overall: valid.reduce((sum, result) => sum + (result.value ?? 0), 0) / valid.length },
665
727
  passed: !hasError && results.every((result) => result.skipped || result.passed === true) && checkpoints.every((result) => result.status === "passed" || result.status === "skipped") && !skippedScorers.length
666
728
  };
667
729
  }
730
+ /** What final scorers and metrics receive. */
731
+ function scoringContext(input) {
732
+ return {
733
+ context: input.context,
734
+ artifacts: input.workspace.artifacts,
735
+ trajectory: { events: input.events },
736
+ ...input.lastTurn ? { turn: input.lastTurn } : {},
737
+ ...turnOutput(input.lastTurn),
738
+ data: input.data,
739
+ checkpoints: input.checkpoints
740
+ };
741
+ }
668
742
  /** Append completed scores to the supplied arrays, even when a later scorer or report write fails. */
669
743
  async function evaluateFinalScorers(definition, input) {
670
- const { context, workspace, events, lastTurn, results, skippedScorers, now, emitRunner } = input;
744
+ const { context, workspace, results, skippedScorers, now, emitRunner } = input;
671
745
  for (const scorer of definition.scoring) {
672
746
  if (input.cancelled || input.reportingFailed || !context || !workspace || (input.primaryError || input.stoppedEarly) && (!("supportsPartial" in scorer) || !scorer.supportsPartial)) {
673
747
  skippedScorers.push(scorer.name);
@@ -681,14 +755,11 @@ async function evaluateFinalScorers(definition, input) {
681
755
  });
682
756
  let result;
683
757
  try {
684
- const score = await runBoundary(evaluateRule(scorer, {
758
+ const score = await runBoundary(evaluateRule(scorer, scoringContext({
759
+ ...input,
685
760
  context,
686
- artifacts: workspace.artifacts,
687
- trajectory: { events },
688
- ...lastTurn ? { turn: lastTurn } : {},
689
- ...turnOutput(lastTurn),
690
- data: input.data
691
- }, definition.judge, "scoring").pipe(Effect.mapError((cause) => new ScoringError({
761
+ workspace
762
+ }), definition.judge, "scoring").pipe(Effect.mapError((cause) => new ScoringError({
692
763
  cause,
693
764
  message: cause.message
694
765
  }))));
@@ -725,6 +796,68 @@ async function evaluateFinalScorers(definition, input) {
725
796
  }
726
797
  }
727
798
  //#endregion
799
+ //#region ../runner/src/metrics.ts
800
+ /**
801
+ * What EvalKit measures for every trial from its trajectory: the agent's time
802
+ * (first event to the last one before final scoring), tokens when the agent
803
+ * reported usage, and tool calls.
804
+ */
805
+ function builtInMetrics(events) {
806
+ const times = events.filter((event) => !event.kind.startsWith("scorer-")).map((event) => Date.parse(event.timestamp)).filter((time) => !Number.isNaN(time));
807
+ const metrics = [];
808
+ if (times.length) metrics.push({
809
+ name: "agent time",
810
+ value: Math.max(...times) - Math.min(...times),
811
+ unit: "ms",
812
+ better: "lower",
813
+ builtIn: true
814
+ });
815
+ let tokens;
816
+ for (const event of events) if (event.source === "aut" && event.kind === "turn-completed" && event.usage) {
817
+ const { totalTokens, inputTokens, outputTokens } = event.usage;
818
+ const turn = totalTokens ?? (inputTokens === void 0 && outputTokens === void 0 ? void 0 : (inputTokens ?? 0) + (outputTokens ?? 0));
819
+ if (turn !== void 0) tokens = (tokens ?? 0) + turn;
820
+ }
821
+ if (tokens !== void 0) metrics.push({
822
+ name: "tokens",
823
+ value: tokens,
824
+ unit: "tokens",
825
+ better: "lower",
826
+ builtIn: true
827
+ });
828
+ metrics.push({
829
+ name: "tool calls",
830
+ value: events.filter((event) => event.source === "aut" && event.kind === "tool-call").length,
831
+ builtIn: true
832
+ });
833
+ return metrics;
834
+ }
835
+ /** Runs the scenario's metrics; a failing metric records its error and the rest still run. */
836
+ async function evaluateMetrics(definition, context) {
837
+ const results = [];
838
+ for (const entry of definition.metrics ?? []) {
839
+ const shape = {
840
+ name: entry.name,
841
+ ...entry.unit ? { unit: entry.unit } : {},
842
+ ...entry.better ? { better: entry.better } : {}
843
+ };
844
+ try {
845
+ const value = await entry.run(context);
846
+ if (value !== void 0 && !Number.isFinite(value)) throw new Error(`Metric ${entry.name} must return a finite number or undefined; got ${String(value)}`);
847
+ results.push({
848
+ ...shape,
849
+ ...value === void 0 ? {} : { value }
850
+ });
851
+ } catch (error) {
852
+ results.push({
853
+ ...shape,
854
+ error: recordError(error)
855
+ });
856
+ }
857
+ }
858
+ return results;
859
+ }
860
+ //#endregion
728
861
  //#region ../runner/src/local-report-store.ts
729
862
  const MANIFEST_FILE = "manifest.json";
730
863
  /** The project's evals and suites when the run started (schema version 4+). */
@@ -1153,7 +1286,7 @@ async function runTrial(definition, options) {
1153
1286
  message: cause instanceof Error ? cause.message : String(cause)
1154
1287
  })
1155
1288
  }), (created) => Effect.tryPromise({
1156
- try: () => created.cleanup(),
1289
+ try: () => created.cleanup(keepWorkspace(options.keepWorkspaces, scoring?.passed === true)),
1157
1290
  catch: (cause) => new DatasetError({
1158
1291
  cause,
1159
1292
  message: cause instanceof Error ? cause.message : String(cause)
@@ -1218,6 +1351,7 @@ async function runTrial(definition, options) {
1218
1351
  const { checkpoints, stoppedEarly, lastTurn } = progress;
1219
1352
  const scoreResults = [];
1220
1353
  const skippedScorers = [];
1354
+ const metrics = [];
1221
1355
  try {
1222
1356
  await evaluateFinalScorers(definition, {
1223
1357
  context,
@@ -1231,13 +1365,23 @@ async function runTrial(definition, options) {
1231
1365
  results: scoreResults,
1232
1366
  skippedScorers,
1233
1367
  data: scoringData,
1368
+ checkpoints,
1234
1369
  now,
1235
1370
  emitRunner
1236
1371
  });
1237
- scoring = scoreSummary(scoreResults, checkpoints, skippedScorers);
1372
+ metrics.push(...builtInMetrics(events));
1373
+ if (context && workspace && !wasCancelled) metrics.push(...await evaluateMetrics(definition, scoringContext({
1374
+ context,
1375
+ workspace,
1376
+ events,
1377
+ lastTurn,
1378
+ data: scoringData,
1379
+ checkpoints
1380
+ })));
1381
+ scoring = scoreSummary(scoreResults, checkpoints, skippedScorers, metrics);
1238
1382
  await trialWriter.writeScores(scoring);
1239
1383
  if (workspace) try {
1240
- artifacts = await snapshotCandidateWorkspace(workspace.artifacts.candidate.root, trialWriter);
1384
+ artifacts = await snapshotCandidateWorkspace(workspace.artifacts.candidate.root, trialWriter, options.artifactExclude);
1241
1385
  } catch (captureError) {
1242
1386
  if (!primaryError) primaryError = captureError;
1243
1387
  await tryEmitError(captureError, emitRunner, now);
@@ -1252,7 +1396,7 @@ async function runTrial(definition, options) {
1252
1396
  await tryEmitError(cleanupError, emitRunner, now);
1253
1397
  }
1254
1398
  }
1255
- scoring ??= scoreSummary(scoreResults, checkpoints, skippedScorers);
1399
+ scoring ??= scoreSummary(scoreResults, checkpoints, skippedScorers, metrics);
1256
1400
  if (!primaryError) try {
1257
1401
  await emitRunner({
1258
1402
  kind: "trial-completed",
@@ -1325,5 +1469,9 @@ function trialCounts(trials) {
1325
1469
  cancelled: cancelledTrials
1326
1470
  };
1327
1471
  }
1472
+ /** Persistent workspaces of trials that did not pass are kept for debugging by default. */
1473
+ function keepWorkspace(policy = "failed", passed) {
1474
+ return policy === "all" || policy === "failed" && !passed;
1475
+ }
1328
1476
  //#endregion
1329
- export { ReportError as _, readRunSummary as a, readTrialScoring as c, staticDatasetFile as d, staticDatasetFiles as f, DatasetError as g, CheckpointExecutionError as h, readRunManifest as i, readTrialSummary as l, CancelledError as m, runMatrix as n, readTrialEvents as o, AutExecutionError as p, ownerIsGone as r, readTrialManifest as s, runEval as t, localReportStore as u, ScoringError as v };
1477
+ export { CheckpointExecutionError as _, readRunSummary as a, ScoringError as b, readTrialScoring as c, trialWorkspacePath as d, staticDatasetFile as f, CancelledError as g, AutExecutionError as h, readRunManifest as i, readTrialSummary as l, globMatcher as m, runMatrix as n, readTrialEvents as o, staticDatasetFiles as p, ownerIsGone as r, readTrialManifest as s, runEval as t, localReportStore as u, DatasetError as v, ReportError as y };
@@ -283,10 +283,10 @@ function catalogScorer(rule, at) {
283
283
  }
284
284
  function catalogStep(step, at) {
285
285
  switch (step.kind) {
286
- case "user": return {
286
+ case "user": return typeof step.message === "string" ? {
287
287
  kind: "user",
288
288
  message: step.message
289
- };
289
+ } : { kind: "user" };
290
290
  case "agent": return {
291
291
  kind: "agent",
292
292
  expectation: step.expectation
@@ -316,6 +316,22 @@ function shapeOf(value) {
316
316
  return { type: typeof json };
317
317
  }
318
318
  //#endregion
319
+ //#region ../core/src/config.ts
320
+ /** Left out of candidate snapshots unless `artifacts.exclude` says otherwise. */
321
+ const DEFAULT_ARTIFACT_EXCLUDE = [
322
+ ".git",
323
+ "node_modules",
324
+ ".venv",
325
+ "__pycache__",
326
+ "_build",
327
+ "_opam",
328
+ "target"
329
+ ];
330
+ /** Pure, typed project configuration. Loading/discovery belongs to the CLI. */
331
+ function defineConfig(configuration) {
332
+ return configuration;
333
+ }
334
+ //#endregion
319
335
  //#region ../core/src/canonical-json.ts
320
336
  /** Canonical JSON: keys sorted recursively, array order preserved. Rejects non-JSON inputs. */
321
337
  function canonicalParameters(value) {
@@ -663,8 +679,17 @@ const CheckpointResultSchema = Schema.Struct({
663
679
  id: Schema.String
664
680
  }))
665
681
  });
682
+ const MetricResultSchema = Schema.Struct({
683
+ name: Schema.String,
684
+ value: Schema.optional(Schema.Number),
685
+ unit: Schema.optional(Schema.String),
686
+ better: Schema.optional(Schema.Literal("lower", "higher")),
687
+ builtIn: Schema.optional(Schema.Literal(true)),
688
+ error: Schema.optional(RecordedErrorSchema)
689
+ });
666
690
  const TrialScoringSchema = Schema.Struct({
667
691
  results: Schema.mutable(Schema.Array(ScoreResultSchema)),
692
+ metrics: Schema.optional(Schema.mutable(Schema.Array(MetricResultSchema))),
668
693
  checkpoints: Schema.optional(Schema.mutable(Schema.Array(CheckpointResultSchema))),
669
694
  skippedScorers: Schema.optional(Schema.mutable(Schema.Array(Schema.String))),
670
695
  overall: Schema.optional(Schema.Number),
@@ -759,6 +784,7 @@ const runnerEvent = {
759
784
  source: Schema.Literal("runner"),
760
785
  kind: Schema.Literal("transcript-step-started"),
761
786
  step: Schema.Number,
787
+ message: Schema.optional(Schema.String),
762
788
  timestamp: Schema.String
763
789
  }),
764
790
  stepCompleted: Schema.Struct({
@@ -873,6 +899,10 @@ function expectToolCall(expected) {
873
899
  expected
874
900
  };
875
901
  }
902
+ /**
903
+ * A user message. Pass a function to compute it for each trial, e.g. from a
904
+ * matrix axis: `user(({ parameters }) => `Set up OCaml with ${parameters.toolchain}.`)`.
905
+ */
876
906
  function user(message) {
877
907
  return {
878
908
  kind: "user",
@@ -906,6 +936,15 @@ function predicate(name, run, options = {}) {
906
936
  ...options
907
937
  });
908
938
  }
939
+ function metric(name, run, options = {}) {
940
+ if (!name.trim()) throw new Error("Metric name must not be empty");
941
+ return markDefinitionSite({
942
+ kind: "metric",
943
+ name,
944
+ run,
945
+ ...options
946
+ });
947
+ }
909
948
  function judge(name, options) {
910
949
  if (!name.trim() || !options.rubric.trim()) throw new Error("Judge name and rubric must not be empty");
911
950
  return markDefinitionSite({
@@ -924,6 +963,12 @@ function defineEval(definition) {
924
963
  if (datasetIds.has(dataset.id)) throw new Error(`Eval ${definition.id} uses dataset ${dataset.id} more than once`);
925
964
  datasetIds.add(dataset.id);
926
965
  }
966
+ const metricNames = new Set(BUILT_IN_METRICS);
967
+ for (const [index, entry] of (definition.metrics ?? []).entries()) {
968
+ if (entry?.kind !== "metric") throw new Error(`Eval ${definition.id} metrics[${index}] is not a metric; create it with metric(...)`);
969
+ if (metricNames.has(entry.name)) throw new Error(`Eval ${definition.id} has more than one metric named ${entry.name} (built-in: ${BUILT_IN_METRICS.join(", ")})`);
970
+ metricNames.add(entry.name);
971
+ }
927
972
  let hasUser = false;
928
973
  for (const step of definition.transcript) if (step.kind === "user") hasUser = true;
929
974
  else if (step.kind !== "agent" && !hasUser) throw new Error("Checkpoint requires a preceding user step");
@@ -1000,6 +1045,12 @@ function registerEvals(registrations) {
1000
1045
  catalog: (options) => evals.map((evaluation) => catalogEntry(evaluation, suiteIdByEvalId.get(authoringId(evaluation)), options))
1001
1046
  };
1002
1047
  }
1048
+ /** Metrics EvalKit records for every trial; scenario metrics may not reuse these names. */
1049
+ const BUILT_IN_METRICS = [
1050
+ "agent time",
1051
+ "tokens",
1052
+ "tool calls"
1053
+ ];
1003
1054
  function recordError(error) {
1004
1055
  if (error instanceof Error) return {
1005
1056
  name: error.name,
@@ -1012,4 +1063,4 @@ function recordError(error) {
1012
1063
  };
1013
1064
  }
1014
1065
  //#endregion
1015
- export { resourceUriSchema as $, TrialManifestSchema as A, NotJsonError as B, RunManifestSchema as C, RunSummarySchema as D, RunStatusSchema as E, normalizeAutEvent as F, relativeDatasetPath as G, toJsonObject as H, turnResultEvents as I, ResourceUriSchema as J, definitionSite as K, defineEvalMatrix as L, TrialScoringSchema as M, TrialSummarySchema as N, ScoreResultSchema as O, UsageSchema as P, resourceUri as Q, canonicalParameters as R, RecordedErrorSchema as S, RunSourceSchema as T, defineDataset as U, toJson as V, isDataset as W, authorId as X, UuidSchema as Y, parseResourceUri as Z, ArtifactEntrySchema as _, defineAgent as a, JsonValueSchema as b, expectToolCall as c, recordError as d, resourceUuid as et, registerEvals as f, ApiErrorSchema as g, parseTrajectoryJsonl as h, authoringId as i, TrialMetadataSchema as j, TrajectoryEventSchema as k, judge as l, user as m, agent as n, defineEval as o, skip as p, ResourceKindSchema as q, agentRuntimes as r, defineSuite as s, DEFAULT_AGENT_RUNTIMES as t, predicate as u, AutIdentitySchema as v, RunMetadataSchema as w, JudgeRunInfoSchema as x, CheckpointResultSchema as y, catalogEntry as z };
1066
+ export { ResourceUriSchema as $, RunSummarySchema as A, defineEvalMatrix as B, JudgeRunInfoSchema as C, RunMetadataSchema as D, RunManifestSchema as E, TrialScoringSchema as F, NotJsonError as G, DEFAULT_ARTIFACT_EXCLUDE as H, TrialSummarySchema as I, defineDataset as J, toJson as K, UsageSchema as L, TrajectoryEventSchema as M, TrialManifestSchema as N, RunSourceSchema as O, TrialMetadataSchema as P, ResourceKindSchema as Q, normalizeAutEvent as R, JsonValueSchema as S, RecordedErrorSchema as T, defineConfig as U, canonicalParameters as V, catalogEntry as W, relativeDatasetPath as X, isDataset as Y, definitionSite as Z, parseTrajectoryJsonl as _, authoringId as a, resourceUuid as at, AutIdentitySchema as b, defineSuite as c, metric as d, UuidSchema as et, predicate as f, user as g, skip as h, agentRuntimes as i, resourceUriSchema as it, ScoreResultSchema as j, RunStatusSchema as k, expectToolCall as l, registerEvals as m, DEFAULT_AGENT_RUNTIMES as n, parseResourceUri as nt, defineAgent as o, recordError as p, toJsonObject as q, agent as r, resourceUri as rt, defineEval as s, BUILT_IN_METRICS as t, authorId as tt, judge as u, ApiErrorSchema as v, MetricResultSchema as w, CheckpointResultSchema as x, ArtifactEntrySchema as y, turnResultEvents as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leostera/evalkit",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Author and run agent evals as code",
5
5
  "type": "module",
6
6
  "license": "MIT",