@leostera/evalkit 0.9.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.
@@ -0,0 +1,230 @@
1
+ //#region ../core/src/usage.ts
2
+ const BUCKETS = [
3
+ "input",
4
+ "output",
5
+ "cacheRead",
6
+ "cacheWrite",
7
+ "reasoning",
8
+ "total"
9
+ ];
10
+ /** ATXF usage from what an adapter reported; undefined when it reported nothing. */
11
+ function normalizeUsage(input) {
12
+ if (!input) return void 0;
13
+ for (const key of BUCKETS) {
14
+ const value = input[key];
15
+ if (value !== void 0 && (!Number.isFinite(value) || value < 0)) throw new Error(`Usage ${key} must be a non-negative number; received ${String(value)}`);
16
+ }
17
+ if (BUCKETS.every((key) => input[key] === void 0)) return void 0;
18
+ return {
19
+ input: input.input ?? 0,
20
+ output: input.output ?? 0,
21
+ ...input.cacheRead === void 0 ? {} : { cacheRead: input.cacheRead },
22
+ ...input.cacheWrite === void 0 ? {} : { cacheWrite: input.cacheWrite },
23
+ ...input.reasoning === void 0 ? {} : { reasoning: input.reasoning },
24
+ ...input.total === void 0 ? {} : { total: input.total }
25
+ };
26
+ }
27
+ /**
28
+ * Tokens the model had to process: uncached input, cache writes and output.
29
+ * Cache reads are left out; see `cachedTokens`. Usage that has only a total
30
+ * (from a provider, or a run saved before 0.11) counts its total less any
31
+ * cache reads.
32
+ */
33
+ function uncachedTokens(usage) {
34
+ const counted = usage.input + (usage.cacheWrite ?? 0) + usage.output;
35
+ if (counted === 0 && usage.total !== void 0) return Math.max(0, usage.total - (usage.cacheRead ?? 0));
36
+ return counted;
37
+ }
38
+ /** Bucket by bucket; a bucket absent from both stays absent. */
39
+ function addUsage(a, b) {
40
+ if (!a) return b;
41
+ if (!b) return a;
42
+ const sum = {
43
+ input: a.input + b.input,
44
+ output: a.output + b.output
45
+ };
46
+ for (const key of [
47
+ "cacheRead",
48
+ "cacheWrite",
49
+ "reasoning",
50
+ "total"
51
+ ]) if (a[key] !== void 0 || b[key] !== void 0) sum[key] = (a[key] ?? 0) + (b[key] ?? 0);
52
+ return sum;
53
+ }
54
+ /**
55
+ * A transcript's usage as ATXF totals it: usage on assistant messages,
56
+ * compactions and branch summaries, and usage records that are not running
57
+ * totals. Every record counts, abandoned branches too, since they were paid
58
+ * for. Undefined when nothing reported usage.
59
+ */
60
+ function transcriptUsage(transcript) {
61
+ let total;
62
+ for (const record of transcript.records) if (record.type === "message" && record.role === "assistant") total = addUsage(total, record.usage);
63
+ else if (record.type === "compaction" || record.type === "branch_summary") total = addUsage(total, record.usage);
64
+ else if (record.type === "usage" && !record.cumulative) total = addUsage(total, record.usage);
65
+ return total;
66
+ }
67
+ /**
68
+ * The built-in token metrics for the usage a transcript reported, each usage
69
+ * counted on its own and then summed; none when nothing reported usage.
70
+ */
71
+ function usageMetrics(usages) {
72
+ if (!usages.length) return [];
73
+ const sum = (read) => usages.reduce((total, usage) => total + read(usage), 0);
74
+ const cacheRead = sum((usage) => usage.cacheRead ?? 0);
75
+ const input = sum((usage) => usage.input + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0));
76
+ return [
77
+ {
78
+ name: "tokens",
79
+ value: sum(uncachedTokens),
80
+ unit: "tokens",
81
+ better: "lower",
82
+ builtIn: true
83
+ },
84
+ {
85
+ name: "cached tokens",
86
+ value: cacheRead,
87
+ unit: "tokens",
88
+ builtIn: true
89
+ },
90
+ {
91
+ name: "output tokens",
92
+ value: sum((usage) => usage.output),
93
+ unit: "tokens",
94
+ better: "lower",
95
+ builtIn: true
96
+ },
97
+ ...input > 0 ? [{
98
+ name: "cache hit rate",
99
+ value: cacheRead / input,
100
+ better: "higher",
101
+ builtIn: true
102
+ }] : []
103
+ ];
104
+ }
105
+ /**
106
+ * Every usage a transcript reports that ATXF counts toward its total: on
107
+ * assistant messages, compactions and branch summaries, and usage records
108
+ * that are not running totals.
109
+ */
110
+ function reportedUsages(transcript) {
111
+ const usages = [];
112
+ for (const record of transcript.records) {
113
+ const usage = record.type === "message" && record.role === "assistant" ? record.usage : record.type === "compaction" || record.type === "branch_summary" ? record.usage : record.type === "usage" && !record.cumulative ? record.usage : void 0;
114
+ if (usage) usages.push(usage);
115
+ }
116
+ return usages;
117
+ }
118
+ //#endregion
119
+ //#region ../core/src/upgrade.ts
120
+ /** Report schema versions `evalkit upgrade` converts to version 6 first. */
121
+ const UPGRADABLE_SCHEMA_VERSIONS = [
122
+ 2,
123
+ 3,
124
+ 4,
125
+ 5
126
+ ];
127
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
128
+ /**
129
+ * A version 6 run status from an old trial's status and whether it passed.
130
+ * Old `completed` meant "finished", passed or not; old `failed` meant it
131
+ * could not finish, which version 6 calls `errored`.
132
+ */
133
+ function upgradeStatus(status, passed) {
134
+ switch (status) {
135
+ case "completed": return passed ? "passed" : "failed";
136
+ case "failed": return "errored";
137
+ case "cancelled": return "cancelled";
138
+ default: return "running";
139
+ }
140
+ }
141
+ /** A trajectory event with the old trial lifecycle events renamed. */
142
+ function upgradeEvent(event) {
143
+ if (!isRecord(event)) return event;
144
+ if (event.kind === "trial-started") return {
145
+ ...event,
146
+ kind: "run-started"
147
+ };
148
+ if (event.kind === "trial-completed") return {
149
+ ...event,
150
+ kind: "run-completed"
151
+ };
152
+ return event;
153
+ }
154
+ /** A whole `trajectory.jsonl`, line by line; lines that are not JSON stay as they are. */
155
+ function upgradeTrajectory(jsonl) {
156
+ return jsonl.split("\n").map((line) => {
157
+ if (!line.trim()) return line;
158
+ try {
159
+ return JSON.stringify(upgradeEvent(JSON.parse(line)));
160
+ } catch {
161
+ return line;
162
+ }
163
+ }).join("\n");
164
+ }
165
+ /**
166
+ * A catalog (`catalog.json`, or its `catalog` list on its own) with each
167
+ * scenario's `trialCount` and `policy.trials` replaced by `repeat`.
168
+ */
169
+ function upgradeCatalog(catalog) {
170
+ const entry = (value) => {
171
+ if (!isRecord(value)) return value;
172
+ const { trialCount, policy, ...rest } = value;
173
+ const { trials, ...keptPolicy } = isRecord(policy) ? policy : {};
174
+ const repeat = typeof trialCount === "number" ? trialCount : typeof trials === "number" ? trials : value.repeat;
175
+ return {
176
+ ...rest,
177
+ ...repeat === void 0 ? {} : { repeat },
178
+ ...isRecord(policy) ? { policy: keptPolicy } : {}
179
+ };
180
+ };
181
+ if (Array.isArray(catalog)) return catalog.map(entry);
182
+ if (isRecord(catalog) && Array.isArray(catalog.catalog)) return {
183
+ ...catalog,
184
+ catalog: catalog.catalog.map(entry)
185
+ };
186
+ return catalog;
187
+ }
188
+ /** The fields of an old run manifest and trial manifest that a version 6 manifest keeps. */
189
+ function upgradeManifest(input) {
190
+ const { run, trial = {}, runId, batch, status } = input;
191
+ const pick = (source, keys) => Object.fromEntries(keys.flatMap((key) => source[key] === void 0 ? [] : [[key, source[key]]]));
192
+ return {
193
+ schemaVersion: 6,
194
+ runUri: `evalkit:run:${runId}`,
195
+ ...pick(run, [
196
+ "evalId",
197
+ "suiteId",
198
+ "parameters",
199
+ "matrix",
200
+ "aut",
201
+ "startedAt",
202
+ "source",
203
+ "process"
204
+ ]),
205
+ ...pick(trial, [
206
+ "evalId",
207
+ "parameters",
208
+ "matrix",
209
+ "aut",
210
+ "startedAt",
211
+ "workspaceIgnore",
212
+ "datasets"
213
+ ]),
214
+ ...batch ? { batch } : {},
215
+ status
216
+ };
217
+ }
218
+ /**
219
+ * Scenario options from before 0.9 that plain JavaScript scenarios can still
220
+ * carry. Ignoring them would silently change what runs, so both
221
+ * `defineEval` and the runner reject them with the replacement.
222
+ */
223
+ function assertNoRemovedOptions(definition) {
224
+ if ("fixtures" in definition) throw new Error(`Eval ${String(definition.id)} uses fixtures, which were replaced by datasets: [defineDataset(...)]`);
225
+ if ("judge" in definition) throw new Error(`Eval ${String(definition.id)} sets judge, which moved to each rule: judge(name, { agent, rubric }), or judge in evalkit.config.ts for every judge (EvalKit 0.10)`);
226
+ if (isRecord(definition.policy) && "timeoutMs" in definition.policy) throw new Error(`Eval ${String(definition.id)} sets policy.timeoutMs, which was replaced by limits: { time: ${JSON.stringify(definition.policy.timeoutMs)} }, enforced since EvalKit 0.11`);
227
+ if (isRecord(definition.policy) && "trials" in definition.policy) throw new Error(`Eval ${String(definition.id)} sets policy.trials, which was replaced by repeat: ${JSON.stringify(definition.policy.trials)} (EvalKit 0.9)`);
228
+ }
229
+ //#endregion
230
+ export { upgradeManifest as a, normalizeUsage as c, uncachedTokens as d, usageMetrics as f, upgradeEvent as i, reportedUsages as l, assertNoRemovedOptions as n, upgradeStatus as o, upgradeCatalog as r, upgradeTrajectory as s, UPGRADABLE_SCHEMA_VERSIONS as t, transcriptUsage as u };
@@ -0,0 +1,402 @@
1
+ import { d as uncachedTokens, f as usageMetrics, l as reportedUsages } from "./upgrade-rMsGzUEJ.mjs";
2
+ //#region ../core/src/upgrade-transcript.ts
3
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4
+ /** The `source.format` of a transcript converted from a version 6 trajectory. */
5
+ const V6_TRAJECTORY_FORMAT = "evalkit-trajectory-v6";
6
+ /** Old usage: input counted cached tokens, which were reported as a subset. */
7
+ function upgradeUsage(value) {
8
+ if (!isRecord(value)) return void 0;
9
+ const num = (key) => typeof value[key] === "number" ? value[key] : void 0;
10
+ const input = num("inputTokens");
11
+ const output = num("outputTokens");
12
+ const cached = num("cachedInputTokens");
13
+ const total = num("totalTokens");
14
+ if (input === void 0 && output === void 0) {
15
+ if (total === void 0) return void 0;
16
+ return {
17
+ input: 0,
18
+ output: 0,
19
+ total
20
+ };
21
+ }
22
+ const cacheRead = Math.min(cached ?? 0, input ?? 0);
23
+ return {
24
+ input: (input ?? 0) - cacheRead,
25
+ output: output ?? 0,
26
+ ...cacheRead ? { cacheRead } : {},
27
+ ...total === void 0 ? {} : { total }
28
+ };
29
+ }
30
+ function parseModel(model) {
31
+ if (typeof model !== "string" || !model) return void 0;
32
+ const slash = model.indexOf("/");
33
+ return slash > 0 && slash < model.length - 1 ? {
34
+ provider: model.slice(0, slash),
35
+ id: model.slice(slash + 1)
36
+ } : {
37
+ provider: "unknown",
38
+ id: model
39
+ };
40
+ }
41
+ const text = (value) => typeof value === "string" ? value : JSON.stringify(value ?? null);
42
+ /**
43
+ * Splits a version 6 trajectory (its events, parsed) into an ATXF
44
+ * transcript of the agent's events and EvalKit's events placed in it.
45
+ * Every record carries the old events it came from in `native`.
46
+ */
47
+ function convertTrajectory(events, run) {
48
+ const records = [];
49
+ const runEvents = [];
50
+ const recordOfEvent = /* @__PURE__ */ new Map();
51
+ let lastId = null;
52
+ let next = 0;
53
+ let sawUsage = false;
54
+ let sawTurns = false;
55
+ /** The assistant message the next tool call joins, while nothing came after it. */
56
+ let joinable;
57
+ let openTurn;
58
+ /**
59
+ * 0.10 recorded EvalKit's prompt only on its step event; it becomes the
60
+ * user message, unless the adapter recorded the same message itself.
61
+ */
62
+ let prompt;
63
+ const native = (...old) => ({
64
+ source: "evalkit",
65
+ format: V6_TRAJECTORY_FORMAT,
66
+ records: old
67
+ });
68
+ const push = (record, index) => {
69
+ const full = {
70
+ ...record,
71
+ id: `r${++next}`,
72
+ parentId: lastId
73
+ };
74
+ records.push(full);
75
+ lastId = full.id;
76
+ recordOfEvent.set(index, full.id);
77
+ joinable = void 0;
78
+ return full;
79
+ };
80
+ const flushPrompt = () => {
81
+ if (!prompt) return;
82
+ const { content, time, index, event } = prompt;
83
+ prompt = void 0;
84
+ push({
85
+ type: "message",
86
+ time,
87
+ role: "user",
88
+ content,
89
+ native: native(event)
90
+ }, index);
91
+ };
92
+ events.forEach((event, index) => {
93
+ if (!isRecord(event)) return;
94
+ const time = typeof event.timestamp === "string" ? event.timestamp : run.startedAt;
95
+ if (event.source !== "aut") {
96
+ const { source: _, ...rest } = event;
97
+ if (event.kind !== "transcript-step-started") flushPrompt();
98
+ runEvents.push({
99
+ ...rest,
100
+ at: lastId
101
+ });
102
+ if (event.kind === "transcript-step-started" && typeof event.message === "string") prompt = {
103
+ content: event.message,
104
+ time,
105
+ index,
106
+ event
107
+ };
108
+ return;
109
+ }
110
+ if (prompt && event.kind === "message" && event.role === "user" && event.content === prompt.content) prompt = void 0;
111
+ else flushPrompt();
112
+ switch (event.kind) {
113
+ case "message": {
114
+ const role = event.role;
115
+ const content = event.content;
116
+ const extra = typeof content === "string" ? {} : { content };
117
+ const ext = Object.keys(extra).length ? { ext: { evalkit: extra } } : {};
118
+ if (role === "assistant") joinable = push({
119
+ type: "message",
120
+ time,
121
+ role: "assistant",
122
+ content: [{
123
+ type: "text",
124
+ text: text(content)
125
+ }],
126
+ native: native(event),
127
+ ...ext
128
+ }, index);
129
+ else if (role === "tool") push({
130
+ type: "message",
131
+ time,
132
+ role: "tool",
133
+ toolCallId: `unknown-${index}`,
134
+ content: text(content),
135
+ native: native(event),
136
+ ...ext
137
+ }, index);
138
+ else push({
139
+ type: "message",
140
+ time,
141
+ role: role === "system" ? "system" : "user",
142
+ content: text(content),
143
+ native: native(event),
144
+ ...ext
145
+ }, index);
146
+ return;
147
+ }
148
+ case "tool-call": {
149
+ const block = {
150
+ type: "tool_call",
151
+ id: String(event.id ?? `call-${index}`),
152
+ name: String(event.name ?? "tool"),
153
+ kind: "function",
154
+ arguments: isRecord(event.arguments) ? event.arguments : { value: event.arguments ?? null }
155
+ };
156
+ if (joinable) {
157
+ joinable.content.push(block);
158
+ joinable.native.records.push(event);
159
+ recordOfEvent.set(index, joinable.id);
160
+ return;
161
+ }
162
+ joinable = push({
163
+ type: "message",
164
+ time,
165
+ role: "assistant",
166
+ content: [block],
167
+ native: native(event)
168
+ }, index);
169
+ return;
170
+ }
171
+ case "tool-result": {
172
+ const result = event.result ?? null;
173
+ push({
174
+ type: "message",
175
+ time,
176
+ role: "tool",
177
+ toolCallId: String(event.id ?? `call-${index}`),
178
+ ...typeof event.name === "string" ? { toolName: event.name } : {},
179
+ content: text(result),
180
+ ...typeof result === "string" ? {} : { details: { result } },
181
+ native: native(event)
182
+ }, index);
183
+ return;
184
+ }
185
+ case "turn-started": {
186
+ sawTurns = true;
187
+ const turnId = `turn-${String(event.turn ?? index)}`;
188
+ push({
189
+ type: "turn",
190
+ time,
191
+ phase: "start",
192
+ turnId,
193
+ native: native(event)
194
+ }, index);
195
+ openTurn = {
196
+ turnId,
197
+ from: records.length
198
+ };
199
+ return;
200
+ }
201
+ case "turn-completed": {
202
+ const usage = upgradeUsage(event.usage);
203
+ const model = parseModel(event.model);
204
+ const from = openTurn?.from ?? 0;
205
+ let holder;
206
+ for (let at = records.length - 1; at >= from; at -= 1) {
207
+ const record = records[at];
208
+ if (record.type === "message" && record.role === "assistant") {
209
+ holder = record;
210
+ break;
211
+ }
212
+ }
213
+ if (usage) sawUsage = true;
214
+ const totalOnly = usage && usage.input === 0 && usage.output === 0 && !usage.cacheRead;
215
+ if (holder && !(usage && holder.usage)) {
216
+ if (usage) holder.usage = usage;
217
+ if (model) holder.model = model;
218
+ if (totalOnly) holder.ext = {
219
+ ...holder.ext,
220
+ evalkit: {
221
+ ...holder.ext?.evalkit,
222
+ totalOnly: true
223
+ }
224
+ };
225
+ } else if (usage) push({
226
+ type: "usage",
227
+ time,
228
+ kind: "turn",
229
+ usage,
230
+ ...model ? { model } : {},
231
+ ...totalOnly ? { ext: { evalkit: { totalOnly: true } } } : {}
232
+ }, index);
233
+ const latency = typeof event.latencyMs === "number" ? { ext: { evalkit: { latencyMs: event.latencyMs } } } : {};
234
+ if (openTurn) {
235
+ push({
236
+ type: "turn",
237
+ time,
238
+ phase: "end",
239
+ turnId: openTurn.turnId,
240
+ outcome: "succeeded",
241
+ native: native(event),
242
+ ...latency
243
+ }, index);
244
+ openTurn = void 0;
245
+ } else if (holder) {
246
+ holder.native.records.push(event);
247
+ recordOfEvent.set(index, holder.id);
248
+ } else push({
249
+ type: "custom",
250
+ time,
251
+ namespace: "evalkit",
252
+ name: "turn-completed",
253
+ data: event,
254
+ native: native(event)
255
+ }, index);
256
+ return;
257
+ }
258
+ default: {
259
+ const { source: _, kind, timestamp: __, ...data } = event;
260
+ push({
261
+ type: "custom",
262
+ time,
263
+ namespace: "evalkit",
264
+ name: String(kind),
265
+ ...Object.keys(data).length ? { data } : {},
266
+ native: native(event)
267
+ }, index);
268
+ }
269
+ }
270
+ });
271
+ flushPrompt();
272
+ const firstTime = records[0]?.time ?? run.startedAt;
273
+ return {
274
+ transcript: {
275
+ header: {
276
+ type: "transcript",
277
+ atxf: "0.1",
278
+ id: run.runId,
279
+ time: firstTime,
280
+ source: {
281
+ agent: run.agent?.id ?? "unknown",
282
+ ...run.agent?.version ? { version: run.agent.version } : {},
283
+ format: V6_TRAJECTORY_FORMAT
284
+ },
285
+ fidelity: {
286
+ topology: "linear",
287
+ instructions: "none",
288
+ tools: "none",
289
+ reasoning: "none",
290
+ toolLifecycle: false,
291
+ turns: sawTurns,
292
+ usage: sawUsage ? "per-response" : "none",
293
+ native: "all",
294
+ ids: "synthetic"
295
+ },
296
+ ext: { evalkit: {
297
+ runId: run.runId,
298
+ ...run.evalId ? { scenarioId: run.evalId } : {}
299
+ } }
300
+ },
301
+ records
302
+ },
303
+ runEvents,
304
+ recordOfEvent
305
+ };
306
+ }
307
+ /**
308
+ * A version 6 `scoring.json` for version 7: each judge's events become its
309
+ * transcript and its usage moves to ATXF's buckets, each checkpoint's
310
+ * matched tool call points at the record holding it, the token metrics
311
+ * are recomputed from the converted transcript, and `steps` is added.
312
+ * Throws when the recomputed tokens (uncached plus cached)
313
+ * do not add up to the saved ones, so a run is never converted wrongly.
314
+ */
315
+ function upgradeScoring(scoring, run, recordOfEvent, agent) {
316
+ if (!isRecord(scoring)) throw new Error("scoring.json is not an object");
317
+ const judgeUsage = [];
318
+ const verdict = (entry, index) => {
319
+ if (!isRecord(entry)) return entry;
320
+ const result = { ...entry };
321
+ if (isRecord(result.matchedToolCall)) {
322
+ const { eventIndex, id } = result.matchedToolCall;
323
+ const messageId = typeof eventIndex === "number" ? recordOfEvent.get(eventIndex) : void 0;
324
+ if (messageId && typeof id === "string") result.matchedToolCall = {
325
+ messageId,
326
+ id
327
+ };
328
+ else delete result.matchedToolCall;
329
+ }
330
+ if (isRecord(result.judge)) {
331
+ const { events, usage, ...judge } = result.judge;
332
+ const converted = Array.isArray(events) ? convertTrajectory(events.map((event) => isRecord(event) ? {
333
+ ...event,
334
+ source: "aut"
335
+ } : event), {
336
+ runId: `${run.runId}-judge-${index}`,
337
+ startedAt: run.startedAt,
338
+ ...isRecord(judge.agent) && typeof judge.agent.id === "string" ? { agent: { id: judge.agent.id } } : {}
339
+ }).transcript : void 0;
340
+ const atxfUsage = upgradeUsage(usage);
341
+ if (atxfUsage) judgeUsage.push(atxfUsage);
342
+ result.judge = {
343
+ ...judge,
344
+ ...atxfUsage ? { usage: atxfUsage } : {},
345
+ ...converted ? { transcript: converted } : {}
346
+ };
347
+ }
348
+ return result;
349
+ };
350
+ const results = Array.isArray(scoring.results) ? scoring.results.map((entry, index) => verdict(entry, index)) : [];
351
+ const checkpoints = Array.isArray(scoring.checkpoints) ? scoring.checkpoints.map((entry, index) => verdict(entry, results.length + index)) : void 0;
352
+ const metrics = Array.isArray(scoring.metrics) ? upgradeMetrics(scoring.metrics, agent, judgeUsage) : void 0;
353
+ return {
354
+ ...scoring,
355
+ results,
356
+ ...checkpoints ? { checkpoints } : {},
357
+ ...metrics ? { metrics } : {}
358
+ };
359
+ }
360
+ /** Built-in metrics recomputed from the converted transcript. */
361
+ const RECOMPUTED_METRICS = /* @__PURE__ */ new Set([
362
+ "steps",
363
+ "tokens",
364
+ "cached tokens",
365
+ "output tokens",
366
+ "cache hit rate",
367
+ "judge tokens"
368
+ ]);
369
+ function upgradeMetrics(metrics, transcript, judges) {
370
+ const agent = reportedUsages(transcript);
371
+ const steps = transcript.records.filter((record) => record.type === "message" && record.role === "assistant").length;
372
+ const saved = (name) => {
373
+ const entry = metrics.find((metric) => isRecord(metric) && metric.name === name);
374
+ return isRecord(entry) && typeof entry.value === "number" ? entry.value : void 0;
375
+ };
376
+ const check = (name, before, usages) => {
377
+ if (before === void 0) return;
378
+ const after = usages.reduce((sum, usage) => sum + uncachedTokens(usage) + (usage.cacheRead ?? 0), 0);
379
+ if (after !== before) throw new Error(`${name} do not add up after converting: ${before} saved, ${after} recomputed`);
380
+ };
381
+ check("tokens", saved("tokens"), agent);
382
+ check("judge tokens", saved("judge tokens"), judges);
383
+ return [
384
+ ...metrics.filter((metric) => !(isRecord(metric) && RECOMPUTED_METRICS.has(String(metric.name)))),
385
+ {
386
+ name: "steps",
387
+ value: steps,
388
+ better: "lower",
389
+ builtIn: true
390
+ },
391
+ ...usageMetrics(agent).map((metric) => metric),
392
+ ...judges.length ? [{
393
+ name: "judge tokens",
394
+ value: judges.reduce((sum, usage) => sum + uncachedTokens(usage), 0),
395
+ unit: "tokens",
396
+ better: "lower",
397
+ builtIn: true
398
+ }] : []
399
+ ];
400
+ }
401
+ //#endregion
402
+ export { upgradeScoring as n, convertTrajectory as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leostera/evalkit",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "description": "Author and run agent evals as code",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -46,6 +46,7 @@
46
46
  "evalkit": "dist/cli.mjs"
47
47
  },
48
48
  "dependencies": {
49
+ "@leostera/atxf": "0.1.1",
49
50
  "effect": "^3.22.2",
50
51
  "hono": "^4.11.6",
51
52
  "jiti": "^2.7.0"