@leostera/evalkit 0.8.0 → 0.10.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.
Files changed (34) hide show
  1. package/CHANGELOG.md +93 -2
  2. package/README.md +5 -4
  3. package/dist/cli.mjs +510 -379
  4. package/dist/dashboard/assets/index-DNR4shrM.js +67 -0
  5. package/dist/dashboard/assets/index-TPYprg75.css +2 -0
  6. package/dist/dashboard/index.html +2 -2
  7. package/dist/{files-C-U0-PWW.mjs → files-DkDQs2JL.mjs} +3 -0
  8. package/dist/{index-db-vbqEGFaU.mjs → index-db-B1FeXzmS.mjs} +333 -51
  9. package/dist/index.d.mts +529 -183
  10. package/dist/index.d.mts.map +1 -1
  11. package/dist/index.mjs +2 -2
  12. package/dist/runner.d.mts +447 -173
  13. package/dist/runner.d.mts.map +1 -1
  14. package/dist/runner.mjs +2 -2
  15. package/dist/share-worker.d.mts +10 -3
  16. package/dist/share-worker.d.mts.map +1 -1
  17. package/dist/share-worker.mjs +6 -3
  18. package/dist/share.d.mts +49 -7
  19. package/dist/share.d.mts.map +1 -1
  20. package/dist/share.mjs +4 -4
  21. package/dist/skills/evalkit/SKILL.md +5 -5
  22. package/dist/skills/evalkit-agents/SKILL.md +5 -3
  23. package/dist/skills/evalkit-datasets/SKILL.md +5 -5
  24. package/dist/skills/evalkit-evals/SKILL.md +5 -5
  25. package/dist/skills/evalkit-judges/SKILL.md +8 -6
  26. package/dist/skills/evalkit-matrices/SKILL.md +2 -2
  27. package/dist/skills/evalkit-results/SKILL.md +6 -6
  28. package/dist/{src-LeGSyDD2.mjs → src-B14fXAGp.mjs} +334 -73
  29. package/dist/{src-CZnDXP1K.mjs → src-Bccv9igQ.mjs} +8 -3
  30. package/dist/{src-mm8L_IxM.mjs → src-BfSQKOJP.mjs} +676 -321
  31. package/dist/upgrade-G-uFPJKg.mjs +111 -0
  32. package/package.json +1 -1
  33. package/dist/dashboard/assets/index-C12IeG2y.css +0 -2
  34. package/dist/dashboard/assets/index-xlVsW6ZR.js +0 -37
@@ -1,6 +1,7 @@
1
+ import { n as assertNoRemovedOptions } from "./upgrade-G-uFPJKg.mjs";
1
2
  import * as Schema from "effect/Schema";
2
3
  //#region ../core/src/identity.ts
3
- const ResourceKindSchema = Schema.Literal("run", "trial", "artifact");
4
+ const ResourceKindSchema = Schema.Literal("run", "artifact");
4
5
  /** Human-authored project-local identifier; never a generated resource URI. */
5
6
  function authorId(value) {
6
7
  if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value)) throw new Error(`ID must be lowercase kebab-case: ${value}`);
@@ -8,7 +9,7 @@ function authorId(value) {
8
9
  }
9
10
  /** RFC 4122 textual UUID. EvalKit accepts v4/v7 and other valid UUID versions. */
10
11
  const UuidSchema = Schema.String.pipe(Schema.pattern(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i));
