@valerypopoff/rivet2-evaluations 2.3.0 → 2.5.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.
@@ -28,30 +28,40 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
28
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
29
 
30
30
  // src/index.ts
31
- var index_exports = {};
32
- __export(index_exports, {
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
33
  EvaluationGraphExecutionError: () => EvaluationGraphExecutionError,
34
34
  InMemoryEvaluationRunStore: () => InMemoryEvaluationRunStore,
35
35
  LEGACY_EVALUATOR_INPUT_IDS: () => LEGACY_EVALUATOR_INPUT_IDS,
36
+ applyEvaluationLibraryMutation: () => applyEvaluationLibraryMutation,
36
37
  areEvaluationDataTypesCompatible: () => areEvaluationDataTypesCompatible,
37
38
  assertEvaluationDatasetSnapshot: () => assertEvaluationDatasetSnapshot,
38
39
  assertEvaluationDatasetValuesMatchDeclaredTypes: () => assertEvaluationDatasetValuesMatchDeclaredTypes,
40
+ assertEvaluationRecordingArtifact: () => assertEvaluationRecordingArtifact,
39
41
  assertPortableJson: () => assertPortableJson,
40
42
  canonicalStringify: () => canonicalStringify,
41
43
  canonicalizePortableJson: () => canonicalizePortableJson,
42
44
  clonePortableJson: () => clonePortableJson,
45
+ createEmptyEvaluationLibrary: () => createEmptyEvaluationLibrary,
43
46
  createEmptyEvaluationProjectData: () => createEmptyEvaluationProjectData,
44
47
  createEmptyMetrics: () => createEmptyMetrics,
45
48
  createEvaluationBaselineSnapshot: () => createEvaluationBaselineSnapshot,
49
+ createEvaluationRunPlan: () => createEvaluationRunPlan,
50
+ createEvaluationRunShell: () => createEvaluationRunShell,
46
51
  deserializeEvaluationDatasetJson: () => deserializeEvaluationDatasetJson,
47
52
  deserializeEvaluationProjectData: () => deserializeEvaluationProjectData,
48
53
  deserializeEvaluationProjectDataFromString: () => deserializeEvaluationProjectDataFromString,
49
54
  deserializeEvaluationSuiteBundleJson: () => deserializeEvaluationSuiteBundleJson,
55
+ diffEvaluationLibraryMutation: () => diffEvaluationLibraryMutation,
50
56
  evaluateAssertion: () => evaluateAssertion,
51
57
  evaluateThresholdResults: () => evaluateThresholdResults,
58
+ evaluationLibraryValueEquals: () => evaluationLibraryValueEquals,
52
59
  finalizeEvaluationRecordingRetention: () => finalizeEvaluationRecordingRetention,
60
+ finalizeEvaluationRun: () => finalizeEvaluationRun,
53
61
  fingerprint: () => fingerprint,
54
62
  fingerprintEvaluationDataset: () => fingerprintEvaluationDataset,
63
+ getEvaluationLibraryResource: () => getEvaluationLibraryResource,
64
+ getEvaluationSuiteBundle: () => getEvaluationSuiteBundle,
55
65
  getEvaluationSuiteMode: () => getEvaluationSuiteMode,
56
66
  getEvaluationTopLevelOutputId: () => getEvaluationTopLevelOutputId,
57
67
  hasAuthoritativeEvaluationCriteria: () => hasAuthoritativeEvaluationCriteria,
@@ -61,11 +71,15 @@ __export(index_exports, {
61
71
  localizeEvaluationDataset: () => localizeEvaluationDataset,
62
72
  normalizeEvaluationAggregate: () => normalizeEvaluationAggregate,
63
73
  normalizeEvaluationBaselineSnapshot: () => normalizeEvaluationBaselineSnapshot,
74
+ normalizeEvaluationLibrary: () => normalizeEvaluationLibrary,
64
75
  normalizeEvaluationRun: () => normalizeEvaluationRun,
65
76
  normalizeEvaluationTrial: () => normalizeEvaluationTrial,
66
77
  portableEvaluationDataTypes: () => portableEvaluationDataTypes,
78
+ preserveEvaluationRunName: () => preserveEvaluationRunName,
79
+ reconcileEvaluationRunSnapshots: () => reconcileEvaluationRunSnapshots,
67
80
  runEvaluationCases: () => runEvaluationCases,
68
81
  runEvaluationSuite: () => runEvaluationSuite,
82
+ runEvaluationTrial: () => runEvaluationTrial,
69
83
  runEvaluationWorkPool: () => runEvaluationWorkPool,
70
84
  serializeEvaluationDatasetJson: () => serializeEvaluationDatasetJson,
71
85
  serializeEvaluationProjectData: () => serializeEvaluationProjectData,
@@ -79,7 +93,7 @@ __export(index_exports, {
79
93
  validateEvaluationDataset: () => validateEvaluationDataset,
80
94
  validateEvaluationDatasetForTransfer: () => validateEvaluationDatasetForTransfer
81
95
  });
82
- module.exports = __toCommonJS(index_exports);
96
+ module.exports = __toCommonJS(src_exports);
83
97
 
84
98
  // src/types.ts
85
99
  var EvaluationGraphExecutionError = class extends Error {
@@ -178,13 +192,29 @@ function fingerprintEvaluationDataset(value) {
178
192
  return fingerprint({ datasetId: value.id, fields: value.fields, cases: value.cases });
179
193
  }
180
194
  function assertEvaluationDatasetSnapshot(snapshot) {
181
- if (snapshot.dataset.projectId !== void 0 && snapshot.dataset.projectId !== snapshot.projectId) {
195
+ if (typeof snapshot.projectId !== "string" || snapshot.projectId.length === 0 || typeof snapshot.fingerprint !== "string" || snapshot.fingerprint.length === 0 || typeof snapshot.createdAt !== "string" || !Number.isFinite(Date.parse(snapshot.createdAt))) {
196
+ throw new Error("An evaluation dataset snapshot has invalid identity or creation time.");
197
+ }
198
+ if (snapshot.dataset.projectId !== snapshot.projectId) {
182
199
  throw new Error("An evaluation dataset snapshot must belong to its project.");
183
200
  }
184
201
  if (fingerprintEvaluationDataset(snapshot.dataset) !== snapshot.fingerprint) {
185
202
  throw new Error("An evaluation dataset snapshot fingerprint must match its dataset ID, fields, and cases.");
186
203
  }
187
204
  }
205
+ function assertEvaluationRecordingArtifact(value) {
206
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
207
+ throw new Error("An evaluation recording artifact must be an object.");
208
+ }
209
+ const artifact = value;
210
+ if (typeof artifact.projectId !== "string" || artifact.projectId.length === 0 || typeof artifact.runId !== "string" || artifact.runId.length === 0 || typeof artifact.trialId !== "string" || artifact.trialId.length === 0 || typeof artifact.serialized !== "string" || typeof artifact.createdAt !== "string" || !Number.isFinite(Date.parse(artifact.createdAt))) {
211
+ throw new Error("An evaluation recording artifact has invalid identity, payload, or creation time.");
212
+ }
213
+ const reference = artifact.reference;
214
+ if (typeof reference !== "object" || reference === null || typeof reference.id !== "string" || reference.id.length === 0 || !["temporary", "failure", "baseline", "retained"].includes(reference.retention ?? "") || reference.expiresAt !== void 0 && (typeof reference.expiresAt !== "string" || !Number.isFinite(Date.parse(reference.expiresAt)))) {
215
+ throw new Error("An evaluation recording artifact has an invalid retention reference.");
216
+ }
217
+ }
188
218
 
189
219
  // src/assertions.ts
190
220
  function readQuotedPathKey(source, start) {
@@ -680,6 +710,12 @@ function isRunExecutionStatus(value) {
680
710
  function isTerminalRunExecutionStatus(status) {
681
711
  return status === "completed" || status === "canceled" || status === "error";
682
712
  }
713
+ function reconcileRecordingReference(primary, secondary) {
714
+ if (!primary) return secondary;
715
+ if (!secondary) return primary;
716
+ const rank = (retention) => retention === "temporary" ? 0 : retention === "failure" ? 1 : 2;
717
+ return rank(secondary.retention) > rank(primary.retention) ? secondary : primary;
718
+ }
683
719
  function shouldReplaceEvaluationRun(existing, incoming) {
684
720
  if (!existing) return true;
685
721
  const existingRevision = existing.revision ?? 0;
@@ -687,6 +723,64 @@ function shouldReplaceEvaluationRun(existing, incoming) {
687
723
  if (incomingRevision !== existingRevision) return incomingRevision > existingRevision;
688
724
  return !isTerminalRunExecutionStatus(existing.executionStatus) || isTerminalRunExecutionStatus(incoming.executionStatus);
689
725
  }
726
+ function preserveEvaluationRunName(existing, incoming) {
727
+ return (existing == null ? void 0 : existing.name) !== void 0 && incoming.name === void 0 ? { ...incoming, name: existing.name } : incoming;
728
+ }
729
+ function reconcileEvaluationRunSnapshots(existing, incoming) {
730
+ if (!existing) return incoming;
731
+ const existingRevision = existing.revision ?? 0;
732
+ const incomingRevision = incoming.revision ?? 0;
733
+ if (incomingRevision < existingRevision) return existing;
734
+ if (incomingRevision > existingRevision) return preserveEvaluationRunName(existing, incoming);
735
+ if (!shouldReplaceEvaluationRun(existing, incoming)) return existing;
736
+ const existingTerminal = isTerminalRunExecutionStatus(existing.executionStatus);
737
+ const incomingTerminal = isTerminalRunExecutionStatus(incoming.executionStatus);
738
+ const existingTrials = existing.trials ?? [];
739
+ const incomingTrials = incoming.trials ?? [];
740
+ const incomingIsPrimary = incomingTerminal !== existingTerminal ? incomingTerminal : incomingTrials.length >= existingTrials.length;
741
+ const primary = preserveEvaluationRunName(existing, incomingIsPrimary ? incoming : existing);
742
+ const secondary = incomingIsPrimary ? existing : incoming;
743
+ const primaryTrials = primary.trials ?? [];
744
+ const secondaryTrialList = secondary.trials ?? [];
745
+ const name = existing.name ?? incoming.name;
746
+ const primaryTrialIds = new Set(primaryTrials.map((trial) => trial.id));
747
+ const secondaryTrials = new Map(secondaryTrialList.map((trial) => [trial.id, trial]));
748
+ return {
749
+ ...primary,
750
+ trials: [
751
+ ...primaryTrials.map((trial) => {
752
+ const other = secondaryTrials.get(trial.id);
753
+ if (!other) return trial;
754
+ const secondaryObservations = new Map(
755
+ (other.observations ?? []).map((observation) => [observation.id, observation])
756
+ );
757
+ const primaryObservationIds = new Set((trial.observations ?? []).map((observation) => observation.id));
758
+ const recording = reconcileRecordingReference(trial.recording, other.recording);
759
+ return {
760
+ ...trial,
761
+ ...recording === void 0 ? {} : { recording },
762
+ observations: [
763
+ ...(trial.observations ?? []).map((observation) => {
764
+ const otherObservation = secondaryObservations.get(observation.id);
765
+ const observationRecording = reconcileRecordingReference(
766
+ observation.recording,
767
+ otherObservation == null ? void 0 : otherObservation.recording
768
+ );
769
+ return observationRecording === observation.recording ? observation : {
770
+ ...observation,
771
+ ...observationRecording === void 0 ? {} : { recording: observationRecording }
772
+ };
773
+ }),
774
+ ...(other.observations ?? []).filter((observation) => !primaryObservationIds.has(observation.id))
775
+ ]
776
+ };
777
+ }),
778
+ ...secondaryTrialList.filter((trial) => !primaryTrialIds.has(trial.id))
779
+ ],
780
+ ...name === void 0 ? {} : { name },
781
+ warnings: [.../* @__PURE__ */ new Set([...primary.warnings ?? [], ...secondary.warnings ?? []])]
782
+ };
783
+ }
690
784
  function isPurpose(value) {
691
785
  return value === "evaluation" || value === "execution-benchmark";
692
786
  }
@@ -797,6 +891,11 @@ function normalizeEvaluationAggregate(value, trials) {
797
891
  if (!isRecord(value)) throw new Error("Evaluation aggregate must be an object.");
798
892
  const normalized = structuredClone(value);
799
893
  if (!isNormalizedScore(normalized.meanScore)) delete normalized.meanScore;
894
+ if (!isNormalizedScore(normalized.medianScore)) delete normalized.medianScore;
895
+ if (!isNormalizedScore(normalized.p95Score)) delete normalized.p95Score;
896
+ if (typeof normalized.medianLatencyMs !== "number" || !Number.isFinite(normalized.medianLatencyMs) || normalized.medianLatencyMs < 0) {
897
+ delete normalized.medianLatencyMs;
898
+ }
800
899
  if (trials) {
801
900
  const completed = trials.filter((trial) => trial.executionStatus === "completed");
802
901
  const evaluated2 = completed.filter((trial) => trial.qualityStatus === "passed" || trial.qualityStatus === "failed");
@@ -899,6 +998,10 @@ function normalizeEvaluationRun(value) {
899
998
  } : value.provenance;
900
999
  const normalized = structuredClone(value);
901
1000
  delete normalized.verdict;
1001
+ const name = typeof value.name === "string" && value.name.trim().length > 0 ? value.name.trim() : void 0;
1002
+ delete normalized.name;
1003
+ const requestedTrialCount = typeof value.requestedTrialCount === "number" && Number.isSafeInteger(value.requestedTrialCount) && value.requestedTrialCount >= 0 ? value.requestedTrialCount : void 0;
1004
+ delete normalized.requestedTrialCount;
902
1005
  const aggregate2 = value.aggregate === void 0 ? void 0 : normalizeEvaluationAggregate(value.aggregate, trials);
903
1006
  return {
904
1007
  ...normalized,
@@ -918,6 +1021,8 @@ function normalizeEvaluationRun(value) {
918
1021
  provenance,
919
1022
  trials,
920
1023
  thresholdResults: Array.isArray(value.thresholdResults) ? structuredClone(value.thresholdResults) : [],
1024
+ ...name === void 0 ? {} : { name },
1025
+ ...requestedTrialCount === void 0 ? {} : { requestedTrialCount },
921
1026
  ...aggregate2 === void 0 ? {} : { aggregate: accountingStatus === "partial" ? withoutAuthoritativeCost(aggregate2) : aggregate2 }
922
1027
  };
923
1028
  }
@@ -1036,7 +1141,7 @@ async function runEvaluationWorkPool(options) {
1036
1141
  const work = options.work[index];
1037
1142
  const result = await options.execute(work, index);
1038
1143
  results[index] = result;
1039
- (_b = options.onSettled) == null ? void 0 : _b.call(options, result, index, results);
1144
+ await ((_b = options.onSettled) == null ? void 0 : _b.call(options, result, index, results));
1040
1145
  }
1041
1146
  };
1042
1147
  await Promise.all(Array.from({ length: Math.min(options.concurrency, options.work.length) }, worker));
@@ -1316,9 +1421,7 @@ function validateSuite(project, suite, dataset, purpose) {
1316
1421
  for (const assertion of suite.assertions) {
1317
1422
  const outputId = getEvaluationTopLevelOutputId(assertion.outputPath);
1318
1423
  if (assertion.outputPath.trim() !== "$" && outputId === void 0) {
1319
- throw new Error(
1320
- `Assertion "${assertion.name}" output path must start with a named target Graph Output.`
1321
- );
1424
+ throw new Error(`Assertion "${assertion.name}" output path must start with a named target Graph Output.`);
1322
1425
  }
1323
1426
  if (outputId !== void 0 && !graphOutputIds.includes(outputId)) {
1324
1427
  throw new Error(`Assertion "${assertion.name}" references missing target output "${outputId}".`);
@@ -1697,9 +1800,9 @@ async function runWithTimeout(run, timeoutMs, outer) {
1697
1800
  outer == null ? void 0 : outer.removeEventListener("abort", abort);
1698
1801
  }
1699
1802
  }
1700
- async function runTrial(input) {
1803
+ async function runEvaluationTrial(input) {
1701
1804
  var _a, _b, _c, _d;
1702
- const { project, suite, dataset, purpose, testCase, caseIndex, trialIndex, runId, runGraph, signal } = input;
1805
+ const { project, suite, dataset, purpose, testCase, caseIndex, trialIndex, runId, runGraph, signal, trialId } = input;
1703
1806
  const evaluationMode = getEvaluationSuiteMode(suite);
1704
1807
  const { inputs, expected } = resolveCaseInputs(suite, dataset, testCase, purpose);
1705
1808
  const seed = deriveSeed((_a = suite.configuration) == null ? void 0 : _a.seed, testCase.id, trialIndex);
@@ -1745,7 +1848,7 @@ async function runTrial(input) {
1745
1848
  const executionStatus2 = "canceled";
1746
1849
  const qualityStatus2 = "not-evaluated";
1747
1850
  return {
1748
- id: makeId("trial"),
1851
+ id: trialId ?? makeId("trial"),
1749
1852
  caseId: testCase.id,
1750
1853
  caseName: testCase.name,
1751
1854
  caseIndex,
@@ -1873,7 +1976,7 @@ async function runTrial(input) {
1873
1976
  const executionStatus2 = "canceled";
1874
1977
  const qualityStatus2 = "not-evaluated";
1875
1978
  return {
1876
- id: makeId("trial"),
1979
+ id: trialId ?? makeId("trial"),
1877
1980
  caseId: testCase.id,
1878
1981
  caseName: testCase.name,
1879
1982
  caseIndex,
@@ -1958,7 +2061,7 @@ async function runTrial(input) {
1958
2061
  }
1959
2062
  const totalMetrics = mergeMetrics(targetMetrics, evaluatorMetrics);
1960
2063
  return {
1961
- id: makeId("trial"),
2064
+ id: trialId ?? makeId("trial"),
1962
2065
  caseId: testCase.id,
1963
2066
  caseName: testCase.name,
1964
2067
  caseIndex,
@@ -1984,6 +2087,13 @@ function percentile(values, percentage) {
1984
2087
  const ordered = values.slice().sort((left, right) => left - right);
1985
2088
  return ordered[Math.min(ordered.length - 1, Math.ceil(percentage / 100 * ordered.length) - 1)];
1986
2089
  }
2090
+ function median(values) {
2091
+ if (values.length === 0) return 0;
2092
+ const ordered = values.slice().sort((left, right) => left - right);
2093
+ const upperIndex = Math.floor(ordered.length / 2);
2094
+ if (ordered.length % 2 !== 0) return ordered[upperIndex];
2095
+ return (ordered[upperIndex - 1] + ordered[upperIndex]) / 2;
2096
+ }
1987
2097
  function meanObservationScore(observations) {
1988
2098
  let weightedScore = 0;
1989
2099
  let totalWeight = 0;
@@ -2058,6 +2168,7 @@ function aggregate(trials, evaluationMode = "pass-fail") {
2058
2168
  const scoredTrialCount = cases.reduce((sum, testCase) => sum + (testCase.scoredTrialCount ?? 0), 0);
2059
2169
  const scoringCaseMeans = cases.map((testCase) => testCase.meanScore).filter((score) => score !== void 0);
2060
2170
  const scoringMeanScore = scoringCaseMeans.length === 0 ? void 0 : scoringCaseMeans.reduce((sum, score) => sum + score, 0) / scoringCaseMeans.length;
2171
+ const incurredLatencies = incurred.map((trial) => trial.totalMetrics.durationMs);
2061
2172
  const aggregateValue = {
2062
2173
  trialCount: trials.length,
2063
2174
  evaluatedTrialCount: evaluated.length,
@@ -2071,13 +2182,15 @@ function aggregate(trials, evaluationMode = "pass-fail") {
2071
2182
  ...evaluationMode === "scoring" ? {
2072
2183
  scoredTrialCount,
2073
2184
  missingScoreTrialCount: trials.length - scoredTrialCount,
2074
- ...scoringMeanScore === void 0 ? {} : { meanScore: scoringMeanScore }
2185
+ ...scoringMeanScore === void 0 ? {} : { meanScore: scoringMeanScore },
2186
+ ...scoringCaseMeans.length === 0 ? {} : {
2187
+ medianScore: median(scoringCaseMeans),
2188
+ p95Score: percentile(scoringCaseMeans, 95)
2189
+ }
2075
2190
  } : meanScore === void 0 ? {} : { meanScore },
2076
- averageLatencyMs: incurred.length === 0 ? 0 : incurred.reduce((sum, trial) => sum + trial.totalMetrics.durationMs, 0) / incurred.length,
2077
- p95LatencyMs: percentile(
2078
- incurred.map((trial) => trial.totalMetrics.durationMs),
2079
- 95
2080
- ),
2191
+ averageLatencyMs: incurred.length === 0 ? 0 : incurredLatencies.reduce((sum, durationMs) => sum + durationMs, 0) / incurred.length,
2192
+ medianLatencyMs: median(incurredLatencies),
2193
+ p95LatencyMs: percentile(incurredLatencies, 95),
2081
2194
  ...incurred.length === 0 || totalCostUsd === void 0 ? {} : { totalCostUsd, averageCostUsd: totalCostUsd / incurred.length },
2082
2195
  targetErrorRate: settled.length === 0 ? 0 : errors.length / settled.length,
2083
2196
  evaluatorErrorRate: settled.length === 0 ? 0 : settled.filter(
@@ -2405,8 +2518,8 @@ function createProvenance(project, suite, dataset, options) {
2405
2518
  accountingComplete: true
2406
2519
  };
2407
2520
  }
2408
- async function runEvaluationSuite(options) {
2409
- var _a, _b, _c, _d, _e;
2521
+ function createEvaluationRunPlan(options) {
2522
+ var _a;
2410
2523
  const suite = resolveSuite(options.evaluationData, options.suiteId);
2411
2524
  const purpose = options.purpose ?? "evaluation";
2412
2525
  const evaluationMode = getEvaluationSuiteMode(suite);
@@ -2416,6 +2529,110 @@ async function runEvaluationSuite(options) {
2416
2529
  );
2417
2530
  }
2418
2531
  validateSuite(options.project, suite, options.dataset, purpose);
2532
+ const trialCount = Math.max(1, Math.floor(((_a = suite.configuration) == null ? void 0 : _a.trialCount) ?? 1));
2533
+ const work = options.dataset.cases.filter((testCase) => testCase.enabled !== false).flatMap(
2534
+ (testCase, caseIndex) => Array.from({ length: trialCount }, (_, trialIndex) => ({ testCase, caseIndex, trialIndex }))
2535
+ );
2536
+ return { suite, purpose, evaluationMode, work };
2537
+ }
2538
+ function finalizeEvaluationRun(options) {
2539
+ var _a, _b;
2540
+ const run = options.run;
2541
+ const evaluationMode = getEvaluationSuiteMode(options.suite);
2542
+ run.trials = options.trials.slice();
2543
+ run.completedAt = options.completedAt ?? (/* @__PURE__ */ new Date()).toISOString();
2544
+ const outcome = aggregate(run.trials, evaluationMode);
2545
+ run.aggregate = outcome.aggregate;
2546
+ run.provenance.accountingComplete = !run.trials.some((trial) => trial.totalMetrics.hasUnknownCost);
2547
+ run.accountingStatus = run.provenance.accountingComplete ? "complete" : "partial";
2548
+ if (run.accountingStatus === "partial" && !run.warnings.includes("Some provider pricing was unavailable. Cost totals are unavailable, and cost requirements cannot be evaluated.")) {
2549
+ run.warnings.push("Some provider pricing was unavailable. Cost totals are unavailable, and cost requirements cannot be evaluated.");
2550
+ }
2551
+ if (options.canceled) {
2552
+ run.executionStatus = "canceled";
2553
+ run.qualityStatus = "not-evaluated";
2554
+ run.qualityReason = { code: "canceled", message: "The evaluation run was canceled." };
2555
+ return run;
2556
+ }
2557
+ if (run.purpose === "execution-benchmark") {
2558
+ run.executionStatus = "completed";
2559
+ run.qualityStatus = "not-evaluated";
2560
+ run.qualityReason = { code: "benchmark", message: "This run measured execution without evaluating output quality." };
2561
+ return run;
2562
+ }
2563
+ if (evaluationMode === "scoring") {
2564
+ run.executionStatus = "completed";
2565
+ if ((outcome.aggregate.missingScoreTrialCount ?? run.trials.length) > 0) {
2566
+ run.qualityStatus = "unable-to-evaluate";
2567
+ run.qualityReason = { code: "scores-incomplete", message: "One or more requested trials did not produce a usable score. Available averages are shown with coverage." };
2568
+ run.warnings.push(`Score coverage is ${outcome.aggregate.scoredTrialCount ?? 0} of ${outcome.aggregate.trialCount} requested trials.`);
2569
+ } else if ((outcome.aggregate.scoredTrialCount ?? 0) > 0) {
2570
+ run.qualityStatus = "scored";
2571
+ run.qualityReason = { code: "scores-complete", message: "Every requested trial produced a score. Overall score is the equal-weight average of case averages." };
2572
+ } else {
2573
+ run.qualityStatus = "unable-to-evaluate";
2574
+ run.qualityReason = { code: "scores-incomplete", message: "No requested trial produced a usable score." };
2575
+ }
2576
+ return run;
2577
+ }
2578
+ const baseline = options.baseline ?? options.evaluationData.baselines.find((candidate) => candidate.suiteId === options.suite.id);
2579
+ const usableBaseline = baseline && baselineCompatible(run.provenance, baseline) ? baseline : void 0;
2580
+ if (baseline && !usableBaseline && !run.warnings.includes("The suite baseline is stale because its target, dataset, bindings, or evaluator definition changed.")) {
2581
+ run.warnings.push("The suite baseline is stale because its target, dataset, bindings, or evaluator definition changed.");
2582
+ }
2583
+ run.thresholdResults = evaluateThresholdResults(outcome.aggregate, options.suite.thresholds, usableBaseline);
2584
+ const hasPassRateThreshold = ((_a = options.suite.thresholds) == null ? void 0 : _a.some((threshold) => threshold.metric === "pass-rate")) ?? false;
2585
+ const hasTargetErrorRateThreshold = ((_b = options.suite.thresholds) == null ? void 0 : _b.some((threshold) => threshold.metric === "target-error-rate")) ?? false;
2586
+ const hasUngovernedCheckFailure = !hasPassRateThreshold && run.trials.some((trial) => trial.executionStatus === "completed" && trial.qualityStatus === "failed");
2587
+ const hasUngovernedTargetError = !hasTargetErrorRateThreshold && run.trials.some((trial) => trial.executionStatus === "error");
2588
+ const hasThresholdFailure = run.thresholdResults.some((result) => result.status === "failed");
2589
+ const hasUnavailableEvidence = run.trials.some((trial) => trial.qualityStatus === "unable-to-evaluate") || run.thresholdResults.some((result) => result.status === "unavailable");
2590
+ const hasAuthoritativePass = run.trials.some((trial) => trial.executionStatus === "completed" && trial.qualityStatus === "passed") || run.thresholdResults.some((result) => result.status === "passed");
2591
+ run.executionStatus = "completed";
2592
+ if (hasUngovernedCheckFailure || hasUngovernedTargetError || hasThresholdFailure) {
2593
+ run.qualityStatus = "failed";
2594
+ run.qualityReason = { code: hasThresholdFailure ? "thresholds-failed" : "checks-failed", message: hasThresholdFailure ? "One or more required aggregate thresholds failed." : "One or more target executions or required quality checks failed." };
2595
+ } else if (hasUnavailableEvidence) {
2596
+ run.qualityStatus = "unable-to-evaluate";
2597
+ run.qualityReason = { code: run.thresholdResults.some((result) => result.status === "unavailable") ? "required-metric-unavailable" : "required-check-error", message: run.thresholdResults.some((result) => result.status === "unavailable") ? "A required aggregate metric could not be evaluated." : "A required quality check could not be evaluated." };
2598
+ } else if (hasAuthoritativePass) {
2599
+ run.qualityStatus = "passed";
2600
+ run.qualityReason = { code: run.thresholdResults.length > 0 ? "thresholds-passed" : "checks-passed", message: run.thresholdResults.length > 0 ? "All required quality checks and aggregate thresholds passed." : "All required quality checks passed." };
2601
+ } else {
2602
+ run.qualityStatus = "not-evaluated";
2603
+ run.qualityReason = { code: "no-completed-trials", message: "No completed trial produced an authoritative quality result." };
2604
+ }
2605
+ return run;
2606
+ }
2607
+ function createEvaluationRunShell(options) {
2608
+ const plan = createEvaluationRunPlan(options);
2609
+ const runId = options.runId ?? makeId("evaluation");
2610
+ const run = {
2611
+ version: 2,
2612
+ id: runId,
2613
+ projectId: options.project.metadata.id,
2614
+ suiteId: plan.suite.id,
2615
+ suiteName: plan.suite.name,
2616
+ revision: 0,
2617
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
2618
+ purpose: plan.purpose,
2619
+ evaluationMode: plan.evaluationMode,
2620
+ executionStatus: "running",
2621
+ qualityStatus: "not-evaluated",
2622
+ qualityReason: { code: "in-progress", message: "The run is still in progress." },
2623
+ accountingStatus: "complete",
2624
+ provenance: createProvenance(options.project, plan.suite, options.dataset, options),
2625
+ trials: [],
2626
+ thresholdResults: [],
2627
+ warnings: [],
2628
+ requestedTrialCount: plan.work.length
2629
+ };
2630
+ return { run, plan };
2631
+ }
2632
+ async function runEvaluationSuite(options) {
2633
+ var _a, _b;
2634
+ const plan = createEvaluationRunPlan(options);
2635
+ const { suite, purpose, evaluationMode } = plan;
2419
2636
  const runId = options.runId ?? makeId("evaluation");
2420
2637
  const run = {
2421
2638
  version: 2,
@@ -2436,27 +2653,61 @@ async function runEvaluationSuite(options) {
2436
2653
  thresholdResults: [],
2437
2654
  warnings: []
2438
2655
  };
2439
- const trialCount = Math.max(1, Math.floor(((_a = suite.configuration) == null ? void 0 : _a.trialCount) ?? 1));
2440
2656
  const concurrency = Math.min(
2441
2657
  MAX_CONCURRENCY,
2442
- Math.max(1, Math.floor(((_b = suite.configuration) == null ? void 0 : _b.concurrency) ?? DEFAULT_CONCURRENCY))
2658
+ Math.max(1, Math.floor(((_a = suite.configuration) == null ? void 0 : _a.concurrency) ?? DEFAULT_CONCURRENCY))
2443
2659
  );
2444
- const work = options.dataset.cases.filter((testCase) => testCase.enabled !== false).flatMap(
2445
- (testCase, caseIndex) => Array.from({ length: trialCount }, (_, trialIndex) => ({ testCase, caseIndex, trialIndex }))
2446
- );
2447
- const publish = () => {
2448
- var _a2;
2660
+ const work = plan.work;
2661
+ run.requestedTrialCount = work.length;
2662
+ const settledTrials = Array.from({ length: work.length });
2663
+ let settledTrialCount = 0;
2664
+ const nextRevision = () => {
2449
2665
  run.revision = (run.revision ?? 0) + 1;
2450
- (_a2 = options.onUpdate) == null ? void 0 : _a2.call(options, structuredClone(run));
2666
+ return run.revision;
2667
+ };
2668
+ const publishLegacySnapshot = () => {
2669
+ if (!options.onUpdate) return;
2670
+ run.trials = settledTrials.filter((trial) => trial !== void 0);
2671
+ options.onUpdate(structuredClone(run));
2672
+ };
2673
+ const publishStarted = async () => {
2674
+ const revision = nextRevision();
2675
+ if (options.onEvent) {
2676
+ const shell = structuredClone(run);
2677
+ shell.trials = [];
2678
+ await options.onEvent({ type: "run-started", revision, run: shell });
2679
+ }
2680
+ publishLegacySnapshot();
2681
+ };
2682
+ const publishSettledTrial = async (trial) => {
2683
+ var _a2;
2684
+ const revision = nextRevision();
2685
+ await ((_a2 = options.onEvent) == null ? void 0 : _a2.call(options, {
2686
+ type: "trial-settled",
2687
+ revision,
2688
+ runId: run.id,
2689
+ projectId: run.projectId,
2690
+ suiteId: run.suiteId,
2691
+ requestedTrialCount: run.requestedTrialCount ?? work.length,
2692
+ settledTrialCount,
2693
+ trial: structuredClone(trial)
2694
+ }));
2695
+ publishLegacySnapshot();
2451
2696
  };
2452
- publish();
2697
+ const publishFinalized = async () => {
2698
+ var _a2;
2699
+ const revision = nextRevision();
2700
+ await ((_a2 = options.onEvent) == null ? void 0 : _a2.call(options, { type: "run-finalized", revision, run: structuredClone(run) }));
2701
+ publishLegacySnapshot();
2702
+ };
2703
+ await publishStarted();
2453
2704
  const workPool = runEvaluationWorkPool({
2454
2705
  work,
2455
2706
  concurrency,
2456
2707
  signal: options.signal,
2457
2708
  execute: async (task) => {
2458
2709
  try {
2459
- return await runTrial({
2710
+ return await runEvaluationTrial({
2460
2711
  ...task,
2461
2712
  project: options.project,
2462
2713
  suite,
@@ -2489,98 +2740,22 @@ async function runEvaluationSuite(options) {
2489
2740
  };
2490
2741
  }
2491
2742
  },
2492
- onSettled: (_result, _index, currentResults) => {
2493
- run.trials = currentResults.filter((result) => result !== void 0);
2494
- publish();
2743
+ onSettled: async (result, index) => {
2744
+ settledTrials[index] = result;
2745
+ settledTrialCount += 1;
2746
+ await publishSettledTrial(result);
2495
2747
  }
2496
2748
  });
2497
2749
  const results = await workPool;
2498
- run.trials = results.filter((result) => result !== void 0);
2499
- run.completedAt = (/* @__PURE__ */ new Date()).toISOString();
2500
- const outcome = aggregate(run.trials, evaluationMode);
2501
- run.aggregate = outcome.aggregate;
2502
- run.provenance.accountingComplete = !run.trials.some((trial) => trial.totalMetrics.hasUnknownCost);
2503
- run.accountingStatus = run.provenance.accountingComplete ? "complete" : "partial";
2504
- if (run.accountingStatus === "partial") {
2505
- run.warnings.push(
2506
- "Some provider pricing was unavailable. Cost totals are unavailable, and cost requirements cannot be evaluated."
2507
- );
2508
- }
2509
- if ((_c = options.signal) == null ? void 0 : _c.aborted) {
2510
- run.executionStatus = "canceled";
2511
- run.qualityStatus = "not-evaluated";
2512
- run.qualityReason = { code: "canceled", message: "The evaluation run was canceled." };
2513
- } else if (purpose === "execution-benchmark") {
2514
- run.executionStatus = "completed";
2515
- run.qualityStatus = "not-evaluated";
2516
- run.qualityReason = {
2517
- code: "benchmark",
2518
- message: "This run measured execution without evaluating output quality."
2519
- };
2520
- } else if (evaluationMode === "scoring") {
2521
- run.executionStatus = "completed";
2522
- if ((outcome.aggregate.missingScoreTrialCount ?? run.trials.length) > 0) {
2523
- run.qualityStatus = "unable-to-evaluate";
2524
- run.qualityReason = {
2525
- code: "scores-incomplete",
2526
- message: "One or more requested trials did not produce a usable score. Available averages are shown with coverage."
2527
- };
2528
- run.warnings.push(
2529
- `Score coverage is ${outcome.aggregate.scoredTrialCount ?? 0} of ${outcome.aggregate.trialCount} requested trials.`
2530
- );
2531
- } else if ((outcome.aggregate.scoredTrialCount ?? 0) > 0) {
2532
- run.qualityStatus = "scored";
2533
- run.qualityReason = {
2534
- code: "scores-complete",
2535
- message: "Every requested trial produced a score. Overall score is the equal-weight average of case averages."
2536
- };
2537
- } else {
2538
- run.qualityStatus = "unable-to-evaluate";
2539
- run.qualityReason = { code: "scores-incomplete", message: "No requested trial produced a usable score." };
2540
- }
2541
- } else {
2542
- const baseline = options.baseline ?? options.evaluationData.baselines.find((candidate) => candidate.suiteId === suite.id);
2543
- const usableBaseline = baseline && baselineCompatible(run.provenance, baseline) ? baseline : void 0;
2544
- if (baseline && !usableBaseline)
2545
- run.warnings.push(
2546
- "The suite baseline is stale because its target, dataset, bindings, or evaluator definition changed."
2547
- );
2548
- run.thresholdResults = evaluateThresholdResults(outcome.aggregate, suite.thresholds, usableBaseline);
2549
- const hasPassRateThreshold = ((_d = suite.thresholds) == null ? void 0 : _d.some((threshold) => threshold.metric === "pass-rate")) ?? false;
2550
- const hasTargetErrorRateThreshold = ((_e = suite.thresholds) == null ? void 0 : _e.some((threshold) => threshold.metric === "target-error-rate")) ?? false;
2551
- const hasUngovernedCheckFailure = !hasPassRateThreshold && run.trials.some((trial) => trial.executionStatus === "completed" && trial.qualityStatus === "failed");
2552
- const hasUngovernedTargetError = !hasTargetErrorRateThreshold && run.trials.some((trial) => trial.executionStatus === "error");
2553
- const hasThresholdFailure = run.thresholdResults.some((result) => result.status === "failed");
2554
- const hasUnavailableEvidence = run.trials.some((trial) => trial.qualityStatus === "unable-to-evaluate") || run.thresholdResults.some((result) => result.status === "unavailable");
2555
- const hasAuthoritativePass = run.trials.some((trial) => trial.executionStatus === "completed" && trial.qualityStatus === "passed") || run.thresholdResults.some((result) => result.status === "passed");
2556
- if (hasUngovernedCheckFailure || hasUngovernedTargetError || hasThresholdFailure) {
2557
- run.qualityStatus = "failed";
2558
- run.qualityReason = {
2559
- code: hasThresholdFailure ? "thresholds-failed" : "checks-failed",
2560
- message: hasThresholdFailure ? "One or more required aggregate thresholds failed." : "One or more target executions or required quality checks failed."
2561
- };
2562
- } else if (hasUnavailableEvidence) {
2563
- run.qualityStatus = "unable-to-evaluate";
2564
- run.qualityReason = {
2565
- code: run.thresholdResults.some((result) => result.status === "unavailable") ? "required-metric-unavailable" : "required-check-error",
2566
- message: run.thresholdResults.some((result) => result.status === "unavailable") ? "A required aggregate metric could not be evaluated." : "A required quality check could not be evaluated."
2567
- };
2568
- } else if (hasAuthoritativePass) {
2569
- run.qualityStatus = "passed";
2570
- run.qualityReason = {
2571
- code: run.thresholdResults.length > 0 ? "thresholds-passed" : "checks-passed",
2572
- message: run.thresholdResults.length > 0 ? "All required quality checks and aggregate thresholds passed." : "All required quality checks passed."
2573
- };
2574
- } else {
2575
- run.qualityStatus = "not-evaluated";
2576
- run.qualityReason = {
2577
- code: "no-completed-trials",
2578
- message: "No completed trial produced an authoritative quality result."
2579
- };
2580
- }
2581
- run.executionStatus = "completed";
2582
- }
2583
- publish();
2750
+ finalizeEvaluationRun({
2751
+ run,
2752
+ trials: results.filter((result) => result !== void 0),
2753
+ suite,
2754
+ evaluationData: options.evaluationData,
2755
+ baseline: options.baseline,
2756
+ canceled: ((_b = options.signal) == null ? void 0 : _b.aborted) === true
2757
+ });
2758
+ await publishFinalized();
2584
2759
  return run;
2585
2760
  }
2586
2761
  async function runEvaluationCases(options) {
@@ -2619,93 +2794,131 @@ function createEvaluationBaselineSnapshot(run) {
2619
2794
  };
2620
2795
  }
2621
2796
 
2622
- // src/store.ts
2623
- var InMemoryEvaluationRunStore = class {
2624
- #runs = /* @__PURE__ */ new Map();
2625
- #datasetSnapshots = /* @__PURE__ */ new Map();
2626
- #recordings = /* @__PURE__ */ new Map();
2627
- async put(run) {
2628
- const normalized = normalizeEvaluationRun(run);
2629
- const key = `${normalized.projectId}/${normalized.id}`;
2630
- const existing = this.#runs.get(key);
2631
- if (!shouldReplaceEvaluationRun(existing, normalized)) return;
2632
- this.#runs.set(key, structuredClone(normalized));
2633
- }
2634
- async get(input) {
2635
- const run = this.#runs.get(`${input.projectId}/${input.runId}`);
2636
- return run ? normalizeEvaluationRun(run) : void 0;
2637
- }
2638
- async list(input) {
2639
- return Array.from(this.#runs.values()).filter(
2640
- (run) => run.projectId === input.projectId && (input.suiteId === void 0 || run.suiteId === input.suiteId)
2641
- ).sort((left, right) => right.startedAt.localeCompare(left.startedAt)).map(normalizeEvaluationRun);
2642
- }
2643
- async delete(input) {
2644
- this.#runs.delete(`${input.projectId}/${input.runId}`);
2645
- for (const [key, artifact] of this.#recordings) {
2646
- if (artifact.projectId === input.projectId && artifact.runId === input.runId) this.#recordings.delete(key);
2797
+ // src/datasetTransfer.ts
2798
+ var DATASET_EXPORT_VERSION = 1;
2799
+ function isRecord2(value) {
2800
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2801
+ }
2802
+ function requireString(value, path) {
2803
+ if (typeof value !== "string" || value.length === 0) throw new Error(`${path} must be a non-empty string.`);
2804
+ return value;
2805
+ }
2806
+ function requireText(value, path) {
2807
+ if (typeof value !== "string") throw new Error(`${path} must be a string.`);
2808
+ return value;
2809
+ }
2810
+ function parseFields(value) {
2811
+ if (!Array.isArray(value)) throw new Error("Evaluation dataset fields must be an array.");
2812
+ const ids = /* @__PURE__ */ new Set();
2813
+ return value.map((field, index) => {
2814
+ if (!isRecord2(field)) throw new Error(`fields[${index}] must be an object.`);
2815
+ const id = requireString(field.id, `fields[${index}].id`);
2816
+ if (ids.has(id)) throw new Error(`Evaluation dataset has duplicate field id "${id}".`);
2817
+ ids.add(id);
2818
+ const role = field.role;
2819
+ if (role !== "input" && role !== "expected" && role !== "metadata") {
2820
+ throw new Error(`fields[${index}].role must be input, expected, or metadata.`);
2647
2821
  }
2648
- }
2649
- async putDatasetSnapshot(snapshot) {
2650
- assertEvaluationDatasetSnapshot(snapshot);
2651
- const key = `${snapshot.projectId}/${snapshot.fingerprint}`;
2652
- if (this.#datasetSnapshots.has(key)) return;
2653
- this.#datasetSnapshots.set(key, structuredClone(snapshot));
2654
- }
2655
- async getDatasetSnapshot(input) {
2656
- const snapshot = this.#datasetSnapshots.get(`${input.projectId}/${input.fingerprint}`);
2657
- return snapshot ? structuredClone(snapshot) : void 0;
2658
- }
2659
- async putRecording(artifact) {
2660
- const key = `${artifact.projectId}/${artifact.reference.id}`;
2661
- const existing = this.#recordings.get(key);
2662
- if (existing && (existing.runId !== artifact.runId || existing.trialId !== artifact.trialId)) {
2663
- throw new Error("An evaluation recording ID cannot be reassigned to another run or trial.");
2822
+ if (field.description !== void 0 && typeof field.description !== "string") {
2823
+ throw new Error(`fields[${index}].description must be a string.`);
2664
2824
  }
2665
- const next = structuredClone(artifact);
2666
- if (existing) next.reference = structuredClone(existing.reference);
2667
- this.#recordings.set(key, next);
2668
- }
2669
- async getRecording(input) {
2670
- const artifact = this.#recordings.get(`${input.projectId}/${input.recordingId}`);
2671
- if (!artifact) return void 0;
2672
- if (artifact.reference.retention === "temporary" && artifact.reference.expiresAt && Date.parse(artifact.reference.expiresAt) <= Date.now()) {
2673
- this.#recordings.delete(`${input.projectId}/${input.recordingId}`);
2674
- return void 0;
2825
+ if (field.required !== void 0 && typeof field.required !== "boolean") {
2826
+ throw new Error(`fields[${index}].required must be a boolean.`);
2675
2827
  }
2676
- return structuredClone(artifact);
2677
- }
2678
- async updateRecordingRetention(input) {
2679
- const key = `${input.projectId}/${input.recordingId}`;
2680
- const artifact = this.#recordings.get(key);
2681
- if (!artifact) return;
2682
- artifact.reference = {
2683
- id: artifact.reference.id,
2684
- retention: input.retention,
2685
- ...input.expiresAt === void 0 ? {} : { expiresAt: input.expiresAt }
2828
+ return {
2829
+ id,
2830
+ name: requireText(field.name, `fields[${index}].name`),
2831
+ dataType: requireString(field.dataType, `fields[${index}].dataType`),
2832
+ role,
2833
+ ...typeof field.description === "string" ? { description: field.description } : {},
2834
+ ...typeof field.required === "boolean" ? { required: field.required } : {}
2686
2835
  };
2687
- }
2688
- async promoteBaseline(input) {
2689
- for (const artifact of this.#recordings.values()) {
2690
- if (artifact.projectId !== input.projectId || artifact.runId !== input.runId) continue;
2691
- artifact.reference = { id: artifact.reference.id, retention: "baseline" };
2836
+ });
2837
+ }
2838
+ function parseCases(value, fieldIds) {
2839
+ if (!Array.isArray(value)) throw new Error("Evaluation dataset cases must be an array.");
2840
+ const ids = /* @__PURE__ */ new Set();
2841
+ return value.map((testCase, index) => {
2842
+ if (!isRecord2(testCase)) throw new Error(`cases[${index}] must be an object.`);
2843
+ const id = requireString(testCase.id, `cases[${index}].id`);
2844
+ if (ids.has(id)) throw new Error(`Evaluation dataset has duplicate case id "${id}".`);
2845
+ ids.add(id);
2846
+ if (!isRecord2(testCase.values)) throw new Error(`cases[${index}].values must be an object.`);
2847
+ for (const [fieldId, fieldValue] of Object.entries(testCase.values)) {
2848
+ if (!fieldIds.has(fieldId)) throw new Error(`cases[${index}].values references unknown field "${fieldId}".`);
2849
+ assertPortableJson(fieldValue, `cases[${index}].values.${fieldId}`);
2850
+ }
2851
+ if (testCase.tags !== void 0 && (!Array.isArray(testCase.tags) || testCase.tags.some((tag) => typeof tag !== "string"))) {
2852
+ throw new Error(`cases[${index}].tags must be an array of strings.`);
2692
2853
  }
2854
+ if (testCase.note !== void 0 && typeof testCase.note !== "string") throw new Error(`cases[${index}].note must be a string.`);
2855
+ if (testCase.enabled !== void 0 && typeof testCase.enabled !== "boolean") throw new Error(`cases[${index}].enabled must be a boolean.`);
2856
+ return {
2857
+ id,
2858
+ name: requireText(testCase.name, `cases[${index}].name`),
2859
+ values: testCase.values,
2860
+ ...typeof testCase.enabled === "boolean" ? { enabled: testCase.enabled } : {},
2861
+ ...Array.isArray(testCase.tags) ? { tags: testCase.tags } : {},
2862
+ ...typeof testCase.note === "string" ? { note: testCase.note } : {}
2863
+ };
2864
+ });
2865
+ }
2866
+ function validateEvaluationDataset(value, scope) {
2867
+ if (!isRecord2(value)) throw new Error("Evaluation dataset must be an object.");
2868
+ const fields = parseFields(value.fields);
2869
+ const importedProjectId = value.projectId === void 0 ? void 0 : requireString(value.projectId, "dataset.projectId");
2870
+ const projectId = (scope == null ? void 0 : scope.projectId) ?? importedProjectId;
2871
+ const dataset = {
2872
+ id: (scope == null ? void 0 : scope.id) ?? requireString(value.id, "dataset.id"),
2873
+ name: requireText(value.name, "dataset.name"),
2874
+ ...projectId === void 0 ? {} : { projectId },
2875
+ fields,
2876
+ cases: parseCases(value.cases, new Set(fields.map((field) => field.id)))
2877
+ };
2878
+ return { ...dataset, contentFingerprint: fingerprintEvaluationDataset(dataset) };
2879
+ }
2880
+ function localizeEvaluationDataset(value, id) {
2881
+ const dataset = validateEvaluationDataset(value, id === void 0 ? void 0 : { id });
2882
+ const { projectId: _legacyProjectId, ...localDataset } = dataset;
2883
+ return { ...localDataset, contentFingerprint: fingerprintEvaluationDataset(localDataset) };
2884
+ }
2885
+ function validateEvaluationDatasetForTransfer(value, scope) {
2886
+ const dataset = validateEvaluationDataset(value, scope);
2887
+ assertEvaluationDatasetValuesMatchDeclaredTypes(dataset);
2888
+ return dataset;
2889
+ }
2890
+ function serializeEvaluationDatasetJson(dataset) {
2891
+ const validated = validateEvaluationDatasetForTransfer(dataset);
2892
+ return JSON.stringify({ version: DATASET_EXPORT_VERSION, dataset: validated }, null, 2);
2893
+ }
2894
+ function deserializeEvaluationDatasetJson(source, scope) {
2895
+ let parsed;
2896
+ try {
2897
+ parsed = JSON.parse(source);
2898
+ } catch {
2899
+ throw new Error("Evaluation dataset JSON is not valid JSON.");
2693
2900
  }
2694
- };
2901
+ if (!isRecord2(parsed) || parsed.version !== DATASET_EXPORT_VERSION || !isRecord2(parsed.dataset)) {
2902
+ throw new Error(`Expected an evaluation dataset export with version ${DATASET_EXPORT_VERSION}.`);
2903
+ }
2904
+ const dataset = validateEvaluationDatasetForTransfer(parsed.dataset, { id: scope.id });
2905
+ const { projectId: _legacyProjectId, ...localDataset } = dataset;
2906
+ return { ...localDataset, contentFingerprint: fingerprintEvaluationDataset(localDataset) };
2907
+ }
2695
2908
 
2696
2909
  // src/serialization.ts
2697
2910
  var yaml = __toESM(require("yaml"), 1);
2698
- function isRecord2(value) {
2911
+ function isRecord3(value) {
2699
2912
  return value !== null && typeof value === "object" && !Array.isArray(value);
2700
2913
  }
2701
2914
  function requireRecord(value, path) {
2702
- if (!isRecord2(value)) throw new Error(`${path} must be an object.`);
2915
+ if (!isRecord3(value)) throw new Error(`${path} must be an object.`);
2703
2916
  return value;
2704
2917
  }
2705
- function requireString(value, path) {
2918
+ function requireString2(value, path) {
2706
2919
  if (typeof value !== "string" || value.trim().length === 0) throw new Error(`${path} must be a non-empty string.`);
2707
2920
  }
2708
- function requireText(value, path) {
2921
+ function requireText2(value, path) {
2709
2922
  if (typeof value !== "string") throw new Error(`${path} must be a string.`);
2710
2923
  }
2711
2924
  function requireBoolean(value, path) {
@@ -2747,21 +2960,21 @@ var EVALUATOR_CONTEXT_INPUTS = /* @__PURE__ */ new Set(["case", "inputs", "expec
2747
2960
  function validateSuiteShape(value, index) {
2748
2961
  const path = `evaluations.suites[${index}]`;
2749
2962
  const suite = requireRecord(value, path);
2750
- for (const key of ["id", "targetGraphId", "datasetId"]) requireString(suite[key], `${path}.${key}`);
2751
- requireText(suite.name, `${path}.name`);
2963
+ for (const key of ["id", "targetGraphId", "datasetId"]) requireString2(suite[key], `${path}.${key}`);
2964
+ requireText2(suite.name, `${path}.name`);
2752
2965
  if (suite.evaluationMode !== void 0 && suite.evaluationMode !== "pass-fail" && suite.evaluationMode !== "scoring") {
2753
2966
  throw new Error(`${path}.evaluationMode must be "pass-fail" or "scoring".`);
2754
2967
  }
2755
2968
  for (const [bindingIndex, bindingValue] of requireArray(suite.inputBindings, `${path}.inputBindings`).entries()) {
2756
2969
  const binding = requireRecord(bindingValue, `${path}.inputBindings[${bindingIndex}]`);
2757
- requireString(binding.graphInputId, `${path}.inputBindings[${bindingIndex}].graphInputId`);
2758
- requireString(binding.datasetFieldId, `${path}.inputBindings[${bindingIndex}].datasetFieldId`);
2970
+ requireString2(binding.graphInputId, `${path}.inputBindings[${bindingIndex}].graphInputId`);
2971
+ requireString2(binding.datasetFieldId, `${path}.inputBindings[${bindingIndex}].datasetFieldId`);
2759
2972
  }
2760
2973
  for (const [assertionIndex, assertionValue] of requireArray(suite.assertions, `${path}.assertions`).entries()) {
2761
2974
  const assertionPath = `${path}.assertions[${assertionIndex}]`;
2762
2975
  const assertion = requireRecord(assertionValue, assertionPath);
2763
- for (const key of ["id", "outputPath"]) requireString(assertion[key], `${assertionPath}.${key}`);
2764
- requireText(assertion.name, `${assertionPath}.name`);
2976
+ for (const key of ["id", "outputPath"]) requireString2(assertion[key], `${assertionPath}.${key}`);
2977
+ requireText2(assertion.name, `${assertionPath}.name`);
2765
2978
  if (typeof assertion.operator !== "string" || !ASSERTION_OPERATORS.has(assertion.operator)) {
2766
2979
  throw new Error(`${assertionPath}.operator is not supported.`);
2767
2980
  }
@@ -2770,7 +2983,7 @@ function validateSuiteShape(value, index) {
2770
2983
  if (expected.kind === "literal") {
2771
2984
  if (!Object.hasOwn(expected, "value")) throw new Error(`${assertionPath}.expected.value is required.`);
2772
2985
  } else if (expected.kind === "dataset-field") {
2773
- requireString(expected.fieldId, `${assertionPath}.expected.fieldId`);
2986
+ requireString2(expected.fieldId, `${assertionPath}.expected.fieldId`);
2774
2987
  } else {
2775
2988
  throw new Error(`${assertionPath}.expected.kind must be "literal" or "dataset-field".`);
2776
2989
  }
@@ -2778,8 +2991,8 @@ function validateSuiteShape(value, index) {
2778
2991
  for (const [evaluatorIndex, evaluatorValue] of requireArray(suite.evaluators, `${path}.evaluators`).entries()) {
2779
2992
  const evaluatorPath = `${path}.evaluators[${evaluatorIndex}]`;
2780
2993
  const evaluator = requireRecord(evaluatorValue, evaluatorPath);
2781
- for (const key of ["id", "graphId"]) requireString(evaluator[key], `${evaluatorPath}.${key}`);
2782
- requireText(evaluator.name, `${evaluatorPath}.name`);
2994
+ for (const key of ["id", "graphId"]) requireString2(evaluator[key], `${evaluatorPath}.${key}`);
2995
+ requireText2(evaluator.name, `${evaluatorPath}.name`);
2783
2996
  validateOptionalPrimitive(evaluator, "required", "boolean", evaluatorPath);
2784
2997
  validateOptionalPrimitive(evaluator, "runOnTargetError", "boolean", evaluatorPath);
2785
2998
  validateOptionalPrimitive(evaluator, "scoreWeight", "number", evaluatorPath);
@@ -2790,10 +3003,10 @@ function validateSuiteShape(value, index) {
2790
3003
  ).entries()) {
2791
3004
  const bindingPath = `${evaluatorPath}.inputBindings[${bindingIndex}]`;
2792
3005
  const binding = requireRecord(bindingValue, bindingPath);
2793
- requireString(binding.graphInputId, `${bindingPath}.graphInputId`);
3006
+ requireString2(binding.graphInputId, `${bindingPath}.graphInputId`);
2794
3007
  const source = requireRecord(binding.source, `${bindingPath}.source`);
2795
- if (source.kind === "dataset-field") requireString(source.fieldId, `${bindingPath}.source.fieldId`);
2796
- else if (source.kind === "target-output") requireString(source.outputId, `${bindingPath}.source.outputId`);
3008
+ if (source.kind === "dataset-field") requireString2(source.fieldId, `${bindingPath}.source.fieldId`);
3009
+ else if (source.kind === "target-output") requireString2(source.outputId, `${bindingPath}.source.outputId`);
2797
3010
  else if (source.kind === "context") {
2798
3011
  if (typeof source.context !== "string" || !EVALUATOR_CONTEXT_INPUTS.has(source.context)) {
2799
3012
  throw new Error(`${bindingPath}.source.context is not supported.`);
@@ -2808,7 +3021,7 @@ function validateSuiteShape(value, index) {
2808
3021
  for (const [thresholdIndex, thresholdValue2] of requireArray(suite.thresholds, `${path}.thresholds`).entries()) {
2809
3022
  const thresholdPath = `${path}.thresholds[${thresholdIndex}]`;
2810
3023
  const threshold = requireRecord(thresholdValue2, thresholdPath);
2811
- for (const key of ["id", "metric", "operator"]) requireString(threshold[key], `${thresholdPath}.${key}`);
3024
+ for (const key of ["id", "metric", "operator"]) requireString2(threshold[key], `${thresholdPath}.${key}`);
2812
3025
  requireFiniteNumber(threshold.value, `${thresholdPath}.value`);
2813
3026
  }
2814
3027
  }
@@ -2823,7 +3036,7 @@ function validateSuiteShape(value, index) {
2823
3036
  validateOptionalPrimitive(suite, "description", "string", path);
2824
3037
  if (suite.tags !== void 0) {
2825
3038
  for (const [tagIndex, tag] of requireArray(suite.tags, `${path}.tags`).entries()) {
2826
- requireString(tag, `${path}.tags[${tagIndex}]`);
3039
+ requireString2(tag, `${path}.tags[${tagIndex}]`);
2827
3040
  }
2828
3041
  }
2829
3042
  }
@@ -2834,7 +3047,7 @@ function validateNumericRecord(value, path) {
2834
3047
  function validateBaselineShape(value, index) {
2835
3048
  const path = `evaluations.baselines[${index}]`;
2836
3049
  const baseline = requireRecord(value, path);
2837
- for (const key of ["id", "suiteId", "createdAt"]) requireString(baseline[key], `${path}.${key}`);
3050
+ for (const key of ["id", "suiteId", "createdAt"]) requireString2(baseline[key], `${path}.${key}`);
2838
3051
  validateOptionalPrimitive(baseline, "sourceRunId", "string", path);
2839
3052
  if (baseline.evaluationMode !== void 0 && baseline.evaluationMode !== "pass-fail" && baseline.evaluationMode !== "scoring") {
2840
3053
  throw new Error(`${path}.evaluationMode must be "pass-fail" or "scoring".`);
@@ -2847,14 +3060,14 @@ function validateBaselineShape(value, index) {
2847
3060
  "targetFingerprint",
2848
3061
  "executionMode"
2849
3062
  ]) {
2850
- requireString(provenance[key], `${path}.provenance.${key}`);
3063
+ requireString2(provenance[key], `${path}.provenance.${key}`);
2851
3064
  }
2852
3065
  const evaluatorFingerprints = requireRecord(
2853
3066
  provenance.evaluatorFingerprints,
2854
3067
  `${path}.provenance.evaluatorFingerprints`
2855
3068
  );
2856
3069
  for (const [key, fingerprint2] of Object.entries(evaluatorFingerprints)) {
2857
- requireString(fingerprint2, `${path}.provenance.evaluatorFingerprints.${key}`);
3070
+ requireString2(fingerprint2, `${path}.provenance.evaluatorFingerprints.${key}`);
2858
3071
  }
2859
3072
  requireBoolean(provenance.accountingComplete, `${path}.provenance.accountingComplete`);
2860
3073
  const aggregate2 = requireRecord(baseline.aggregate, `${path}.aggregate`);
@@ -2878,6 +3091,9 @@ function validateBaselineShape(value, index) {
2878
3091
  "notEvaluatedTrialCount",
2879
3092
  "unableToEvaluateTrialCount",
2880
3093
  "meanScore",
3094
+ "medianScore",
3095
+ "p95Score",
3096
+ "medianLatencyMs",
2881
3097
  "totalCostUsd",
2882
3098
  "averageCostUsd",
2883
3099
  "scoredTrialCount",
@@ -2889,8 +3105,8 @@ function validateBaselineShape(value, index) {
2889
3105
  for (const [caseIndex, caseValue] of requireArray(baseline.cases, `${path}.cases`).entries()) {
2890
3106
  const casePath = `${path}.cases[${caseIndex}]`;
2891
3107
  const caseAggregate = requireRecord(caseValue, casePath);
2892
- requireString(caseAggregate.caseId, `${casePath}.caseId`);
2893
- requireString(caseAggregate.caseName, `${casePath}.caseName`);
3108
+ requireString2(caseAggregate.caseId, `${casePath}.caseId`);
3109
+ requireString2(caseAggregate.caseName, `${casePath}.caseName`);
2894
3110
  validateNumericRecord(caseAggregate.metrics, `${casePath}.metrics`);
2895
3111
  for (const key of [
2896
3112
  "passRate",
@@ -2944,117 +3160,160 @@ function deserializeEvaluationProjectDataFromString(data) {
2944
3160
  return deserializeEvaluationProjectData(yaml.parse(data));
2945
3161
  }
2946
3162
 
2947
- // src/datasetTransfer.ts
2948
- var DATASET_EXPORT_VERSION = 1;
2949
- function isRecord3(value) {
2950
- return typeof value === "object" && value !== null && !Array.isArray(value);
2951
- }
2952
- function requireString2(value, path) {
2953
- if (typeof value !== "string" || value.length === 0) throw new Error(`${path} must be a non-empty string.`);
2954
- return value;
2955
- }
2956
- function requireText2(value, path) {
2957
- if (typeof value !== "string") throw new Error(`${path} must be a string.`);
2958
- return value;
3163
+ // src/library.ts
3164
+ function createEmptyEvaluationLibrary() {
3165
+ return {
3166
+ version: 1,
3167
+ data: createEmptyEvaluationProjectData(),
3168
+ datasets: [],
3169
+ migratedLegacyProjectIds: []
3170
+ };
2959
3171
  }
2960
- function parseFields(value) {
2961
- if (!Array.isArray(value)) throw new Error("Evaluation dataset fields must be an array.");
2962
- const ids = /* @__PURE__ */ new Set();
2963
- return value.map((field, index) => {
2964
- if (!isRecord3(field)) throw new Error(`fields[${index}] must be an object.`);
2965
- const id = requireString2(field.id, `fields[${index}].id`);
2966
- if (ids.has(id)) throw new Error(`Evaluation dataset has duplicate field id "${id}".`);
2967
- ids.add(id);
2968
- const role = field.role;
2969
- if (role !== "input" && role !== "expected" && role !== "metadata") {
2970
- throw new Error(`fields[${index}].role must be input, expected, or metadata.`);
2971
- }
2972
- if (field.description !== void 0 && typeof field.description !== "string") {
2973
- throw new Error(`fields[${index}].description must be a string.`);
2974
- }
2975
- if (field.required !== void 0 && typeof field.required !== "boolean") {
2976
- throw new Error(`fields[${index}].required must be a boolean.`);
3172
+ function normalizeEvaluationLibrary(value) {
3173
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return createEmptyEvaluationLibrary();
3174
+ const candidate = value;
3175
+ let data = createEmptyEvaluationProjectData();
3176
+ try {
3177
+ data = deserializeEvaluationProjectData(candidate.data);
3178
+ } catch {
3179
+ const dataCandidate = typeof candidate.data === "object" && candidate.data !== null && !Array.isArray(candidate.data) ? candidate.data : void 0;
3180
+ const suites2 = Array.isArray(dataCandidate == null ? void 0 : dataCandidate.suites) ? dataCandidate.suites.flatMap((suite) => {
3181
+ try {
3182
+ return [deserializeEvaluationProjectData({ version: 1, suites: [suite], baselines: [] }).suites[0]];
3183
+ } catch {
3184
+ return [];
3185
+ }
3186
+ }) : [];
3187
+ const baselines = Array.isArray(dataCandidate == null ? void 0 : dataCandidate.baselines) ? dataCandidate.baselines.flatMap((baseline) => {
3188
+ try {
3189
+ return [deserializeEvaluationProjectData({ version: 1, suites: [], baselines: [baseline] }).baselines[0]];
3190
+ } catch {
3191
+ return [];
3192
+ }
3193
+ }) : [];
3194
+ const suiteIds2 = new Set(suites2.map((suite) => suite.id));
3195
+ data = { version: 1, suites: suites2, baselines: baselines.filter((baseline) => suiteIds2.has(baseline.suiteId)) };
3196
+ }
3197
+ const datasets = Array.isArray(candidate.datasets) ? candidate.datasets.flatMap((dataset) => {
3198
+ try {
3199
+ return [localizeEvaluationDataset(dataset)];
3200
+ } catch {
3201
+ return [];
2977
3202
  }
2978
- return {
2979
- id,
2980
- name: requireText2(field.name, `fields[${index}].name`),
2981
- dataType: requireString2(field.dataType, `fields[${index}].dataType`),
2982
- role,
2983
- ...typeof field.description === "string" ? { description: field.description } : {},
2984
- ...typeof field.required === "boolean" ? { required: field.required } : {}
2985
- };
3203
+ }) : [];
3204
+ const suites = uniqueById(data.suites);
3205
+ const suiteIds = new Set(suites.map((suite) => suite.id));
3206
+ return {
3207
+ version: 1,
3208
+ data: {
3209
+ ...data,
3210
+ suites,
3211
+ baselines: uniqueById(data.baselines).filter((baseline) => suiteIds.has(baseline.suiteId))
3212
+ },
3213
+ datasets: uniqueById(datasets),
3214
+ migratedLegacyProjectIds: Array.isArray(candidate.migratedLegacyProjectIds) ? Array.from(new Set(candidate.migratedLegacyProjectIds.filter((id) => typeof id === "string"))) : []
3215
+ };
3216
+ }
3217
+ function uniqueById(values) {
3218
+ const seen = /* @__PURE__ */ new Set();
3219
+ return values.filter((value) => {
3220
+ if (seen.has(value.id)) return false;
3221
+ seen.add(value.id);
3222
+ return true;
2986
3223
  });
2987
3224
  }
2988
- function parseCases(value, fieldIds) {
2989
- if (!Array.isArray(value)) throw new Error("Evaluation dataset cases must be an array.");
2990
- const ids = /* @__PURE__ */ new Set();
2991
- return value.map((testCase, index) => {
2992
- if (!isRecord3(testCase)) throw new Error(`cases[${index}] must be an object.`);
2993
- const id = requireString2(testCase.id, `cases[${index}].id`);
2994
- if (ids.has(id)) throw new Error(`Evaluation dataset has duplicate case id "${id}".`);
2995
- ids.add(id);
2996
- if (!isRecord3(testCase.values)) throw new Error(`cases[${index}].values must be an object.`);
2997
- for (const [fieldId, fieldValue] of Object.entries(testCase.values)) {
2998
- if (!fieldIds.has(fieldId)) throw new Error(`cases[${index}].values references unknown field "${fieldId}".`);
2999
- assertPortableJson(fieldValue, `cases[${index}].values.${fieldId}`);
3225
+
3226
+ // src/store.ts
3227
+ var InMemoryEvaluationRunStore = class {
3228
+ #runs = /* @__PURE__ */ new Map();
3229
+ #datasetSnapshots = /* @__PURE__ */ new Map();
3230
+ #recordings = /* @__PURE__ */ new Map();
3231
+ #library = createEmptyEvaluationLibrary();
3232
+ async getLibrary() {
3233
+ return structuredClone(this.#library);
3234
+ }
3235
+ async putLibrary(library) {
3236
+ this.#library = structuredClone(normalizeEvaluationLibrary(library));
3237
+ }
3238
+ async put(run) {
3239
+ const normalized = normalizeEvaluationRun(run);
3240
+ const key = `${normalized.projectId}/${normalized.id}`;
3241
+ const existing = this.#runs.get(key);
3242
+ const next = reconcileEvaluationRunSnapshots(existing, normalized);
3243
+ if (next === existing) return;
3244
+ this.#runs.set(key, structuredClone(next));
3245
+ }
3246
+ async updateRunName(input) {
3247
+ const key = `${input.projectId}/${input.runId}`;
3248
+ const existing = this.#runs.get(key);
3249
+ if (!existing) return void 0;
3250
+ const renamed = normalizeEvaluationRun({ ...existing, name: input.name });
3251
+ this.#runs.set(key, structuredClone(renamed));
3252
+ return structuredClone(renamed);
3253
+ }
3254
+ async get(input) {
3255
+ const run = this.#runs.get(`${input.projectId}/${input.runId}`);
3256
+ return run ? normalizeEvaluationRun(run) : void 0;
3257
+ }
3258
+ async list(input) {
3259
+ return Array.from(this.#runs.values()).filter(
3260
+ (run) => run.projectId === input.projectId && (input.suiteId === void 0 || run.suiteId === input.suiteId)
3261
+ ).sort((left, right) => right.startedAt.localeCompare(left.startedAt)).map(normalizeEvaluationRun);
3262
+ }
3263
+ async delete(input) {
3264
+ this.#runs.delete(`${input.projectId}/${input.runId}`);
3265
+ for (const [key, artifact] of this.#recordings) {
3266
+ if (artifact.projectId === input.projectId && artifact.runId === input.runId) this.#recordings.delete(key);
3000
3267
  }
3001
- if (testCase.tags !== void 0 && (!Array.isArray(testCase.tags) || testCase.tags.some((tag) => typeof tag !== "string"))) {
3002
- throw new Error(`cases[${index}].tags must be an array of strings.`);
3268
+ }
3269
+ async putDatasetSnapshot(snapshot) {
3270
+ assertEvaluationDatasetSnapshot(snapshot);
3271
+ const key = `${snapshot.projectId}/${snapshot.fingerprint}`;
3272
+ if (this.#datasetSnapshots.has(key)) return;
3273
+ this.#datasetSnapshots.set(key, structuredClone(snapshot));
3274
+ }
3275
+ async getDatasetSnapshot(input) {
3276
+ const snapshot = this.#datasetSnapshots.get(`${input.projectId}/${input.fingerprint}`);
3277
+ return snapshot ? structuredClone(snapshot) : void 0;
3278
+ }
3279
+ async putRecording(artifact) {
3280
+ assertEvaluationRecordingArtifact(artifact);
3281
+ const key = `${artifact.projectId}/${artifact.reference.id}`;
3282
+ const existing = this.#recordings.get(key);
3283
+ if (existing && (existing.runId !== artifact.runId || existing.trialId !== artifact.trialId)) {
3284
+ throw new Error("An evaluation recording ID cannot be reassigned to another run or trial.");
3003
3285
  }
3004
- if (testCase.note !== void 0 && typeof testCase.note !== "string") throw new Error(`cases[${index}].note must be a string.`);
3005
- if (testCase.enabled !== void 0 && typeof testCase.enabled !== "boolean") throw new Error(`cases[${index}].enabled must be a boolean.`);
3006
- return {
3007
- id,
3008
- name: requireText2(testCase.name, `cases[${index}].name`),
3009
- values: testCase.values,
3010
- ...typeof testCase.enabled === "boolean" ? { enabled: testCase.enabled } : {},
3011
- ...Array.isArray(testCase.tags) ? { tags: testCase.tags } : {},
3012
- ...typeof testCase.note === "string" ? { note: testCase.note } : {}
3286
+ const next = structuredClone(artifact);
3287
+ if (existing) next.reference = structuredClone(existing.reference);
3288
+ this.#recordings.set(key, next);
3289
+ }
3290
+ async getRecording(input) {
3291
+ const artifact = this.#recordings.get(`${input.projectId}/${input.recordingId}`);
3292
+ if (!artifact) return void 0;
3293
+ if (artifact.reference.retention === "temporary" && artifact.reference.expiresAt && Date.parse(artifact.reference.expiresAt) <= Date.now()) {
3294
+ this.#recordings.delete(`${input.projectId}/${input.recordingId}`);
3295
+ return void 0;
3296
+ }
3297
+ return structuredClone(artifact);
3298
+ }
3299
+ async updateRecordingRetention(input) {
3300
+ const key = `${input.projectId}/${input.recordingId}`;
3301
+ const artifact = this.#recordings.get(key);
3302
+ if (!artifact) return false;
3303
+ artifact.reference = {
3304
+ id: artifact.reference.id,
3305
+ retention: input.retention,
3306
+ ...input.expiresAt === void 0 ? {} : { expiresAt: input.expiresAt }
3013
3307
  };
3014
- });
3015
- }
3016
- function validateEvaluationDataset(value, scope) {
3017
- if (!isRecord3(value)) throw new Error("Evaluation dataset must be an object.");
3018
- const fields = parseFields(value.fields);
3019
- const importedProjectId = value.projectId === void 0 ? void 0 : requireString2(value.projectId, "dataset.projectId");
3020
- const projectId = (scope == null ? void 0 : scope.projectId) ?? importedProjectId;
3021
- const dataset = {
3022
- id: (scope == null ? void 0 : scope.id) ?? requireString2(value.id, "dataset.id"),
3023
- name: requireText2(value.name, "dataset.name"),
3024
- ...projectId === void 0 ? {} : { projectId },
3025
- fields,
3026
- cases: parseCases(value.cases, new Set(fields.map((field) => field.id)))
3027
- };
3028
- return { ...dataset, contentFingerprint: fingerprintEvaluationDataset(dataset) };
3029
- }
3030
- function localizeEvaluationDataset(value, id) {
3031
- const dataset = validateEvaluationDataset(value, id === void 0 ? void 0 : { id });
3032
- const { projectId: _legacyProjectId, ...localDataset } = dataset;
3033
- return { ...localDataset, contentFingerprint: fingerprintEvaluationDataset(localDataset) };
3034
- }
3035
- function validateEvaluationDatasetForTransfer(value, scope) {
3036
- const dataset = validateEvaluationDataset(value, scope);
3037
- assertEvaluationDatasetValuesMatchDeclaredTypes(dataset);
3038
- return dataset;
3039
- }
3040
- function serializeEvaluationDatasetJson(dataset) {
3041
- const validated = validateEvaluationDatasetForTransfer(dataset);
3042
- return JSON.stringify({ version: DATASET_EXPORT_VERSION, dataset: validated }, null, 2);
3043
- }
3044
- function deserializeEvaluationDatasetJson(source, scope) {
3045
- let parsed;
3046
- try {
3047
- parsed = JSON.parse(source);
3048
- } catch {
3049
- throw new Error("Evaluation dataset JSON is not valid JSON.");
3308
+ return true;
3050
3309
  }
3051
- if (!isRecord3(parsed) || parsed.version !== DATASET_EXPORT_VERSION || !isRecord3(parsed.dataset)) {
3052
- throw new Error(`Expected an evaluation dataset export with version ${DATASET_EXPORT_VERSION}.`);
3310
+ async promoteBaseline(input) {
3311
+ for (const artifact of this.#recordings.values()) {
3312
+ if (artifact.projectId !== input.projectId || artifact.runId !== input.runId) continue;
3313
+ artifact.reference = { id: artifact.reference.id, retention: "baseline" };
3314
+ }
3053
3315
  }
3054
- const dataset = validateEvaluationDatasetForTransfer(parsed.dataset, { id: scope.id });
3055
- const { projectId: _legacyProjectId, ...localDataset } = dataset;
3056
- return { ...localDataset, contentFingerprint: fingerprintEvaluationDataset(localDataset) };
3057
- }
3316
+ };
3058
3317
 
3059
3318
  // src/suiteTransfer.ts
3060
3319
  var SUITE_BUNDLE_EXPORT_VERSION = 1;
@@ -3106,30 +3365,139 @@ function deserializeEvaluationSuiteBundleJson(source, scope) {
3106
3365
  }
3107
3366
  };
3108
3367
  }
3368
+
3369
+ // src/librarySync.ts
3370
+ function evaluationLibraryValueEquals(left, right) {
3371
+ if (left === void 0 || right === void 0) return left === right;
3372
+ return canonicalStringify(left) === canonicalStringify(right);
3373
+ }
3374
+ function getEvaluationSuiteBundle(library, id) {
3375
+ const suite = library.data.suites.find((candidate) => candidate.id === id);
3376
+ if (!suite) return void 0;
3377
+ return {
3378
+ suite,
3379
+ baselines: library.data.baselines.filter((baseline) => baseline.suiteId === id)
3380
+ };
3381
+ }
3382
+ function getEvaluationLibraryResource(library, kind, id) {
3383
+ if (kind === "suite") return { kind, id, value: getEvaluationSuiteBundle(library, id) };
3384
+ return { kind, id, value: library.datasets.find((dataset) => dataset.id === id) };
3385
+ }
3386
+ function resourceIds(library, kind) {
3387
+ return new Set(kind === "suite" ? library.data.suites.map((suite) => suite.id) : library.datasets.map((dataset) => dataset.id));
3388
+ }
3389
+ function diffEvaluationLibraryMutation(previous, next, versions) {
3390
+ const changes = [];
3391
+ for (const kind of ["dataset", "suite"]) {
3392
+ const ids = /* @__PURE__ */ new Set([...resourceIds(previous, kind), ...resourceIds(next, kind)]);
3393
+ for (const id of [...ids].sort()) {
3394
+ if (kind === "suite") {
3395
+ const before = getEvaluationSuiteBundle(previous, id);
3396
+ const after = getEvaluationSuiteBundle(next, id);
3397
+ if (evaluationLibraryValueEquals(before, after)) continue;
3398
+ const currentVersion = versions.suites[id];
3399
+ if (before !== void 0 && currentVersion === void 0) {
3400
+ throw new Error(`The suite "${id}" has no synchronization version.`);
3401
+ }
3402
+ const expectedVersion = before === void 0 ? null : currentVersion;
3403
+ if (after === void 0) {
3404
+ changes.push({ kind: "delete-suite", id, expectedVersion });
3405
+ } else {
3406
+ changes.push({
3407
+ kind: "put-suite",
3408
+ id,
3409
+ expectedVersion,
3410
+ suite: after.suite,
3411
+ baselines: after.baselines
3412
+ });
3413
+ }
3414
+ } else {
3415
+ const before = previous.datasets.find((dataset) => dataset.id === id);
3416
+ const after = next.datasets.find((dataset) => dataset.id === id);
3417
+ if (evaluationLibraryValueEquals(before, after)) continue;
3418
+ const currentVersion = versions.datasets[id];
3419
+ if (before !== void 0 && currentVersion === void 0) {
3420
+ throw new Error(`The dataset "${id}" has no synchronization version.`);
3421
+ }
3422
+ const expectedVersion = before === void 0 ? null : currentVersion;
3423
+ if (after === void 0) {
3424
+ changes.push({ kind: "delete-dataset", id, expectedVersion });
3425
+ } else {
3426
+ changes.push({ kind: "put-dataset", id, expectedVersion, dataset: after });
3427
+ }
3428
+ }
3429
+ }
3430
+ }
3431
+ return changes.length === 0 ? void 0 : { changes };
3432
+ }
3433
+ function replaceById(items, value) {
3434
+ const index = items.findIndex((item) => item.id === value.id);
3435
+ if (index === -1) return [...items, value];
3436
+ return items.map((item) => item.id === value.id ? value : item);
3437
+ }
3438
+ function applyEvaluationLibraryMutation(library, mutation) {
3439
+ let suites = library.data.suites;
3440
+ let baselines = library.data.baselines;
3441
+ let datasets = library.datasets;
3442
+ for (const change of mutation.changes) {
3443
+ switch (change.kind) {
3444
+ case "put-suite":
3445
+ suites = replaceById(suites, change.suite);
3446
+ baselines = [...baselines.filter((baseline) => baseline.suiteId !== change.id), ...change.baselines];
3447
+ break;
3448
+ case "delete-suite":
3449
+ suites = suites.filter((suite) => suite.id !== change.id);
3450
+ baselines = baselines.filter((baseline) => baseline.suiteId !== change.id);
3451
+ break;
3452
+ case "put-dataset":
3453
+ datasets = replaceById(datasets, change.dataset);
3454
+ break;
3455
+ case "delete-dataset":
3456
+ datasets = datasets.filter((dataset) => dataset.id !== change.id);
3457
+ break;
3458
+ }
3459
+ }
3460
+ return normalizeEvaluationLibrary({
3461
+ version: 1,
3462
+ data: { version: 1, suites, baselines },
3463
+ datasets,
3464
+ migratedLegacyProjectIds: library.migratedLegacyProjectIds
3465
+ });
3466
+ }
3109
3467
  // Annotate the CommonJS export names for ESM import in node:
3110
3468
  0 && (module.exports = {
3111
3469
  EvaluationGraphExecutionError,
3112
3470
  InMemoryEvaluationRunStore,
3113
3471
  LEGACY_EVALUATOR_INPUT_IDS,
3472
+ applyEvaluationLibraryMutation,
3114
3473
  areEvaluationDataTypesCompatible,
3115
3474
  assertEvaluationDatasetSnapshot,
3116
3475
  assertEvaluationDatasetValuesMatchDeclaredTypes,
3476
+ assertEvaluationRecordingArtifact,
3117
3477
  assertPortableJson,
3118
3478
  canonicalStringify,
3119
3479
  canonicalizePortableJson,
3120
3480
  clonePortableJson,
3481
+ createEmptyEvaluationLibrary,
3121
3482
  createEmptyEvaluationProjectData,
3122
3483
  createEmptyMetrics,
3123
3484
  createEvaluationBaselineSnapshot,
3485
+ createEvaluationRunPlan,
3486
+ createEvaluationRunShell,
3124
3487
  deserializeEvaluationDatasetJson,
3125
3488
  deserializeEvaluationProjectData,
3126
3489
  deserializeEvaluationProjectDataFromString,
3127
3490
  deserializeEvaluationSuiteBundleJson,
3491
+ diffEvaluationLibraryMutation,
3128
3492
  evaluateAssertion,
3129
3493
  evaluateThresholdResults,
3494
+ evaluationLibraryValueEquals,
3130
3495
  finalizeEvaluationRecordingRetention,
3496
+ finalizeEvaluationRun,
3131
3497
  fingerprint,
3132
3498
  fingerprintEvaluationDataset,
3499
+ getEvaluationLibraryResource,
3500
+ getEvaluationSuiteBundle,
3133
3501
  getEvaluationSuiteMode,
3134
3502
  getEvaluationTopLevelOutputId,
3135
3503
  hasAuthoritativeEvaluationCriteria,
@@ -3139,11 +3507,15 @@ function deserializeEvaluationSuiteBundleJson(source, scope) {
3139
3507
  localizeEvaluationDataset,
3140
3508
  normalizeEvaluationAggregate,
3141
3509
  normalizeEvaluationBaselineSnapshot,
3510
+ normalizeEvaluationLibrary,
3142
3511
  normalizeEvaluationRun,
3143
3512
  normalizeEvaluationTrial,
3144
3513
  portableEvaluationDataTypes,
3514
+ preserveEvaluationRunName,
3515
+ reconcileEvaluationRunSnapshots,
3145
3516
  runEvaluationCases,
3146
3517
  runEvaluationSuite,
3518
+ runEvaluationTrial,
3147
3519
  runEvaluationWorkPool,
3148
3520
  serializeEvaluationDatasetJson,
3149
3521
  serializeEvaluationProjectData,