@leostera/evalkit 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
- import { n as assertNoRemovedOptions } from "./upgrade-G-uFPJKg.mjs";
1
+ import { n as assertNoRemovedOptions, u as transcriptUsage } from "./upgrade-rMsGzUEJ.mjs";
2
2
  import * as Schema from "effect/Schema";
3
+ import { buildContext } from "@leostera/atxf";
3
4
  //#region ../core/src/identity.ts
4
5
  const ResourceKindSchema = Schema.Literal("run", "artifact");
5
6
  /** Human-authored project-local identifier; never a generated resource URI. */
@@ -153,6 +154,188 @@ function isDataset(value) {
153
154
  return typeof value === "object" && value !== null && value.kind === "dataset" && typeof value.source === "object";
154
155
  }
155
156
  //#endregion
157
+ //#region ../core/src/limits.ts
158
+ const UNITS = {
159
+ ms: 1,
160
+ s: 1e3,
161
+ m: 6e4,
162
+ h: 36e5
163
+ };
164
+ /** `'20m'` → 1_200_000. A number is milliseconds. */
165
+ function parseDuration(value, owner) {
166
+ if (typeof value === "number") return positive(value, owner);
167
+ const match = /^\s*(\d+(?:\.\d+)?)\s*(ms|s|m|h)\s*$/.exec(value);
168
+ if (!match) throw new Error(`${owner} must be milliseconds or a duration such as 90s, 20m or 1h; received ${JSON.stringify(value)}`);
169
+ return positive(Number(match[1]) * UNITS[match[2]], owner);
170
+ }
171
+ function positive(value, owner) {
172
+ if (!Number.isFinite(value) || value <= 0) throw new Error(`${owner} must be a positive number; received ${value}`);
173
+ return value;
174
+ }
175
+ /** Checks and normalizes limits; `owner` names where they were written. */
176
+ function parseLimits(input, owner) {
177
+ if (input === void 0) return {};
178
+ if (typeof input !== "object" || input === null || Array.isArray(input)) throw new Error(`${owner} limits must be an object`);
179
+ const limits = {};
180
+ for (const [key, value] of Object.entries(input)) {
181
+ if (value === void 0) continue;
182
+ if (key === "time") limits.time = parseDuration(value, `${owner} limits.time`);
183
+ else if (key === "steps" || key === "tokens") {
184
+ if (!Number.isInteger(value) || value <= 0) throw new Error(`${owner} limits.${key} must be a positive whole number; received ${String(value)}`);
185
+ limits[key] = value;
186
+ } else throw new Error(`${owner} has an unknown limit ${JSON.stringify(key)}; limits are steps, tokens and time`);
187
+ }
188
+ return limits;
189
+ }
190
+ /** Later layers win, kind by kind: config, then scenario, then matrix cell, then the command line. */
191
+ function resolveLimits(...layers) {
192
+ return Object.assign({}, ...layers);
193
+ }
194
+ /** A matrix's `limits` axes are parameters named `limits.<kind>`. */
195
+ const LIMIT_PARAMETER_PREFIX = "limits.";
196
+ /** The limits a matrix cell's `limits.<kind>` parameters set. */
197
+ function limitsFromParameters(parameters) {
198
+ const input = {};
199
+ for (const [key, value] of Object.entries(parameters ?? {})) if (key.startsWith("limits.")) input[key.slice(7)] = value;
200
+ return parseLimits(input, "The matrix cell");
201
+ }
202
+ /** `steps=80` or `time=1h`, from `--limit`. */
203
+ function parseLimitFlag(flag) {
204
+ const index = flag.indexOf("=");
205
+ if (index < 1) throw new Error(`--limit takes kind=value, such as steps=80 or time=20m; received ${JSON.stringify(flag)}`);
206
+ const kind = flag.slice(0, index).trim();
207
+ const raw = flag.slice(index + 1).trim();
208
+ return parseLimits({ [kind]: kind === "time" && !/^\d+$/.test(raw) ? raw : Number(raw) }, "--limit");
209
+ }
210
+ /** `40 steps`, `2,000,000 tokens`, `20m`. */
211
+ function formatLimit(kind, value) {
212
+ if (kind === "time") {
213
+ if (value % 36e5 === 0) return `${value / 36e5}h`;
214
+ if (value % 6e4 === 0) return `${value / 6e4}m`;
215
+ if (value % 1e3 === 0) return `${value / 1e3}s`;
216
+ return `${value}ms`;
217
+ }
218
+ return `${value.toLocaleString("en-US")} ${kind}`;
219
+ }
220
+ //#endregion
221
+ //#region ../core/src/transcript.ts
222
+ const TREE = /* @__PURE__ */ new Set([
223
+ "message",
224
+ "context",
225
+ "settings",
226
+ "compaction",
227
+ "context_edit",
228
+ "branch_summary",
229
+ "turn",
230
+ "tool_status",
231
+ "usage",
232
+ "checkpoint",
233
+ "agent_link",
234
+ "custom"
235
+ ]);
236
+ function isTreeRecord(record) {
237
+ return TREE.has(record.type);
238
+ }
239
+ /**
240
+ * The records from the transcript's root to its active leaf, in order: what
241
+ * the session is now, without abandoned branches (ATXF §4).
242
+ */
243
+ function activePath(transcript) {
244
+ const byId = /* @__PURE__ */ new Map();
245
+ let leaf = null;
246
+ for (const record of transcript.records) if (isTreeRecord(record)) {
247
+ byId.set(record.id, record);
248
+ leaf = record.id;
249
+ } else if (record.type === "leaf") leaf = record.targetId;
250
+ const path = [];
251
+ const seen = /* @__PURE__ */ new Set();
252
+ for (let id = leaf; id !== null && !seen.has(id);) {
253
+ seen.add(id);
254
+ const record = byId.get(id);
255
+ if (!record) break;
256
+ path.push(record);
257
+ id = record.parentId;
258
+ }
259
+ return path.reverse();
260
+ }
261
+ /** Messages on the active path, optionally of one role. */
262
+ function transcriptMessages(transcript, role) {
263
+ return activePath(transcript).filter((record) => record.type === "message" && (role === void 0 || record.role === role));
264
+ }
265
+ /** Every tool call on the active path, in order, with its result. */
266
+ function transcriptToolCalls(transcript, name) {
267
+ return callsIn(activePath(transcript)).filter((entry) => name === void 0 || entry.call.name === name).map(({ message, call, result }) => ({
268
+ id: call.id,
269
+ name: call.name,
270
+ arguments: call.arguments ?? {},
271
+ ...call.input !== void 0 ? { input: call.input } : {},
272
+ ...result ? {
273
+ result: toolResultValue(result),
274
+ resultId: result.id
275
+ } : {},
276
+ isError: result?.isError === true,
277
+ messageId: message.id
278
+ }));
279
+ }
280
+ /** Tool calls among `records`, each paired with the first result for it. */
281
+ function callsIn(records) {
282
+ const calls = [];
283
+ const open = /* @__PURE__ */ new Map();
284
+ for (const record of records) {
285
+ if (record.type !== "message") continue;
286
+ if (record.role === "assistant") {
287
+ for (const block of record.content) if (block.type === "tool_call") {
288
+ const entry = {
289
+ message: record,
290
+ call: block
291
+ };
292
+ calls.push(entry);
293
+ open.set(block.id, entry);
294
+ }
295
+ }
296
+ if (record.role === "tool") {
297
+ const entry = open.get(record.toolCallId);
298
+ if (entry && !entry.result) entry.result = record;
299
+ }
300
+ }
301
+ return calls;
302
+ }
303
+ /** The text of a message's content: text blocks joined, other blocks left out. */
304
+ function contentText(content) {
305
+ if (typeof content === "string") return content;
306
+ return content.flatMap((block) => block.type === "text" ? [block.text] : []).join("\n");
307
+ }
308
+ /** The text of the last assistant message on the active path, or `''`. */
309
+ function transcriptReply(transcript) {
310
+ const last = transcriptMessages(transcript, "assistant").at(-1);
311
+ return last ? contentText(last.content) : "";
312
+ }
313
+ /**
314
+ * A tool result's value: the JSON an adapter returned (kept in
315
+ * `details.result`), else the result's text.
316
+ */
317
+ function toolResultValue(result) {
318
+ if (result.details && "result" in result.details) return result.details.result;
319
+ return contentText(result.content);
320
+ }
321
+ /** `provider/id`, or `id` when the provider is unknown. */
322
+ function modelName(model) {
323
+ if (!model) return void 0;
324
+ return model.provider === "unknown" ? model.id : `${model.provider}/${model.id}`;
325
+ }
326
+ /** `openai/gpt-5` → `{ provider: 'openai', id: 'gpt-5' }`; without a provider, `unknown`. */
327
+ function parseModel(model) {
328
+ if (typeof model !== "string") return model;
329
+ const slash = model.indexOf("/");
330
+ return slash > 0 && slash < model.length - 1 ? {
331
+ provider: model.slice(0, slash),
332
+ id: model.slice(slash + 1)
333
+ } : {
334
+ provider: "unknown",
335
+ id: model
336
+ };
337
+ }
338
+ //#endregion
156
339
  //#region ../core/src/json.ts