11
- const ResourceUriSchema = Schema.String.pipe(Schema.pattern(/^evalkit:(run|trial|artifact):[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i));
12
+ const ResourceUriSchema = Schema.String.pipe(Schema.pattern(/^evalkit:(run|artifact):[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i));
12
13
  function resourceUriSchema(kind) {
13
14
  return Schema.String.pipe(Schema.pattern(new RegExp(`^evalkit:${kind}:[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`, "i")));
14
15
  }
@@ -16,7 +17,7 @@ function resourceUri(kind, uuid) {
16
17
  return `evalkit:${kind}:${uuid}`;
17
18
  }
18
19
  function parseResourceUri(value, expectedKind) {
19
- const match = /^evalkit:(run|trial|artifact):(.+)$/.exec(value);
20
+ const match = /^evalkit:(run|artifact):(.+)$/.exec(value);
20
21
  if (!match) throw new Error(`Invalid EvalKit resource URI: ${value}`);
21
22
  const kind = match[1];
22
23
  if (expectedKind && kind !== expectedKind) throw new Error(`Expected an EvalKit ${expectedKind} URI; received ${value}`);
@@ -101,10 +102,10 @@ function relativeDatasetPath(value, label) {
101
102
  return parts.length ? parts.join("/") : ".";
102
103
  }
103
104
  /**
104
- * Declares named data that scenarios use: files placed in each trial's
105
+ * Declares named data that scenarios use: files placed in each run's
105
106
  * workspace, a value for the agent and scorers, or both. Static datasets give
106
107
  * `files`, `value` or `valueFile`; computed datasets give `onLoad` (cached per
107
- * process) and optionally `onRun` (per trial).
108
+ * process) and optionally `onRun` (per run).
108
109
  */
109
110
  function defineDataset(definition) {
110
111
  return markDefinitionSite(checkedDataset(definition));
@@ -205,11 +206,194 @@ function toJsonObject(value, path) {
205
206
  return converted;
206
207
  }
207
208
  //#endregion
209
+ //#region ../core/src/judge.ts
210
+ const KEY = /^[A-Za-z0-9][A-Za-z0-9_-]{0,31}$/;
211
+ function unitInterval(value) {
212
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1;
213
+ }
214
+ /**
215
+ * A judge that answers with one of these choices, in this order:
216
+ * `choices({ A: ['Accurate and helpful', 1], B: ['Partly right', 0.5], C: ['Wrong', 0] })`.
217
+ */
218
+ function choices(options) {
219
+ const entries = Object.entries(options);
220
+ if (entries.length < 2) throw new Error("choices(...) needs at least two choices");
221
+ const seen = /* @__PURE__ */ new Set();
222
+ return {
223
+ kind: "choices",
224
+ choices: entries.map(([key, [label, value]]) => {
225
+ if (!KEY.test(key)) throw new Error(`Choice ${JSON.stringify(key)} must be a short word of letters, digits, "-" or "_"`);
226
+ if (seen.has(key.toUpperCase())) throw new Error(`Choice ${key} is listed twice`);
227
+ seen.add(key.toUpperCase());
228
+ if (!label.trim()) throw new Error(`Choice ${key} needs a label`);
229
+ if (!unitInterval(value)) throw new Error(`Choice ${key} must be worth a number from 0 to 1; received ${String(value)}`);
230
+ return {
231
+ key,
232
+ label,
233
+ value
234
+ };
235
+ })
236
+ };
237
+ }
238
+ /**
239
+ * A judge that scores each criterion from 0 to 1; the score is the weighted
240
+ * mean. A number is the weight; `{ weight, description }` also says what the
241
+ * criterion means. Weights default to 1.
242
+ */
243
+ function criteria(options) {
244
+ return {
245
+ kind: "criteria",
246
+ criteria: declaredCriteria(options)
247
+ };
248
+ }
249
+ /** Checked criteria declarations, for `criteria()` and `predicate(..., { criteria })`. */
250
+ function declaredCriteria(options) {
251
+ const entries = Object.entries(options);
252
+ if (!entries.length) throw new Error("criteria(...) needs a criterion");
253
+ return entries.map(([name, option]) => {
254
+ if (!name.trim()) throw new Error("A criterion needs a name");
255
+ const weight = typeof option === "number" ? option : option.weight ?? 1;
256
+ if (!Number.isFinite(weight) || weight <= 0) throw new Error(`Criterion ${name} must have a positive weight; received ${String(weight)}`);
257
+ const description = typeof option === "number" ? void 0 : option.description;
258
+ return {
259
+ name,
260
+ weight,
261
+ ...description ? { description } : {}
262
+ };
263
+ });
264
+ }
265
+ /** What a criterion counts for in the weighted mean. */
266
+ const criterionValue = (result) => "value" in result ? result.value : 0;
267
+ /** The weighted mean of criteria; weights default to 1, a criterion not reached counts 0. */
268
+ function weightedMean(results) {
269
+ let total = 0;
270
+ let weights = 0;
271
+ for (const result of results) {
272
+ const weight = result.weight ?? 1;
273
+ total += weight * criterionValue(result);
274
+ weights += weight;
275
+ }
276
+ if (!weights) throw new Error("Criteria need a positive total weight");
277
+ return total / weights;
278
+ }
279
+ /** The inputs a judge sees when the rule names none. */
280
+ const DEFAULT_JUDGE_INPUTS = ["conversation", "reply"];
281
+ /** The names of the inputs a judge sees, in prompt order. */
282
+ function judgeInputNames(rule) {
283
+ return rule.inputs ? Object.keys(rule.inputs) : [...DEFAULT_JUDGE_INPUTS];
284
+ }
285
+ /**
286
+ * The prompt a judge agent receives. `values` holds each input's text;
287
+ * an input without one shows as a `{placeholder}`, which is how the
288
+ * dashboard shows the prompt before any run.
289
+ */
290
+ function judgePrompt(options) {
291
+ const inputs = options.inputs.map((name) => `${name}:\n${options.values?.[name] ?? `{${name}}`}`).join("\n\n");
292
+ return [
293
+ "You are judging one run of an AI agent. Judge only by the rubric, using only the inputs below.",
294
+ `Rubric:\n${options.rubric.trim()}`,
295
+ inputs,
296
+ answerInstruction(options.answer)
297
+ ].join("\n\n");
298
+ }
299
+ function answerInstruction(answer) {
300
+ switch (answer.kind) {
301
+ case "verdict": return "Answer with only a JSON object: {\"value\": <a number from 0 to 1>, \"explanation\": \"<one or two sentences why>\"}";
302
+ case "choices": return [`Answer with one of ${answer.choices.map((choice) => choice.key).join(", ")} on the first line, then one sentence why on the next.`, ...answer.choices.map((choice) => `${choice.key}) ${choice.label}`)].join("\n");
303
+ case "criteria": return [
304
+ "Score each criterion from 0 to 1:",
305
+ ...answer.criteria.map((criterion) => `- ${criterion.name}${criterion.description ? `: ${criterion.description}` : ""}`),
306
+ `Answer with only a JSON object: {"criteria": {${answer.criteria.map((criterion) => `${JSON.stringify(criterion.name)}: {"value": <0 to 1>, "explanation": "<why>"}`).join(", ")}}, "explanation": "<one sentence overall>"}`
307
+ ].join("\n");
308
+ }
309
+ }
310
+ /** Thrown when a judge's reply is not in the format asked for; the message says why. */
311
+ var UnreadableAnswer = class extends Error {};
312
+ /**
313
+ * Reads a judge's reply in the format its rule asked for. Throws
314
+ * `UnreadableAnswer` with a reason the judge can be told, so it can answer again.
315
+ */
316
+ function readJudgeAnswer(answer, reply) {
317
+ if (answer.kind === "choices") return readChoice(answer.choices, reply);
318
+ const object = jsonObject(reply);
319
+ if (answer.kind === "verdict") {
320
+ if (!unitInterval(object.value)) throw new UnreadableAnswer("\"value\" must be a number from 0 to 1");
321
+ return {
322
+ value: object.value,
323
+ ...typeof object.explanation === "string" && object.explanation.trim() ? { explanation: object.explanation.trim() } : {},
324
+ ...object.evidence !== void 0 ? { evidence: object.evidence } : {}
325
+ };
326
+ }
327
+ const scored = object.criteria;
328
+ if (typeof scored !== "object" || scored === null || Array.isArray(scored)) throw new UnreadableAnswer("\"criteria\" must be an object with one entry per criterion");
329
+ const results = answer.criteria.map((criterion) => {
330
+ const entry = scored[criterion.name];
331
+ const value = typeof entry === "number" ? entry : typeof entry === "object" && entry !== null && !Array.isArray(entry) ? entry.value : void 0;
332
+ if (!unitInterval(value)) throw new UnreadableAnswer(`criterion ${JSON.stringify(criterion.name)} needs a "value" from 0 to 1`);
333
+ const explanation = typeof entry === "object" && entry !== null && !Array.isArray(entry) && typeof entry.explanation === "string" && entry.explanation.trim() ? entry.explanation.trim() : void 0;
334
+ return {
335
+ name: criterion.name,
336
+ value,
337
+ weight: criterion.weight,
338
+ ...explanation ? { explanation } : {}
339
+ };
340
+ });
341
+ return {
342
+ value: weightedMean(results),
343
+ criteria: results,
344
+ ...typeof object.explanation === "string" && object.explanation.trim() ? { explanation: object.explanation.trim() } : {}
345
+ };
346
+ }
347
+ function readChoice(options, reply) {
348
+ if (typeof reply !== "string") throw new UnreadableAnswer("answer with plain text, not JSON");
349
+ const lines = reply.split("\n").map((line) => line.trim()).filter(Boolean);
350
+ const first = lines[0] ?? "";
351
+ const match = /^(?:answer\s*[:-]?\s*)?[*_("'[\s]*([A-Za-z0-9][A-Za-z0-9_-]*)/i.exec(first);
352
+ const said = match?.[1]?.toUpperCase();
353
+ const choice = options.find((option) => option.key.toUpperCase() === said);
354
+ if (!choice) throw new UnreadableAnswer(`the first line must be one of ${options.map((option) => option.key).join(", ")}`);
355
+ const explanation = [first.slice(match[0].length).replace(/^[*_)"'\].:\s-]+/, "").trim(), ...lines.slice(1)].filter(Boolean).join(" ");
356
+ return {
357
+ value: choice.value,
358
+ choice: choice.key,
359
+ ...explanation ? { explanation } : {}
360
+ };
361
+ }
362
+ /** The JSON object in a reply: the reply itself, or the first `{…}` in its text (code fences allowed). */
363
+ function jsonObject(reply) {
364
+ if (typeof reply === "object" && reply !== null && !Array.isArray(reply)) return reply;
365
+ if (typeof reply !== "string") throw new UnreadableAnswer("answer with a JSON object");
366
+ const start = reply.indexOf("{");
367
+ const end = reply.lastIndexOf("}");
368
+ if (start === -1 || end < start) throw new UnreadableAnswer("answer with only a JSON object");
369
+ let parsed;
370
+ try {
371
+ parsed = JSON.parse(reply.slice(start, end + 1));
372
+ } catch {
373
+ throw new UnreadableAnswer("the JSON object could not be parsed");
374
+ }
375
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new UnreadableAnswer("answer with a JSON object");
376
+ return parsed;
377
+ }
378
+ /**
379
+ * The pass mark in words: `only 1`, `≥ 0.5`, or for choices the ones that
380
+ * pass, such as `A or B`.
381
+ */
382
+ function passMark(passAt, answer) {
383
+ const at = passAt ?? 1;
384
+ if (answer?.kind === "choices") {
385
+ const passing = answer.choices.filter((choice) => choice.value >= at).map((choice) => choice.key);
386
+ if (!passing.length) return "no choice passes";
387
+ if (passing.length === answer.choices.length) return "any choice";
388
+ return passing.length === 1 ? `only ${passing[0]}` : `${passing.slice(0, -1).join(", ")} or ${passing.at(-1)}`;
389
+ }
390
+ return at === 1 ? "only 1" : `≥ ${at}`;
391
+ }
392
+ //#endregion
208
393
  //#region ../core/src/catalog.ts
209
394
  function catalogEntry(evaluation, suite, options = {}) {
210
395
  const at = (value) => relativeSite(definitionSite(value), options.root);
211
396
  const definedAt = at(evaluation);
212
- const judgeAgent = evaluation.judge ? catalogAgent(evaluation.judge, at) : void 0;
213
397
  const policy = evaluation.policy ?? {};
214
398
  return {
215
399
  id: evaluation.id,
@@ -217,16 +401,14 @@ function catalogEntry(evaluation, suite, options = {}) {
217
401
  ...evaluation.name ? { name: evaluation.name } : {},
218
402
  ...suite ? { suiteId: authoringId(suite) } : {},
219
403
  agent: catalogAgent(evaluation.agent, at),
220
- ...judgeAgent ? { judgeAgent } : {},
221
404
  datasets: (evaluation.datasets ?? []).map((dataset) => catalogDataset(dataset, at)),
222
- trialCount: policy.trials ?? 1,
405
+ repeat: evaluation.repeat ?? 1,
223
406
  policy: {
224
- trials: policy.trials ?? 1,
225
407
  ...policy.timeoutMs !== void 0 ? { timeoutMs: policy.timeoutMs } : {},
226
408
  ...policy.failfast !== void 0 ? { failfast: policy.failfast } : {}
227
409
  },
228
- transcript: evaluation.transcript.map((step) => catalogStep(step, at)),
229
- scorers: evaluation.scoring.map((rule) => catalogScorer(rule, at)),
410
+ transcript: evaluation.transcript.map((step) => catalogStep(step, at, options.judge)),
411
+ scorers: evaluation.scoring.map((rule) => catalogScorer(rule, at, options.judge)),
230
412
  ...evaluation.metadata !== void 0 ? { metadata: toJson(evaluation.metadata, `${evaluation.id}.metadata`) } : {},
231
413
  ...definedAt ? { definedAt } : {}
232
414
  };
@@ -270,18 +452,37 @@ function catalogDataset(dataset, at) {
270
452
  ...source.valueFile !== void 0 ? { value: { file: source.valueFile } } : source.value !== void 0 ? { value: { shape: shapeOf(source.value) } } : {}
271
453
  };
272
454
  }
273
- function catalogScorer(rule, at) {
455
+ function catalogScorer(rule, at, defaultJudge) {
274
456
  const definedAt = at(rule);
275
457
  return {
276
458
  name: rule.name,
277
459
  kind: rule.kind,
278
- ...rule.kind === "predicate" && rule.passAt !== void 0 ? { passAt: rule.passAt } : {},
460
+ ...rule.description ? { description: rule.description } : {},
461
+ ...rule.passAt !== void 0 ? { passAt: rule.passAt } : {},
279
462
  ...rule.supportsPartial !== void 0 ? { supportsPartial: rule.supportsPartial } : {},
280
- ...rule.kind === "judge" ? { rubric: rule.rubric } : {},
463
+ ...rule.kind === "judge" ? catalogJudge(rule, at, defaultJudge) : rule.criteria ? { answer: {
464
+ kind: "criteria",
465
+ criteria: rule.criteria
466
+ } } : {},
281
467
  ...definedAt ? { definedAt } : {}
282
468
  };
283
469
  }
284
- function catalogStep(step, at) {
470
+ function catalogJudge(rule, at, defaultJudge) {
471
+ const agent = rule.agent ?? defaultJudge;
472
+ const inputs = judgeInputNames(rule);
473
+ return {
474
+ rubric: rule.rubric,
475
+ ...agent ? { agent: catalogAgent(agent, at) } : {},
476
+ inputs,
477
+ answer: rule.answer,
478
+ prompt: judgePrompt({
479
+ rubric: rule.rubric,
480
+ answer: rule.answer,
481
+ inputs
482
+ })
483
+ };
484
+ }
485
+ function catalogStep(step, at, defaultJudge) {
285
486
  switch (step.kind) {
286
487
  case "user": return typeof step.message === "string" ? {
287
488
  kind: "user",
@@ -298,7 +499,7 @@ function catalogStep(step, at) {
298
499
  };
299
500
  default: return {
300
501
  kind: "checkpoint",
301
- scorer: catalogScorer(step, at)
502
+ scorer: catalogScorer(step, at, defaultJudge)
302
503
  };
303
504
  }
304
505
  }
@@ -317,6 +518,10 @@ function shapeOf(value) {
317
518
  }
318
519
  //#endregion
319
520
  //#region ../core/src/config.ts
521
+ const DEFAULT_ATTACHMENT_LIMITS = {
522
+ maxFileBytes: 5e6,
523
+ maxRunBytes: 2e7
524
+ };
320
525
  /** Left out of candidate snapshots unless `artifacts.exclude` says otherwise. */
321
526
  const DEFAULT_ARTIFACT_EXCLUDE = [
322
527
  ".git",
@@ -332,6 +537,18 @@ function defineConfig(configuration) {
332
537
  return configuration;
333
538
  }
334
539
  //#endregion
540
+ //#region ../core/src/usage.ts
541
+ /**
542
+ * The tokens a turn counts toward the `tokens` metric: `inputTokens +
543
+ * outputTokens` (cached input included), or `totalTokens` when neither is
544
+ * reported. Undefined when the turn reported no usage.
545
+ */
546
+ function turnTokens(usage) {
547
+ if (!usage) return void 0;
548
+ if (usage.inputTokens !== void 0 || usage.outputTokens !== void 0) return (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0);
549
+ return usage.totalTokens;
550
+ }
551
+ //#endregion
335
552
  //#region ../core/src/canonical-json.ts
336
553
  /** Canonical JSON: keys sorted recursively, array order preserved. Rejects non-JSON inputs. */
337
554
  function canonicalParameters(value) {
@@ -546,17 +763,20 @@ function turnResultEvents(result, turn, now) {
546
763
  role: "assistant",
547
764
  content: result.reply
548
765
  }, now));
549
- if (result.usage) events.push(normalizeAutEvent({
766
+ if (result.usage || result.model) events.push(normalizeAutEvent({
550
767
  kind: "turn-completed",
551
768
  turn,
552
- usage: result.usage
769
+ ...result.usage ? { usage: result.usage } : {},
770
+ ...result.model ? { model: result.model } : {}
553
771
  }, now));
554
772
  return events;
555
773
  }
556
774
  //#endregion
557
775
  //#region ../core/src/schema.ts
558
776
  /** Runtime schemas for serialized EvalKit report and API boundaries. */
559
- const RunStatusSchema = Schema.Literal("running", "completed", "failed", "cancelled");
777
+ const RunStatusSchema = Schema.Literal("running", "passed", "failed", "errored", "cancelled");
778
+ /** The report schema version this EvalKit writes and reads. */
779
+ const REPORT_SCHEMA_VERSION = 6;
560
780
  const RecordedErrorSchema = Schema.Struct({
561
781
  name: Schema.String,
562
782
  message: Schema.String,
@@ -594,8 +814,19 @@ const RunSourceSchema = Schema.Struct({
594
814
  url: Schema.optional(Schema.String)
595
815
  }))
596
816
  });
817
+ const BatchSchema = Schema.Struct({
818
+ id: Schema.String,
819
+ index: Schema.Number,
820
+ size: Schema.Number
821
+ });
822
+ const RunDatasetSchema = Schema.Struct({
823
+ id: Schema.String,
824
+ visibility: Schema.Literal("candidate", "evaluator"),
825
+ source: Schema.Literal("static", "computed"),
826
+ digest: Schema.String
827
+ });
597
828
  const RunMetadataSchema = Schema.Struct({
598
- schemaVersion: Schema.Literal(2, 3, 4, 5),
829
+ schemaVersion: Schema.Literal(6),
599
830
  runUri: resourceUriSchema("run"),
600
831
  evalId: Schema.String,
601
832
  suiteId: Schema.optional(Schema.String),
@@ -607,39 +838,21 @@ const RunMetadataSchema = Schema.Struct({
607
838
  process: Schema.optional(Schema.Struct({
608
839
  pid: Schema.Number,
609
840
  hostname: Schema.String
610
- }))
611
- });
612
- const TrialMetadataSchema = Schema.Struct({
613
- schemaVersion: Schema.Literal(2, 3, 4, 5),
614
- runUri: resourceUriSchema("run"),
615
- trialUri: resourceUriSchema("trial"),
616
- trialIndex: Schema.Number,
617
- evalId: Schema.String,
618
- parameters: Schema.optional(ParametersSchema),
619
- matrix: Schema.optional(MatrixCellSchema),
620
- aut: Schema.optional(AutIdentitySchema),
621
- startedAt: Schema.String,
841
+ })),
842
+ batch: Schema.optional(BatchSchema),
622
843
  /** The effective snapshot ignore patterns: the project's, then the scenario's. */
623
844
  workspaceIgnore: Schema.optional(Schema.mutable(Schema.Array(Schema.String))),
624
- datasets: Schema.optional(Schema.mutable(Schema.Array(Schema.Struct({
625
- id: Schema.String,
626
- visibility: Schema.Literal("candidate", "evaluator"),
627
- source: Schema.Literal("static", "computed"),
628
- digest: Schema.String
629
- }))))
845
+ datasets: Schema.optional(Schema.mutable(Schema.Array(RunDatasetSchema)))
630
846
  });
631
- /** On-disk manifests include a lifecycle status; older finalized manifests may still say running. */
847
+ /** On-disk manifests include a lifecycle status. */
632
848
  const RunManifestSchema = Schema.Struct({
633
849
  ...RunMetadataSchema.fields,
634
850
  status: Schema.optional(RunStatusSchema)
635
851
  });
636
- const TrialManifestSchema = Schema.Struct({
637
- ...TrialMetadataSchema.fields,
638
- status: Schema.optional(RunStatusSchema)
639
- });
640
852
  const UsageSchema = Schema.Struct({
641
853
  inputTokens: Schema.optional(Schema.Number),
642
854
  outputTokens: Schema.optional(Schema.Number),
855
+ cachedInputTokens: Schema.optional(Schema.Number),
643
856
  totalTokens: Schema.optional(Schema.Number)
644
857
  });
645
858
  const JudgeRunInfoSchema = Schema.Struct({
@@ -649,9 +862,32 @@ const JudgeRunInfoSchema = Schema.Struct({
649
862
  name: Schema.optional(Schema.String),
650
863
  version: Schema.optional(Schema.String)
651
864
  })),
865
+ model: Schema.optional(Schema.String),
652
866
  usage: Schema.optional(UsageSchema),
867
+ prompt: Schema.optional(Schema.String),
868
+ reply: Schema.optional(JsonValueSchema),
869
+ attempts: Schema.optional(Schema.Number),
653
870
  events: Schema.optional(Schema.mutable(Schema.Array(JsonValueSchema)))
654
871
  });
872
+ const CriterionResultSchema = Schema.Union(Schema.Struct({
873
+ name: Schema.String,
874
+ value: Schema.Number,
875
+ weight: Schema.optional(Schema.Number),
876
+ explanation: Schema.optional(Schema.String),
877
+ evidence: Schema.optional(JsonValueSchema)
878
+ }), Schema.Struct({
879
+ name: Schema.String,
880
+ reached: Schema.Literal(false),
881
+ weight: Schema.optional(Schema.Number),
882
+ explanation: Schema.optional(Schema.String)
883
+ }));
884
+ /** What every score and checkpoint result can carry about its verdict. */
885
+ const verdictFields = {
886
+ passAt: Schema.optional(Schema.Number),
887
+ choice: Schema.optional(Schema.String),
888
+ criteria: Schema.optional(Schema.mutable(Schema.Array(CriterionResultSchema))),
889
+ attachments: Schema.optional(Schema.mutable(Schema.Array(Schema.String)))
890
+ };
655
891
  const ScoreResultSchema = Schema.Struct({
656
892
  name: Schema.String,
657
893
  kind: Schema.Literal("predicate", "judge"),
@@ -659,6 +895,7 @@ const ScoreResultSchema = Schema.Struct({
659
895
  passed: Schema.optional(Schema.Boolean),
660
896
  explanation: Schema.optional(Schema.String),
661
897
  evidence: Schema.optional(JsonValueSchema),
898
+ ...verdictFields,
662
899
  judge: Schema.optional(JudgeRunInfoSchema),
663
900
  durationMs: Schema.Number,
664
901
  error: Schema.optional(RecordedErrorSchema),
@@ -673,6 +910,7 @@ const CheckpointResultSchema = Schema.Struct({
673
910
  passed: Schema.optional(Schema.Boolean),
674
911
  explanation: Schema.optional(Schema.String),
675
912
  evidence: Schema.optional(JsonValueSchema),
913
+ ...verdictFields,
676
914
  judge: Schema.optional(JudgeRunInfoSchema),
677
915
  durationMs: Schema.optional(Schema.Number),
678
916
  error: Schema.optional(RecordedErrorSchema),
@@ -689,7 +927,7 @@ const MetricResultSchema = Schema.Struct({
689
927
  builtIn: Schema.optional(Schema.Literal(true)),
690
928
  error: Schema.optional(RecordedErrorSchema)
691
929
  });
692
- const TrialScoringSchema = Schema.Struct({
930
+ const RunScoringSchema = Schema.Struct({
693
931
  results: Schema.mutable(Schema.Array(ScoreResultSchema)),
694
932
  metrics: Schema.optional(Schema.mutable(Schema.Array(MetricResultSchema))),
695
933
  checkpoints: Schema.optional(Schema.mutable(Schema.Array(CheckpointResultSchema))),
@@ -702,22 +940,18 @@ const ArtifactEntrySchema = Schema.Struct({
702
940
  kind: Schema.Literal("file", "directory"),
703
941
  size: Schema.optional(Schema.Number)
704
942
  });
705
- const TrialSummarySchema = Schema.Struct({
706
- status: RunStatusSchema,
707
- endedAt: Schema.String,
708
- durationMs: Schema.optional(Schema.Number),
709
- scoring: Schema.optional(TrialScoringSchema),
710
- artifacts: Schema.optional(Schema.mutable(Schema.Array(ArtifactEntrySchema))),
711
- error: Schema.optional(RecordedErrorSchema)
943
+ const AttachmentEntrySchema = Schema.Struct({
944
+ name: Schema.String,
945
+ size: Schema.Number,
946
+ truncatedFrom: Schema.optional(Schema.Number)
712
947
  });
713
948
  const RunSummarySchema = Schema.Struct({
714
949
  status: RunStatusSchema,
715
950
  endedAt: Schema.String,
716
951
  durationMs: Schema.optional(Schema.Number),
717
- trialCount: Schema.Number,
718
- passed: Schema.Number,
719
- failed: Schema.Number,
720
- cancelled: Schema.optional(Schema.Number),
952
+ scoring: Schema.optional(RunScoringSchema),
953
+ artifacts: Schema.optional(Schema.mutable(Schema.Array(ArtifactEntrySchema))),
954
+ attachments: Schema.optional(Schema.mutable(Schema.Array(AttachmentEntrySchema))),
721
955
  error: Schema.optional(RecordedErrorSchema)
722
956
  });
723
957
  const autEvent = {
@@ -761,7 +995,8 @@ const autEvent = {
761
995
  turn: Schema.Number,
762
996
  timestamp: Schema.String,
763
997
  latencyMs: Schema.optional(Schema.Number),
764
- usage: Schema.optional(UsageSchema)
998
+ usage: Schema.optional(UsageSchema),
999
+ model: Schema.optional(Schema.String)
765
1000
  }),
766
1001
  completed: Schema.Struct({
767
1002
  source: Schema.Literal("aut"),
@@ -777,9 +1012,9 @@ const autEvent = {
777
1012
  })
778
1013
  };
779
1014
  const runnerEvent = {
780
- trialStarted: Schema.Struct({
1015
+ runStarted: Schema.Struct({
781
1016
  source: Schema.Literal("runner"),
782
- kind: Schema.Literal("trial-started"),
1017
+ kind: Schema.Literal("run-started"),
783
1018
  timestamp: Schema.String
784
1019
  }),
785
1020
  stepStarted: Schema.Struct({
@@ -843,9 +1078,9 @@ const runnerEvent = {
843
1078
  error: RecordedErrorSchema,
844
1079
  timestamp: Schema.String
845
1080
  }),
846
- trialCompleted: Schema.Struct({
1081
+ runCompleted: Schema.Struct({
847
1082
  source: Schema.Literal("runner"),
848
- kind: Schema.Literal("trial-completed"),
1083
+ kind: Schema.Literal("run-completed"),
849
1084
  timestamp: Schema.String
850
1085
  }),
851
1086
  error: Schema.Struct({
@@ -902,7 +1137,7 @@ function expectToolCall(expected) {
902
1137
  };
903
1138
  }
904
1139
  /**
905
- * A user message. Pass a function to compute it for each trial, e.g. from a
1140
+ * A user message. Pass a function to compute it for each run, e.g. from a
906
1141
  * matrix axis: `user(({ parameters }) => `Set up OCaml with ${parameters.toolchain}.`)`.
907
1142
  */
908
1143
  function user(message) {
@@ -918,8 +1153,8 @@ function agent(expectation) {
918
1153
  };
919
1154
  }
920
1155
  /**
921
- * Marks a scorer as not applicable to this trial. A skipped result neither
922
- * passes nor fails the trial and is left out of score averages.
1156
+ * Marks a scorer as not applicable to this run. A skipped result neither
1157
+ * passes nor fails the run and is left out of score averages.
923
1158
  */
924
1159
  function skip(reason) {
925
1160
  if (!reason.trim()) throw new Error("skip() needs a reason");
@@ -928,16 +1163,20 @@ function skip(reason) {
928
1163
  reason
929
1164
  };
930
1165
  }
1166
+ /** A scorer in code. Its `evidence` type carries through to `checkpoint(rule)`. */
931
1167
  function predicate(name, run, options = {}) {
932
1168
  if (!name.trim()) throw new Error("Predicate name must not be empty");
933
- if (options.passAt !== void 0 && !(Number.isFinite(options.passAt) && options.passAt >= 0 && options.passAt <= 1)) throw new Error(`Predicate ${name} passAt must be a number from 0 to 1; received ${options.passAt}`);
1169
+ assertPassAt(`Predicate ${name}`, options.passAt);
1170
+ const { criteria: declared, ...rest } = options;
934
1171
  return markDefinitionSite({
935
1172
  kind: "predicate",
936
1173
  name,
937
1174
  run,
938
- ...options
1175
+ ...rest,
1176
+ ...declared ? { criteria: declaredCriteria(Object.fromEntries(Object.entries(declared).map(([key, option]) => [key, typeof option === "string" ? { description: option } : option]))) } : {}
939
1177
  });
940
1178
  }
1179
+ /** A built-in metric's name is a type error here, as well as at definition time. */
941
1180
  function metric(name, run, options = {}) {
942
1181
  if (!name.trim()) throw new Error("Metric name must not be empty");
943
1182
  return markDefinitionSite({
@@ -947,17 +1186,26 @@ function metric(name, run, options = {}) {
947
1186
  ...options
948
1187
  });
949
1188
  }
1189
+ const INPUT_NAME = /^[a-z][a-zA-Z0-9_-]{0,31}$/;
950
1190
  function judge(name, options) {
951
- if (!name.trim() || !options.rubric.trim()) throw new Error("Judge name and rubric must not be empty");
1191
+ if (!name.trim() || !options.rubric?.trim()) throw new Error("Judge name and rubric must not be empty");
1192
+ assertPassAt(`Judge ${name}`, options.passAt);
1193
+ for (const input of Object.keys(options.inputs ?? {})) if (!INPUT_NAME.test(input)) throw new Error(`Judge ${name} input ${JSON.stringify(input)} must start with a lowercase letter and use letters, digits, "-" or "_"`);
1194
+ if (options.inputs && !Object.keys(options.inputs).length) throw new Error(`Judge ${name} inputs must name at least one input`);
1195
+ if (options.agent && typeof options.agent.start !== "function") throw new Error(`Judge ${name} agent must be made with defineAgent()`);
952
1196
  return markDefinitionSite({
953
1197
  kind: "judge",
954
1198
  name,
955
- ...options
1199
+ ...options,
1200
+ answer: options.answer ?? { kind: "verdict" }
956
1201
  });
957
1202
  }
1203
+ function assertPassAt(owner, passAt) {
1204
+ if (passAt !== void 0 && !(Number.isFinite(passAt) && passAt >= 0 && passAt <= 1)) throw new Error(`${owner} passAt must be a number from 0 to 1; received ${passAt}`);
1205
+ }
958
1206
  function defineEval(definition) {
959
1207
  authoringId(definition);
960
- if ("fixtures" in definition) throw new Error(`Eval ${definition.id} uses fixtures, which were replaced by datasets: [defineDataset(...)]`);
1208
+ assertNoRemovedOptions(definition);
961
1209
  if (definition.metadata !== void 0) toJsonObject(definition.metadata, `${definition.id}.metadata`);
962
1210
  for (const [index, pattern] of (definition.workspace?.ignore ?? []).entries()) if (typeof pattern !== "string" || pattern.replace(/^!/, "").trim() === "") throw new Error(`Eval ${definition.id} workspace.ignore[${index}] must be a non-empty glob, got ${JSON.stringify(pattern)}`);
963
1211
  const datasetIds = /* @__PURE__ */ new Set();
@@ -975,10 +1223,13 @@ function defineEval(definition) {
975
1223
  let hasUser = false;
976
1224
  for (const step of definition.transcript) if (step.kind === "user") hasUser = true;
977
1225
  else if (step.kind !== "agent" && !hasUser) throw new Error("Checkpoint requires a preceding user step");
978
- if ((definition.transcript.some((step) => step.kind === "judge") || definition.scoring.some((rule) => rule.kind === "judge")) && !definition.judge) throw new Error("Judge rules require an eval judge agent");
979
- if (definition.judge && definition.judge === definition.agent) throw new Error("Judge agent must be distinct from the agent under test");
1226
+ assertJudgesIndependent(definition);
980
1227
  return markDefinitionSite(definition);
981
1228
  }
1229
+ /** A judge never grades with the agent it is judging. */
1230
+ function assertJudgesIndependent(definition, defaultJudge) {
1231
+ for (const rule of [...definition.transcript, ...definition.scoring]) if (rule.kind === "judge" && (rule.agent ?? defaultJudge) === definition.agent) throw new Error(`Judge ${rule.name} in ${definition.id} uses the agent under test; a judge needs its own agent`);
1232
+ }
982
1233
  function defineSuite(suite) {
983
1234
  authoringId(suite);
984
1235
  return suite;
@@ -1048,11 +1299,21 @@ function registerEvals(registrations) {
1048
1299
  catalog: (options) => evals.map((evaluation) => catalogEntry(evaluation, suiteIdByEvalId.get(authoringId(evaluation)), options))
1049
1300
  };
1050
1301
  }
1051
- /** Metrics EvalKit records for every trial; scenario metrics may not reuse these names. */
1302
+ /**
1303
+ * Metrics EvalKit records for every run; scenario metrics may not reuse
1304
+ * these names.
1305
+ *
1306
+ * - `agent time` (ms): from the run's first event to its last one before
1307
+ * final scoring.
1308
+ * - `tokens`: the sum of `turnTokens(usage)` over the turns that reported
1309
+ * usage, cached input included. Absent when no turn reported any.
1310
+ * - `tool calls`: the agent's `tool-call` events.
1311
+ */
1052
1312
  const BUILT_IN_METRICS = [
1053
1313
  "agent time",
1054
1314
  "tokens",
1055
- "tool calls"
1315
+ "tool calls",
1316
+ "judge tokens"
1056
1317
  ];
1057
1318
  function recordError(error) {
1058
1319
  if (error instanceof Error) return {
@@ -1066,4 +1327,4 @@ function recordError(error) {
1066
1327
  };
1067
1328
  }
1068
1329
  //#endregion
1069
- 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 };
1330
+ export { judgeInputNames as $, RunDatasetSchema as A, normalizeAutEvent as B, BatchSchema as C, MetricResultSchema as D, JudgeRunInfoSchema as E, RunStatusSchema as F, DEFAULT_ARTIFACT_EXCLUDE as G, defineEvalMatrix as H, RunSummarySchema as I, catalogEntry as J, DEFAULT_ATTACHMENT_LIMITS as K, ScoreResultSchema as L, RunMetadataSchema as M, RunScoringSchema as N, REPORT_SCHEMA_VERSION as O, RunSourceSchema as P, criteria as Q, TrajectoryEventSchema as R, AutIdentitySchema as S, JsonValueSchema as T, canonicalParameters as U, turnResultEvents as V, turnTokens as W, answerInstruction as X, UnreadableAnswer as Y, choices as Z, user as _, resourceUriSchema as _t, assertJudgesIndependent as a, toJson as at, ArtifactEntrySchema as b, defineEval as c, isDataset as ct, judge as d, ResourceKindSchema as dt, judgePrompt as et, metric as f, ResourceUriSchema as ft, skip as g, resourceUri as gt, registerEvals as h, parseResourceUri as ht, agentRuntimes as i, NotJsonError as it, RunManifestSchema as j, RecordedErrorSchema as k, defineSuite as l, relativeDatasetPath as lt, recordError as m, authorId as mt, DEFAULT_AGENT_RUNTIMES as n, readJudgeAnswer as nt, authoringId as o, toJsonObject as ot, predicate as p, UuidSchema as pt, defineConfig as q, agent as r, weightedMean as rt, defineAgent as s, defineDataset as st, BUILT_IN_METRICS as t, passMark as tt, expectToolCall as u, definitionSite as ut, parseTrajectoryJsonl as v, resourceUuid as vt, CheckpointResultSchema as w, AttachmentEntrySchema as x, ApiErrorSchema as y, UsageSchema as z };