157
340
  /** A value that cannot be stored in a report, with the path of the offending part. */
158
341
  var NotJsonError = class extends Error {
@@ -403,10 +586,8 @@ function catalogEntry(evaluation, suite, options = {}) {
403
586
  agent: catalogAgent(evaluation.agent, at),
404
587
  datasets: (evaluation.datasets ?? []).map((dataset) => catalogDataset(dataset, at)),
405
588
  repeat: evaluation.repeat ?? 1,
406
- policy: {
407
- ...policy.timeoutMs !== void 0 ? { timeoutMs: policy.timeoutMs } : {},
408
- ...policy.failfast !== void 0 ? { failfast: policy.failfast } : {}
409
- },
589
+ policy: policy.failfast !== void 0 ? { failfast: policy.failfast } : {},
590
+ ...evaluation.limits ? { limits: parseLimits(evaluation.limits, `Eval ${evaluation.id}`) } : {},
410
591
  transcript: evaluation.transcript.map((step) => catalogStep(step, at, options.judge)),
411
592
  scorers: evaluation.scoring.map((rule) => catalogScorer(rule, at, options.judge)),
412
593
  ...evaluation.metadata !== void 0 ? { metadata: toJson(evaluation.metadata, `${evaluation.id}.metadata`) } : {},
@@ -537,18 +718,6 @@ function defineConfig(configuration) {
537
718
  return configuration;
538
719
  }
539
720
  //#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
552
721
  //#region ../core/src/canonical-json.ts
553
722
  /** Canonical JSON: keys sorted recursively, array order preserved. Rejects non-JSON inputs. */
554
723
  function canonicalParameters(value) {
@@ -561,10 +730,27 @@ function canonicalParameters(value) {
561
730
  //#endregion
562
731
  //#region ../core/src/matrix.ts
563
732
  const COUNT_BUDGET = 1e5;
733
+ /** The matrix's parameters with its `limits` as `limits.<kind>` axes. */
734
+ function withLimitAxes(definition) {
735
+ if (definition.limits === void 0) return definition.parameters;
736
+ const parameters = { ...definition.parameters };
737
+ for (const [kind, values] of Object.entries(definition.limits)) {
738
+ if (values === void 0) continue;
739
+ const axis = `${LIMIT_PARAMETER_PREFIX}${kind}`;
740
+ if (Object.hasOwn(parameters, axis)) throw new Error(`Matrix declares ${axis} as both a parameter and a limit`);
741
+ for (const value of values) parseLimits({ [kind]: value }, `Matrix ${definition.id}`);
742
+ parameters[axis] = values;
743
+ }
744
+ return parameters;
745
+ }
564
746
  const equal = (a, b) => canonicalParameters(a) === canonicalParameters(b);
565
747
  const matches = (pattern, values) => Object.entries(pattern).every(([key, value]) => Object.hasOwn(values, key) && equal(values[key], value));
566
- function defineEvalMatrix(definition) {
567
- authoringId(definition);
748
+ function defineEvalMatrix(input) {
749
+ authoringId(input);
750
+ const definition = {
751
+ ...input,
752
+ parameters: withLimitAxes(input)
753
+ };
568
754
  const identities = /* @__PURE__ */ new Set();
569
755
  for (const evaluation of definition.evals) {
570
756
  const id = authoringId(evaluation);
@@ -601,7 +787,7 @@ function defineEvalMatrix(definition) {
601
787
  const caseChoices = new Map(caseKeys.map((key) => [key, cases.map((assignment) => assignment[key])]));
602
788
  function select(selection = {}) {
603
789
  for (const key of Object.keys(selection.parameters ?? {})) if (!declared.has(key)) throw new Error(`Unknown matrix axis: ${key}`);
604
- for (const key of Object.keys(selection.overrides ?? {})) if (declared.has(key)) throw new Error(`Select axis ${key}; do not override it`);
790
+ for (const key of Object.keys(selection.overrides ?? {})) if (declared.has(key)) throw new Error(`${key} is a matrix axis: pick its value with -a ${key}=<value> (--select), not --param`);
605
791
  canonicalParameters(selection.overrides ?? {});
606
792
  for (const name of selection.evals ?? []) if (!definition.evals.some((e) => e.id === name)) throw new Error(`Unknown matrix eval: ${name}`);
607
793
  const evals = definition.evals.filter((e) => !selection.evals || selection.evals.includes(e.id));
@@ -694,89 +880,76 @@ function defineEvalMatrix(definition) {
694
880
  };
695
881
  }
696
882
  //#endregion
697
- //#region ../core/src/aut-events.ts
698
- /** Converts an adapter event into a recorded `AutEvent`. */
699
- function normalizeAutEvent(input, now) {
700
- if (typeof input !== "object" || input === null) throw new Error("AUT events must be objects");
701
- const timestamp = input.timestamp ?? now().toISOString();
702
- switch (input.kind) {
703
- case "message": return {
704
- kind: "message",
705
- role: input.role,
706
- content: toJson(input.content, "event.content"),
707
- timestamp
708
- };
709
- case "tool-call": return {
710
- kind: "tool-call",
711
- id: input.id,
712
- name: input.name,
713
- arguments: input.arguments === void 0 ? {} : toJson(input.arguments, "event.arguments"),
714
- timestamp
715
- };
716
- case "tool-result": return {
717
- kind: "tool-result",
718
- id: input.id,
719
- ...input.name === void 0 ? {} : { name: input.name },
720
- result: input.result === void 0 ? null : toJson(input.result, "event.result"),
721
- timestamp
722
- };
723
- case "completed": return {
724
- kind: "completed",
725
- ...input.output === void 0 ? {} : { output: toJson(input.output, "event.output") },
726
- timestamp
727
- };
728
- case "started":
729
- case "turn-started":
730
- case "turn-completed":
731
- case "error": return {
732
- ...input,
733
- timestamp
734
- };
735
- default: throw new Error(`Unknown AUT event kind: ${String(input.kind)}`);
736
- }
883
+ //#region ../core/src/isolation.ts
884
+ const NETWORKS = [
885
+ "none",
886
+ "fixtures",
887
+ "full"
888
+ ];
889
+ const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
890
+ /** No isolation: commands run on the host in the run's workspace. */
891
+ function none() {
892
+ return { kind: "none" };
737
893
  }
738
894
  /**
739
- * The events a returned turn result stands for: each tool call (and its
740
- * result), then the reply, then the usage as a `turn-completed` event.
895
+ * Container isolation through the `docker` CLI, or a compatible one such as
896
+ * podman's: a container per run, the candidate workspace mounted at
897
+ * `/workspace`, the evaluator workspace never mounted, and its own process
898
+ * table, so a `pkill` reaches only that run.
741
899
  */
742
- function turnResultEvents(result, turn, now) {
743
- if (typeof result === "string") result = { reply: result };
744
- if (typeof result !== "object" || result === null) throw new Error("send() must return nothing, a string, or a turn result");
745
- const events = [];
746
- for (const [index, call] of (result.toolCalls ?? []).entries()) {
747
- const id = call.id ?? `turn-${turn}-call-${index + 1}`;
748
- events.push(normalizeAutEvent({
749
- kind: "tool-call",
750
- id,
751
- name: call.name,
752
- arguments: call.arguments
753
- }, now));
754
- if (call.result !== void 0) events.push(normalizeAutEvent({
755
- kind: "tool-result",
756
- id,
757
- name: call.name,
758
- result: call.result
759
- }, now));
760
- }
761
- if (result.reply !== void 0) events.push(normalizeAutEvent({
762
- kind: "message",
763
- role: "assistant",
764
- content: result.reply
765
- }, now));
766
- if (result.usage || result.model) events.push(normalizeAutEvent({
767
- kind: "turn-completed",
768
- turn,
769
- ...result.usage ? { usage: result.usage } : {},
770
- ...result.model ? { model: result.model } : {}
771
- }, now));
772
- return events;
900
+ function container(options) {
901
+ if (typeof options?.image !== "string" || !options.image.trim()) throw new Error("container() needs an image: a name such as node:22, or a Dockerfile path");
902
+ const network = options.network ?? "none";
903
+ if (!NETWORKS.includes(network)) throw new Error(`container() network must be ${NETWORKS.join(", ")}; received ${String(network)}`);
904
+ const env = [...options.env ?? []];
905
+ for (const name of env) if (!ENV_NAME.test(name)) throw new Error(`container() env takes variable names, such as API_KEY; received ${JSON.stringify(name)}`);
906
+ const mounts = (options.mounts ?? []).map(parseMount);
907
+ return {
908
+ kind: "container",
909
+ image: options.image.trim(),
910
+ network,
911
+ runtime: options.runtime ?? "docker",
912
+ env,
913
+ mounts
914
+ };
915
+ }
916
+ function parseMount(mount) {
917
+ const parsed = typeof mount === "string" ? (() => {
918
+ const at = mount.lastIndexOf(":");
919
+ return at > 0 ? {
920
+ host: mount.slice(0, at),
921
+ path: mount.slice(at + 1)
922
+ } : void 0;
923
+ })() : mount;
924
+ if (!parsed?.host || !parsed.path?.startsWith("/")) throw new Error(`container() mounts take { host, path } with an absolute container path, or 'host:/path'; received ${JSON.stringify(mount)}`);
925
+ if (parsed.path === "/workspace" || parsed.path.startsWith("/workspace/")) throw new Error(`container() cannot mount over /workspace, the run's workspace: ${parsed.path}`);
926
+ return {
927
+ host: parsed.host,
928
+ path: parsed.path
929
+ };
930
+ }
931
+ /**
932
+ * macOS `sandbox-exec` isolation, for projects that cannot use containers:
933
+ * the run cannot read or write the project, the home directory or other
934
+ * runs' workspaces, except its own workspace and `allow`, and can signal
935
+ * only processes it started.
936
+ */
937
+ function sandboxExec(options = {}) {
938
+ for (const name of options.env ?? []) if (!ENV_NAME.test(name)) throw new Error(`sandboxExec() env takes variable names, such as API_KEY; received ${JSON.stringify(name)}`);
939
+ return {
940
+ kind: "sandbox-exec",
941
+ allow: [...options.allow ?? []],
942
+ read: [...options.read ?? []],
943
+ deny: [...options.deny ?? []],
944
+ ...options.env ? { env: [...options.env] } : {}
945
+ };
773
946
  }
774
947
  //#endregion
775
948
  //#region ../core/src/schema.ts
776
949
  /** Runtime schemas for serialized EvalKit report and API boundaries. */
777
- const RunStatusSchema = Schema.Literal("running", "passed", "failed", "errored", "cancelled");
950
+ const RunStatusSchema = Schema.Literal("running", "passed", "failed", "errored", "cancelled", "limited");
778
951
  /** The report schema version this EvalKit writes and reads. */
779
- const REPORT_SCHEMA_VERSION = 6;
952
+ const REPORT_SCHEMA_VERSION = 7;
780
953
  const RecordedErrorSchema = Schema.Struct({
781
954
  name: Schema.String,
782
955
  message: Schema.String,
@@ -825,8 +998,23 @@ const RunDatasetSchema = Schema.Struct({
825
998
  source: Schema.Literal("static", "computed"),
826
999
  digest: Schema.String
827
1000
  });
1001
+ const LimitsSchema = Schema.Struct({
1002
+ steps: Schema.optional(Schema.Number),
1003
+ tokens: Schema.optional(Schema.Number),
1004
+ time: Schema.optional(Schema.Number)
1005
+ });
1006
+ const LimitReachedSchema = Schema.Struct({
1007
+ limit: Schema.Literal("steps", "tokens", "time"),
1008
+ value: Schema.Number,
1009
+ used: Schema.Number
1010
+ });
1011
+ const RunIsolationSchema = Schema.Union(Schema.Struct({
1012
+ kind: Schema.Literal("container"),
1013
+ image: Schema.String,
1014
+ network: Schema.Literal("none", "fixtures", "full")
1015
+ }), Schema.Struct({ kind: Schema.Literal("sandbox-exec") }));
828
1016
  const RunMetadataSchema = Schema.Struct({
829
- schemaVersion: Schema.Literal(6),
1017
+ schemaVersion: Schema.Literal(7),
830
1018
  runUri: resourceUriSchema("run"),
831
1019
  evalId: Schema.String,
832
1020
  suiteId: Schema.optional(Schema.String),
@@ -842,19 +1030,37 @@ const RunMetadataSchema = Schema.Struct({
842
1030
  batch: Schema.optional(BatchSchema),
843
1031
  /** The effective snapshot ignore patterns: the project's, then the scenario's. */
844
1032
  workspaceIgnore: Schema.optional(Schema.mutable(Schema.Array(Schema.String))),
845
- datasets: Schema.optional(Schema.mutable(Schema.Array(RunDatasetSchema)))
1033
+ datasets: Schema.optional(Schema.mutable(Schema.Array(RunDatasetSchema))),
1034
+ limits: Schema.optional(LimitsSchema),
1035
+ isolation: Schema.optional(RunIsolationSchema)
846
1036
  });
847
1037
  /** On-disk manifests include a lifecycle status. */
848
1038
  const RunManifestSchema = Schema.Struct({
849
1039
  ...RunMetadataSchema.fields,
850
1040
  status: Schema.optional(RunStatusSchema)
851
1041
  });
1042
+ /** ATXF usage: disjoint buckets, `input` without cached tokens. */
852
1043
  const UsageSchema = Schema.Struct({
853
- inputTokens: Schema.optional(Schema.Number),
854
- outputTokens: Schema.optional(Schema.Number),
855
- cachedInputTokens: Schema.optional(Schema.Number),
856
- totalTokens: Schema.optional(Schema.Number)
1044
+ input: Schema.Number,
1045
+ output: Schema.Number,
1046
+ cacheRead: Schema.optional(Schema.Number),
1047
+ cacheWrite: Schema.optional(Schema.Number),
1048
+ reasoning: Schema.optional(Schema.Number),
1049
+ total: Schema.optional(Schema.Number),
1050
+ cost: Schema.optional(Schema.Struct({
1051
+ currency: Schema.optional(Schema.String),
1052
+ input: Schema.optional(Schema.Number),
1053
+ output: Schema.optional(Schema.Number),
1054
+ cacheRead: Schema.optional(Schema.Number),
1055
+ cacheWrite: Schema.optional(Schema.Number),
1056
+ total: Schema.Number
1057
+ }))
857
1058
  });
1059
+ /**
1060
+ * An ATXF transcript in JSON (`{ header, records }`). Its records are checked
1061
+ * by the ATXF validator where they are written, not here.
1062
+ */
1063
+ const TranscriptSchema = Schema.declare((value) => typeof value === "object" && value !== null && typeof value.header === "object" && value.header?.type === "transcript" && Array.isArray(value.records), { identifier: "Transcript" });
858
1064
  const JudgeRunInfoSchema = Schema.Struct({
859
1065
  agent: Schema.optional(Schema.Struct({
860
1066
  id: Schema.String,
@@ -867,7 +1073,8 @@ const JudgeRunInfoSchema = Schema.Struct({
867
1073
  prompt: Schema.optional(Schema.String),
868
1074
  reply: Schema.optional(JsonValueSchema),
869
1075
  attempts: Schema.optional(Schema.Number),
870
- events: Schema.optional(Schema.mutable(Schema.Array(JsonValueSchema)))
1076
+ /** The judge's own session, an ATXF transcript (checked when written). */
1077
+ transcript: Schema.optional(TranscriptSchema)
871
1078
  });
872
1079
  const CriterionResultSchema = Schema.Union(Schema.Struct({
873
1080
  name: Schema.String,
@@ -915,7 +1122,7 @@ const CheckpointResultSchema = Schema.Struct({
915
1122
  durationMs: Schema.optional(Schema.Number),
916
1123
  error: Schema.optional(RecordedErrorSchema),
917
1124
  matchedToolCall: Schema.optional(Schema.Struct({
918
- eventIndex: Schema.Number,
1125
+ messageId: Schema.String,
919
1126
  id: Schema.String
920
1127
  }))
921
1128
  });
@@ -952,165 +1159,92 @@ const RunSummarySchema = Schema.Struct({
952
1159
  scoring: Schema.optional(RunScoringSchema),
953
1160
  artifacts: Schema.optional(Schema.mutable(Schema.Array(ArtifactEntrySchema))),
954
1161
  attachments: Schema.optional(Schema.mutable(Schema.Array(AttachmentEntrySchema))),
955
- error: Schema.optional(RecordedErrorSchema)
1162
+ error: Schema.optional(RecordedErrorSchema),
1163
+ limit: Schema.optional(LimitReachedSchema)
956
1164
  });
957
- const autEvent = {
958
- started: Schema.Struct({
959
- source: Schema.Literal("aut"),
960
- kind: Schema.Literal("started"),
961
- timestamp: Schema.String
962
- }),
963
- message: Schema.Struct({
964
- source: Schema.Literal("aut"),
965
- kind: Schema.Literal("message"),
966
- role: Schema.Literal("system", "user", "assistant", "tool"),
967
- content: JsonValueSchema,
968
- timestamp: Schema.String
969
- }),
970
- toolCall: Schema.Struct({
971
- source: Schema.Literal("aut"),
972
- kind: Schema.Literal("tool-call"),
973
- id: Schema.String,
974
- name: Schema.String,
975
- arguments: JsonValueSchema,
976
- timestamp: Schema.String
977
- }),
978
- toolResult: Schema.Struct({
979
- source: Schema.Literal("aut"),
980
- kind: Schema.Literal("tool-result"),
981
- id: Schema.String,
982
- name: Schema.optional(Schema.String),
983
- result: JsonValueSchema,
984
- timestamp: Schema.String
985
- }),
986
- turnStarted: Schema.Struct({
987
- source: Schema.Literal("aut"),
988
- kind: Schema.Literal("turn-started"),
989
- turn: Schema.Number,
990
- timestamp: Schema.String
991
- }),
992
- turnCompleted: Schema.Struct({
993
- source: Schema.Literal("aut"),
994
- kind: Schema.Literal("turn-completed"),
995
- turn: Schema.Number,
996
- timestamp: Schema.String,
997
- latencyMs: Schema.optional(Schema.Number),
998
- usage: Schema.optional(UsageSchema),
999
- model: Schema.optional(Schema.String)
1000
- }),
1001
- completed: Schema.Struct({
1002
- source: Schema.Literal("aut"),
1003
- kind: Schema.Literal("completed"),
1004
- output: Schema.optional(JsonValueSchema),
1005
- timestamp: Schema.String
1006
- }),
1007
- error: Schema.Struct({
1008
- source: Schema.Literal("aut"),
1009
- kind: Schema.Literal("error"),
1010
- error: RecordedErrorSchema,
1011
- timestamp: Schema.String
1012
- })
1165
+ const runEventFields = {
1166
+ /** The transcript record it happened after; null before the first. */
1167
+ at: Schema.NullOr(Schema.String),
1168
+ timestamp: Schema.String
1013
1169
  };
1014
1170
  const runnerEvent = {
1015
1171
  runStarted: Schema.Struct({
1016
- source: Schema.Literal("runner"),
1017
1172
  kind: Schema.Literal("run-started"),
1018
- timestamp: Schema.String
1173
+ ...runEventFields
1019
1174
  }),
1020
1175
  stepStarted: Schema.Struct({
1021
- source: Schema.Literal("runner"),
1022
1176
  kind: Schema.Literal("transcript-step-started"),
1023
1177
  step: Schema.Number,
1024
1178
  message: Schema.optional(Schema.String),
1025
- timestamp: Schema.String
1179
+ ...runEventFields
1026
1180
  }),
1027
1181
  stepCompleted: Schema.Struct({
1028
- source: Schema.Literal("runner"),
1029
1182
  kind: Schema.Literal("transcript-step-completed"),
1030
1183
  step: Schema.Number,
1031
- timestamp: Schema.String
1184
+ ...runEventFields
1032
1185
  }),
1033
1186
  stepSkipped: Schema.Struct({
1034
- source: Schema.Literal("runner"),
1035
1187
  kind: Schema.Literal("transcript-step-skipped"),
1036
1188
  step: Schema.Number,
1037
- timestamp: Schema.String
1189
+ ...runEventFields
1038
1190
  }),
1039
1191
  checkpointStarted: Schema.Struct({
1040
- source: Schema.Literal("runner"),
1041
1192
  kind: Schema.Literal("checkpoint-started"),
1042
1193
  step: Schema.Number,
1043
1194
  name: Schema.String,
1044
- timestamp: Schema.String
1195
+ ...runEventFields
1045
1196
  }),
1046
1197
  checkpointCompleted: Schema.Struct({
1047
- source: Schema.Literal("runner"),
1048
1198
  kind: Schema.Literal("checkpoint-completed"),
1049
1199
  step: Schema.Number,
1050
1200
  status: Schema.Literal("passed", "failed", "skipped"),
1051
- timestamp: Schema.String
1201
+ ...runEventFields
1052
1202
  }),
1053
1203
  checkpointError: Schema.Struct({
1054
- source: Schema.Literal("runner"),
1055
1204
  kind: Schema.Literal("checkpoint-error"),
1056
1205
  step: Schema.Number,
1057
1206
  error: RecordedErrorSchema,
1058
- timestamp: Schema.String
1207
+ ...runEventFields
1059
1208
  }),
1060
1209
  scorerStarted: Schema.Struct({
1061
- source: Schema.Literal("runner"),
1062
1210
  kind: Schema.Literal("scorer-started"),
1063
1211
  scorer: Schema.String,
1064
- timestamp: Schema.String
1212
+ ...runEventFields
1065
1213
  }),
1066
1214
  scorerCompleted: Schema.Struct({
1067
- source: Schema.Literal("runner"),
1068
1215
  kind: Schema.Literal("scorer-completed"),
1069
1216
  scorer: Schema.String,
1070
1217
  value: Schema.optional(Schema.Number),
1071
1218
  skipped: Schema.optional(Schema.Literal(true)),
1072
- timestamp: Schema.String
1219
+ ...runEventFields
1073
1220
  }),
1074
1221
  scorerFailed: Schema.Struct({
1075
- source: Schema.Literal("runner"),
1076
1222
  kind: Schema.Literal("scorer-failed"),
1077
1223
  scorer: Schema.String,
1078
1224
  error: RecordedErrorSchema,
1079
- timestamp: Schema.String
1225
+ ...runEventFields
1226
+ }),
1227
+ limitReached: Schema.Struct({
1228
+ kind: Schema.Literal("limit-reached"),
1229
+ ...LimitReachedSchema.fields,
1230
+ ...runEventFields
1080
1231
  }),
1081
1232
  runCompleted: Schema.Struct({
1082
- source: Schema.Literal("runner"),
1083
1233
  kind: Schema.Literal("run-completed"),
1084
- timestamp: Schema.String
1234
+ ...runEventFields
1085
1235
  }),
1086
1236
  error: Schema.Struct({
1087
- source: Schema.Literal("runner"),
1088
1237
  kind: Schema.Literal("error"),
1089
1238
  error: RecordedErrorSchema,
1090
- timestamp: Schema.String
1239
+ ...runEventFields
1091
1240
  })
1092
1241
  };
1093
- const TrajectoryEventSchema = Schema.Union(...Object.values(autEvent), ...Object.values(runnerEvent));
1242
+ const RunEventSchema = Schema.Union(...Object.values(runnerEvent));
1094
1243
  const ApiErrorSchema = Schema.Struct({
1095
1244
  error: Schema.String,
1096
1245
  message: Schema.String
1097
1246
  });
1098
1247
  //#endregion
1099
- //#region ../core/src/trajectory.ts
1100
- /** Decode one private report's JSONL trajectory; reject malformed/non-event lines with their line number. */
1101
- function parseTrajectoryJsonl(contents) {
1102
- const events = [];
1103
- for (const [index, line] of contents.split(/\r?\n/).entries()) {
1104
- if (!line.trim()) continue;
1105
- try {
1106
- events.push(Schema.decodeUnknownSync(TrajectoryEventSchema)(JSON.parse(line)));
1107
- } catch (cause) {
1108
- throw new Error(`Invalid trajectory JSONL at line ${index + 1}`, { cause });
1109
- }
1110
- }
1111
- return events;
1112
- }
1113
- //#endregion
1114
1248
  //#region ../core/src/index.ts
1115
1249
  internalModule(import.meta.url);
1116
1250
  /** Used when an agent declares no runtimes. */
@@ -1207,6 +1341,7 @@ function defineEval(definition) {
1207
1341
  authoringId(definition);
1208
1342
  assertNoRemovedOptions(definition);
1209
1343
  if (definition.metadata !== void 0) toJsonObject(definition.metadata, `${definition.id}.metadata`);
1344
+ parseLimits(definition.limits, `Eval ${definition.id}`);
1210
1345
  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)}`);
1211
1346
  const datasetIds = /* @__PURE__ */ new Set();
1212
1347
  for (const [index, dataset] of (definition.datasets ?? []).entries()) {
@@ -1303,15 +1438,23 @@ function registerEvals(registrations) {
1303
1438
  * Metrics EvalKit records for every run; scenario metrics may not reuse
1304
1439
  * these names.
1305
1440
  *
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.
1441
+ * - `agent time` (ms): from the session's start to the end of its last turn.
1442
+ * - `tokens`: uncached input, cache writes and output, summed over the
1443
+ * transcript (`uncachedTokens`). Absent when nothing reported usage.
1444
+ * - `cached tokens`: input tokens read from a prompt cache.
1445
+ * - `output tokens`: output, reasoning included.
1446
+ * - `cache hit rate`: the share of input tokens read from a cache, 0 to 1.
1447
+ * - `steps`: assistant messages, one per model call.
1448
+ * - `tool calls`: tool calls in the transcript.
1449
+ * - `judge tokens`: `tokens` over the judges' transcripts, never the agent's.
1311
1450
  */
1312
1451
  const BUILT_IN_METRICS = [
1313
1452
  "agent time",
1314
1453
  "tokens",
1454
+ "cached tokens",
1455
+ "output tokens",
1456
+ "cache hit rate",
1457
+ "steps",
1315
1458
  "tool calls",
1316
1459
  "judge tokens"
1317
1460
  ];
@@ -1326,5 +1469,35 @@ function recordError(error) {
1326
1469
  message: String(error)
1327
1470
  };
1328
1471
  }
1472
+ /** Messages on the active path of what the agent did, optionally of one role. */
1473
+ function messages(scoring, role) {
1474
+ return transcriptMessages(scoring.transcript, role).map((record) => ({
1475
+ id: record.id,
1476
+ role: record.role,
1477
+ text: contentText(record.content),
1478
+ time: record.time,
1479
+ record
1480
+ }));
1481
+ }
1482
+ /** The agent's tool calls, in order, each with its result when recorded; optionally of one tool. */
1483
+ function toolCalls(scoring, name) {
1484
+ return transcriptToolCalls(scoring.transcript, name);
1485
+ }
1486
+ /** The text of the agent's last reply, or `''`. */
1487
+ function lastReply(scoring) {
1488
+ return transcriptReply(scoring.transcript);
1489
+ }
1490
+ /** Everything the agent's transcript reported using; undefined when nothing reported usage. */
1491
+ function usage(scoring) {
1492
+ return transcriptUsage(scoring.transcript);
1493
+ }
1494
+ /**
1495
+ * What the model saw at the end of the agent's transcript, rebuilt by ATXF
1496
+ * (compactions and context edits applied): its instructions, tools and
1497
+ * messages.
1498
+ */
1499
+ function modelContext(scoring) {
1500
+ return buildContext(scoring.transcript);
1501
+ }
1329
1502
  //#endregion
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 };
1503
+ export { DEFAULT_ARTIFACT_EXCLUDE as $, JudgeRunInfoSchema as A, definitionSite as At, RunMetadataSchema as B, ApiErrorSchema as C, limitsFromParameters as Ct, BatchSchema as D, defineDataset as Dt, AutIdentitySchema as E, resolveLimits as Et, RecordedErrorSchema as F, parseResourceUri as Ft, ScoreResultSchema as G, RunSourceSchema as H, RunDatasetSchema as I, resourceUri as It, container as J, TranscriptSchema as K, RunEventSchema as L, resourceUriSchema as Lt, LimitsSchema as M, ResourceUriSchema as Mt, MetricResultSchema as N, UuidSchema as Nt, CheckpointResultSchema as O, isDataset as Ot, REPORT_SCHEMA_VERSION as P, authorId as Pt, canonicalParameters as Q, RunIsolationSchema as R, resourceUuid as Rt, user as S, formatLimit as St, AttachmentEntrySchema as T, parseLimits as Tt, RunStatusSchema as U, RunScoringSchema as V, RunSummarySchema as W, sandboxExec as X, none as Y, defineEvalMatrix as Z, recordError as _, contentText as _t, assertJudgesIndependent as a, choices as at, toolCalls as b, parseModel as bt, defineEval as c, judgePrompt as ct, judge as d, weightedMean as dt, DEFAULT_ATTACHMENT_LIMITS as et, lastReply as f, NotJsonError as ft, predicate as g, callsIn as gt, modelContext as h, activePath as ht, agentRuntimes as i, answerInstruction as it, LimitReachedSchema as j, ResourceKindSchema as jt, JsonValueSchema as k, relativeDatasetPath as kt, defineSuite as l, passMark as lt, metric as m, toJsonObject as mt, DEFAULT_AGENT_RUNTIMES as n, catalogEntry as nt, authoringId as o, criteria as ot, messages as p, toJson as pt, UsageSchema as q, agent as r, UnreadableAnswer as rt, defineAgent as s, judgeInputNames as st, BUILT_IN_METRICS as t, defineConfig as tt, expectToolCall as u, readJudgeAnswer as ut, registerEvals as v, isTreeRecord as vt, ArtifactEntrySchema as w, parseLimitFlag as wt, usage as x, toolResultValue as xt, skip as y, modelName as yt, RunManifestSchema as z };