@vitest-evals/github-reporter 0.16.0 → 0.17.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.
- package/README.md +16 -3
- package/dist/cli.js +222 -50
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +222 -50
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +41 -4
- package/dist/index.d.ts +41 -4
- package/dist/index.js +218 -51
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +215 -50
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -53,7 +53,10 @@ type EvalReport = {
|
|
|
53
53
|
average: number;
|
|
54
54
|
minimum?: number;
|
|
55
55
|
};
|
|
56
|
-
usage
|
|
56
|
+
/** Application and other upstream usage. */
|
|
57
|
+
usage: AggregatedUsageSummary;
|
|
58
|
+
/** Judge-model usage. */
|
|
59
|
+
judgeUsage: AggregatedUsageSummary;
|
|
57
60
|
cases: EvalCase[];
|
|
58
61
|
failures: EvalCase[];
|
|
59
62
|
};
|
|
@@ -63,8 +66,11 @@ type UsageSummary = {
|
|
|
63
66
|
outputTokens?: number;
|
|
64
67
|
reasoningTokens?: number;
|
|
65
68
|
totalTokens?: number;
|
|
69
|
+
costUsd?: number;
|
|
66
70
|
toolCalls?: number;
|
|
67
71
|
};
|
|
72
|
+
/** Aggregated usage; cost stays absent when no run reported one. */
|
|
73
|
+
type AggregatedUsageSummary = Required<Omit<UsageSummary, "costUsd">> & Pick<UsageSummary, "costUsd">;
|
|
68
74
|
/** Tool-call summary shown in reporter output. */
|
|
69
75
|
type ToolCallSummary = {
|
|
70
76
|
name: string;
|
|
@@ -171,6 +177,8 @@ type PublishCheckRunOptions = SummaryOptions & {
|
|
|
171
177
|
sha?: string;
|
|
172
178
|
name?: string;
|
|
173
179
|
apiUrl?: string;
|
|
180
|
+
detailsUrl?: string;
|
|
181
|
+
externalId?: string;
|
|
174
182
|
checkRunId?: number;
|
|
175
183
|
maxAnnotations?: number;
|
|
176
184
|
/**
|
|
@@ -187,7 +195,26 @@ type PublishCheckRunResult = {
|
|
|
187
195
|
status: "created" | "updated";
|
|
188
196
|
id?: number;
|
|
189
197
|
htmlUrl?: string;
|
|
198
|
+
sha?: string;
|
|
190
199
|
};
|
|
200
|
+
/**
|
|
201
|
+
* Resolve the commit SHA a Check Run should attach to.
|
|
202
|
+
*
|
|
203
|
+
* On `pull_request`, `GITHUB_SHA` is the temporary merge commit. PR status and
|
|
204
|
+
* required checks attach to the head commit, so prefer an explicit head SHA
|
|
205
|
+
* (option/env or `pull_request.head.sha` from the event payload) first.
|
|
206
|
+
*/
|
|
207
|
+
declare function resolveCheckSha(env?: NodeJS.ProcessEnv, options?: {
|
|
208
|
+
sha?: string;
|
|
209
|
+
eventPath?: string;
|
|
210
|
+
}): string | undefined;
|
|
211
|
+
/**
|
|
212
|
+
* Build a Check Run details URL that points back at the current workflow job
|
|
213
|
+
* (or run) when GitHub Actions env is present.
|
|
214
|
+
*/
|
|
215
|
+
declare function resolveCheckDetailsUrl(env?: NodeJS.ProcessEnv, options?: {
|
|
216
|
+
detailsUrl?: string;
|
|
217
|
+
}): string | undefined;
|
|
191
218
|
/** Publishes the eval report to a GitHub Check Run when configuration allows it. */
|
|
192
219
|
declare function publishCheckRun(report: EvalReport, options?: PublishCheckRunOptions): Promise<PublishCheckRunResult>;
|
|
193
220
|
|
|
@@ -201,12 +228,21 @@ type PublishEvalReportOptions = SummaryOptions & EvalGatePolicy & {
|
|
|
201
228
|
annotations?: boolean;
|
|
202
229
|
checkRun?: boolean;
|
|
203
230
|
failOnCheckError?: boolean;
|
|
231
|
+
/**
|
|
232
|
+
* When true, keep the workflow step green on a failed gate so the Check
|
|
233
|
+
* Run owns PR status instead of a canned job failure line. Defaults to
|
|
234
|
+
* true only when a Check Run is actually published; if publishing is
|
|
235
|
+
* skipped/fails, the step still exits non-zero on a failed gate.
|
|
236
|
+
*/
|
|
237
|
+
softFail?: boolean;
|
|
204
238
|
maxAnnotations?: number;
|
|
205
239
|
checkRunId?: number;
|
|
206
240
|
checkName?: string;
|
|
207
241
|
token?: string;
|
|
208
242
|
repository?: string;
|
|
209
243
|
sha?: string;
|
|
244
|
+
detailsUrl?: string;
|
|
245
|
+
externalId?: string;
|
|
210
246
|
warn?: (message: string) => void;
|
|
211
247
|
};
|
|
212
248
|
/** Result from publishing eval reports, including merged report data. */
|
|
@@ -215,8 +251,9 @@ type PublishEvalReportResult = {
|
|
|
215
251
|
resultFiles: string[];
|
|
216
252
|
gate: EvalGateResult;
|
|
217
253
|
/**
|
|
218
|
-
* True when an enforced gate rejects the report
|
|
219
|
-
* never fail the step, matching
|
|
254
|
+
* True when an enforced gate rejects the report and soft-fail did not keep
|
|
255
|
+
* the step green. Advisory (ungated) runs never fail the step, matching
|
|
256
|
+
* `fail-on-failures: false` defaults.
|
|
220
257
|
*/
|
|
221
258
|
shouldFail: boolean;
|
|
222
259
|
checkRun?: PublishCheckRunResult;
|
|
@@ -224,4 +261,4 @@ type PublishEvalReportResult = {
|
|
|
224
261
|
/** Reads, merges, and publishes eval reports to GitHub Actions surfaces. */
|
|
225
262
|
declare function publishEvalReport(options: PublishEvalReportOptions): Promise<PublishEvalReportResult>;
|
|
226
263
|
|
|
227
|
-
export { type AnnotationOptions, type CheckAnnotation, type EvalCase, type EvalFailure, type EvalGatePolicy, type EvalGateResult, type EvalReport, type PublishCheckRunOptions, type PublishCheckRunResult, type PublishEvalReportOptions, type PublishEvalReportResult, type SummaryOptions, buildCheckAnnotations, collectEvalReport, computePassRate, evaluateEvalGate, formatPercent, publishCheckRun, publishEvalReport, renderGateWorkflowCommand, renderJobSummary, renderWorkflowCommands };
|
|
264
|
+
export { type AnnotationOptions, type CheckAnnotation, type EvalCase, type EvalFailure, type EvalGatePolicy, type EvalGateResult, type EvalReport, type PublishCheckRunOptions, type PublishCheckRunResult, type PublishEvalReportOptions, type PublishEvalReportResult, type SummaryOptions, buildCheckAnnotations, collectEvalReport, computePassRate, evaluateEvalGate, formatPercent, publishCheckRun, publishEvalReport, renderGateWorkflowCommand, renderJobSummary, renderWorkflowCommands, resolveCheckDetailsUrl, resolveCheckSha };
|
package/dist/index.js
CHANGED
|
@@ -29,7 +29,9 @@ __export(index_exports, {
|
|
|
29
29
|
publishEvalReport: () => publishEvalReport,
|
|
30
30
|
renderGateWorkflowCommand: () => renderGateWorkflowCommand,
|
|
31
31
|
renderJobSummary: () => renderJobSummary,
|
|
32
|
-
renderWorkflowCommands: () => renderWorkflowCommands
|
|
32
|
+
renderWorkflowCommands: () => renderWorkflowCommands,
|
|
33
|
+
resolveCheckDetailsUrl: () => resolveCheckDetailsUrl,
|
|
34
|
+
resolveCheckSha: () => resolveCheckSha
|
|
33
35
|
});
|
|
34
36
|
module.exports = __toCommonJS(index_exports);
|
|
35
37
|
|
|
@@ -126,7 +128,8 @@ function collectEvalReport(input, options = {}) {
|
|
|
126
128
|
const cases = workspace.cases.map(collectEvalCase);
|
|
127
129
|
const failures = cases.filter((testCase) => testCase.status === "failed");
|
|
128
130
|
const evalScores = cases.map((testCase) => testCase.eval?.avgScore).filter((score) => isFiniteNumber(score));
|
|
129
|
-
const usage =
|
|
131
|
+
const usage = sumAppUsage(cases);
|
|
132
|
+
const judgeUsage = sumJudgeUsage(cases);
|
|
130
133
|
const durationMs = workspace.runs[0]?.durationMs;
|
|
131
134
|
return {
|
|
132
135
|
status: input.success && failures.length === 0 ? "passed" : "failed",
|
|
@@ -146,6 +149,7 @@ function collectEvalReport(input, options = {}) {
|
|
|
146
149
|
minimum: Math.min(...evalScores)
|
|
147
150
|
} : void 0,
|
|
148
151
|
usage,
|
|
152
|
+
judgeUsage,
|
|
149
153
|
cases,
|
|
150
154
|
failures
|
|
151
155
|
};
|
|
@@ -270,24 +274,67 @@ function stringifyReason(value) {
|
|
|
270
274
|
}
|
|
271
275
|
return typeof value === "string" ? value : stringifyValue(value, 4e3);
|
|
272
276
|
}
|
|
273
|
-
function
|
|
274
|
-
|
|
277
|
+
function emptyUsage() {
|
|
278
|
+
return {
|
|
275
279
|
inputTokens: 0,
|
|
276
280
|
outputTokens: 0,
|
|
277
281
|
reasoningTokens: 0,
|
|
278
282
|
totalTokens: 0,
|
|
279
283
|
toolCalls: 0
|
|
280
284
|
};
|
|
285
|
+
}
|
|
286
|
+
function addRunUsage(total, usage) {
|
|
287
|
+
total.inputTokens += usage?.inputTokens ?? 0;
|
|
288
|
+
total.outputTokens += usage?.outputTokens ?? 0;
|
|
289
|
+
total.reasoningTokens += usage?.reasoningTokens ?? 0;
|
|
290
|
+
total.totalTokens += usage?.totalTokens ?? (usage?.inputTokens ?? 0) + (usage?.outputTokens ?? 0) + (usage?.reasoningTokens ?? 0);
|
|
291
|
+
if (usage?.costUsd !== void 0) {
|
|
292
|
+
total.costUsd = (total.costUsd ?? 0) + usage.costUsd;
|
|
293
|
+
}
|
|
294
|
+
total.toolCalls += usage?.toolCalls ?? 0;
|
|
295
|
+
}
|
|
296
|
+
function sumAppUsage(cases) {
|
|
297
|
+
const usage = emptyUsage();
|
|
298
|
+
const runUsages = cases.map(appUsageForCase).filter((item) => item !== void 0);
|
|
299
|
+
for (const runUsage of runUsages) {
|
|
300
|
+
addRunUsage(usage, runUsage);
|
|
301
|
+
}
|
|
302
|
+
omitPartialCost(usage, runUsages);
|
|
303
|
+
return usage;
|
|
304
|
+
}
|
|
305
|
+
function appUsageForCase(testCase) {
|
|
306
|
+
const usage = testCase.harness?.usage;
|
|
307
|
+
const effectiveToolCalls = toolCallCount(testCase);
|
|
308
|
+
if (!usage && effectiveToolCalls === 0) {
|
|
309
|
+
return void 0;
|
|
310
|
+
}
|
|
311
|
+
return {
|
|
312
|
+
...usage,
|
|
313
|
+
...effectiveToolCalls > 0 ? { toolCalls: effectiveToolCalls } : {}
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
function sumJudgeUsage(cases) {
|
|
317
|
+
const usage = emptyUsage();
|
|
318
|
+
const runUsages = [];
|
|
281
319
|
for (const testCase of cases) {
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
320
|
+
for (const score of testCase.eval?.scores ?? []) {
|
|
321
|
+
for (const run of score.judgeRuns ?? []) {
|
|
322
|
+
runUsages.push(run.usage);
|
|
323
|
+
addRunUsage(usage, run.usage);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
288
326
|
}
|
|
327
|
+
omitPartialCost(usage, runUsages);
|
|
289
328
|
return usage;
|
|
290
329
|
}
|
|
330
|
+
function omitPartialCost(total, usages) {
|
|
331
|
+
if (usages.some(hasUsageWithoutCost)) {
|
|
332
|
+
total.costUsd = void 0;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
function hasUsageWithoutCost(usage) {
|
|
336
|
+
return usage?.costUsd === void 0 && ((usage?.totalTokens ?? (usage?.inputTokens ?? 0) + (usage?.outputTokens ?? 0) + (usage?.reasoningTokens ?? 0)) > 0 || (usage?.toolCalls ?? 0) > 0);
|
|
337
|
+
}
|
|
291
338
|
function toolCallCount(testCase) {
|
|
292
339
|
const usageToolCalls = testCase.harness?.usage?.toolCalls;
|
|
293
340
|
if (usageToolCalls !== void 0) {
|
|
@@ -431,8 +478,8 @@ function evaluateEvalGate(report, policy = {}) {
|
|
|
431
478
|
status: "failed",
|
|
432
479
|
enforced: true,
|
|
433
480
|
passRate,
|
|
434
|
-
title: "Eval
|
|
435
|
-
message: `${formatNumber(nonEvalFailures)}
|
|
481
|
+
title: "Eval run failed",
|
|
482
|
+
message: `${formatNumber(nonEvalFailures)} test failure${nonEvalFailures === 1 ? "" : "s"} outside eval cases; ${counts}`
|
|
436
483
|
};
|
|
437
484
|
}
|
|
438
485
|
if (report.totals.evalTotal === 0) {
|
|
@@ -441,7 +488,7 @@ function evaluateEvalGate(report, policy = {}) {
|
|
|
441
488
|
status: "failed",
|
|
442
489
|
enforced: true,
|
|
443
490
|
passRate: null,
|
|
444
|
-
title: "Eval
|
|
491
|
+
title: "Eval run failed",
|
|
445
492
|
message: "no eval cases were reported"
|
|
446
493
|
};
|
|
447
494
|
}
|
|
@@ -451,8 +498,8 @@ function evaluateEvalGate(report, policy = {}) {
|
|
|
451
498
|
status: "failed",
|
|
452
499
|
enforced: true,
|
|
453
500
|
passRate,
|
|
454
|
-
title: "Eval
|
|
455
|
-
message: `
|
|
501
|
+
title: "Eval run failed",
|
|
502
|
+
message: `Vitest failed without reporting a failed test; ${counts}`
|
|
456
503
|
};
|
|
457
504
|
}
|
|
458
505
|
if (minPassRate !== void 0 && (passRate === null || passRate + Number.EPSILON < minPassRate)) {
|
|
@@ -462,7 +509,7 @@ function evaluateEvalGate(report, policy = {}) {
|
|
|
462
509
|
enforced: true,
|
|
463
510
|
passRate,
|
|
464
511
|
title: `Eval pass rate ${formatPercent(passRate)} \u2014 required ${formatPercent(minPassRate)}`,
|
|
465
|
-
message: `
|
|
512
|
+
message: `pass rate is below the minimum: ${counts}; minimum ${formatPercent(minPassRate)}`
|
|
466
513
|
};
|
|
467
514
|
}
|
|
468
515
|
if (minScoreAverage !== void 0) {
|
|
@@ -473,8 +520,8 @@ function evaluateEvalGate(report, policy = {}) {
|
|
|
473
520
|
status: "failed",
|
|
474
521
|
enforced: true,
|
|
475
522
|
passRate,
|
|
476
|
-
title: "
|
|
477
|
-
message: `no score
|
|
523
|
+
title: "Average score unavailable",
|
|
524
|
+
message: `no average score was reported; minimum ${formatScore(minScoreAverage)}`
|
|
478
525
|
};
|
|
479
526
|
}
|
|
480
527
|
if (average + Number.EPSILON < minScoreAverage) {
|
|
@@ -483,8 +530,8 @@ function evaluateEvalGate(report, policy = {}) {
|
|
|
483
530
|
status: "failed",
|
|
484
531
|
enforced: true,
|
|
485
532
|
passRate,
|
|
486
|
-
title: `
|
|
487
|
-
message: `
|
|
533
|
+
title: `Average score ${formatScore(average)} \u2014 required ${formatScore(minScoreAverage)}`,
|
|
534
|
+
message: `average score is below the minimum: ${counts}; minimum ${formatScore(minScoreAverage)}`
|
|
488
535
|
};
|
|
489
536
|
}
|
|
490
537
|
}
|
|
@@ -494,7 +541,7 @@ function evaluateEvalGate(report, policy = {}) {
|
|
|
494
541
|
enforced: true,
|
|
495
542
|
passRate,
|
|
496
543
|
title: enforcedPassTitle(report, passRate, minPassRate, minScoreAverage),
|
|
497
|
-
message: `
|
|
544
|
+
message: `requirements met: ${counts}${formatMinimumSuffix(minPassRate, minScoreAverage)}`
|
|
498
545
|
};
|
|
499
546
|
}
|
|
500
547
|
function computePassRate(report) {
|
|
@@ -527,7 +574,7 @@ function resolveMinPassRate(policy) {
|
|
|
527
574
|
}
|
|
528
575
|
function defaultCheckTitle(report) {
|
|
529
576
|
if (report.failures.length === 0 && report.status === "passed") {
|
|
530
|
-
return "No eval
|
|
577
|
+
return "No eval cases failed";
|
|
531
578
|
}
|
|
532
579
|
if (report.failures.length === 0) {
|
|
533
580
|
return "Vitest run failed";
|
|
@@ -536,10 +583,10 @@ function defaultCheckTitle(report) {
|
|
|
536
583
|
}
|
|
537
584
|
function enforcedPassTitle(report, passRate, minPassRate, minScoreAverage) {
|
|
538
585
|
if (minPassRate !== void 0) {
|
|
539
|
-
return `Eval pass rate ${formatPercent(passRate)} \u2014
|
|
586
|
+
return `Eval pass rate ${formatPercent(passRate)} \u2014 minimum ${formatPercent(minPassRate)}`;
|
|
540
587
|
}
|
|
541
588
|
if (minScoreAverage !== void 0) {
|
|
542
|
-
return `
|
|
589
|
+
return `Average score ${formatScore(report.score?.average)} \u2014 minimum ${formatScore(minScoreAverage)}`;
|
|
543
590
|
}
|
|
544
591
|
return defaultCheckTitle(report);
|
|
545
592
|
}
|
|
@@ -548,19 +595,22 @@ function formatEvalCounts(report, passRate) {
|
|
|
548
595
|
const passRateText = passRate === null ? "n/a" : formatPercent(passRate);
|
|
549
596
|
return `${formatNumber(report.totals.evalPassed)}/${formatNumber(
|
|
550
597
|
report.totals.evalTotal
|
|
551
|
-
)} passed (${passRateText}),
|
|
598
|
+
)} passed (${passRateText}), average score ${scoreText}`;
|
|
552
599
|
}
|
|
553
|
-
function
|
|
600
|
+
function formatMinimumSuffix(minPassRate, minScoreAverage) {
|
|
554
601
|
const parts = [];
|
|
555
602
|
if (minPassRate !== void 0) {
|
|
556
|
-
parts.push(`pass rate
|
|
603
|
+
parts.push(`minimum pass rate ${formatPercent(minPassRate)}`);
|
|
557
604
|
}
|
|
558
605
|
if (minScoreAverage !== void 0) {
|
|
559
|
-
parts.push(`
|
|
606
|
+
parts.push(`minimum average score ${formatScore(minScoreAverage)}`);
|
|
560
607
|
}
|
|
561
608
|
return parts.length === 0 ? "" : `; ${parts.join(", ")}`;
|
|
562
609
|
}
|
|
563
610
|
|
|
611
|
+
// src/github.ts
|
|
612
|
+
var import_node_fs = require("fs");
|
|
613
|
+
|
|
564
614
|
// src/summary.ts
|
|
565
615
|
var DEFAULT_MAX_FAILURES = 20;
|
|
566
616
|
var DEFAULT_MAX_REASON_CHARS = 8e3;
|
|
@@ -588,23 +638,23 @@ function renderJobSummary(report, options = {}) {
|
|
|
588
638
|
""
|
|
589
639
|
];
|
|
590
640
|
if (report.failures.length > 0) {
|
|
591
|
-
const failureHeading = options.gate?.ok === true ? "###
|
|
641
|
+
const failureHeading = options.gate?.ok === true ? "### Cases Below Target" : "### Failures";
|
|
592
642
|
lines.push(failureHeading, "");
|
|
593
643
|
failures.forEach((testCase, index) => {
|
|
594
644
|
lines.push(...renderFailureDetails(testCase, index + 1, options), "");
|
|
595
645
|
});
|
|
596
646
|
if (report.failures.length > failures.length) {
|
|
597
|
-
const omittedLabel = options.gate?.ok === true ? "
|
|
647
|
+
const omittedLabel = options.gate?.ok === true ? "cases below target" : "failures";
|
|
598
648
|
lines.push(
|
|
599
649
|
`${report.failures.length - failures.length} more ${omittedLabel} omitted from this summary.`,
|
|
600
650
|
""
|
|
601
651
|
);
|
|
602
652
|
}
|
|
603
653
|
} else if (report.totals.evalTotal > 0) {
|
|
604
|
-
lines.push("### Failures", "", "No eval
|
|
654
|
+
lines.push("### Failures", "", "No eval cases failed.", "");
|
|
605
655
|
}
|
|
606
656
|
if (report.totals.evalTotal === 0) {
|
|
607
|
-
lines.push("No eval
|
|
657
|
+
lines.push("No eval results were found in the Vitest JSON report.", "");
|
|
608
658
|
}
|
|
609
659
|
return `${lines.join("\n")}
|
|
610
660
|
`;
|
|
@@ -614,9 +664,9 @@ function formatCountLine(passed, failed, total) {
|
|
|
614
664
|
}
|
|
615
665
|
function renderSummaryTable(report, nonEvalFailures, gate) {
|
|
616
666
|
const rows = [
|
|
617
|
-
["Status", gate?.status ?? report.status],
|
|
667
|
+
["Status", capitalize(gate?.status ?? report.status)],
|
|
618
668
|
[
|
|
619
|
-
"
|
|
669
|
+
"Eval cases",
|
|
620
670
|
formatCountLine(
|
|
621
671
|
report.totals.evalPassed,
|
|
622
672
|
report.totals.evalFailed,
|
|
@@ -625,23 +675,31 @@ function renderSummaryTable(report, nonEvalFailures, gate) {
|
|
|
625
675
|
]
|
|
626
676
|
];
|
|
627
677
|
if (gate?.passRate !== void 0 && gate.passRate !== null) {
|
|
628
|
-
rows.push(["Pass
|
|
678
|
+
rows.push(["Pass rate", formatPercent(gate.passRate)]);
|
|
629
679
|
} else if (report.totals.evalTotal > 0) {
|
|
630
680
|
rows.push([
|
|
631
|
-
"Pass
|
|
681
|
+
"Pass rate",
|
|
632
682
|
formatPercent(report.totals.evalPassed / report.totals.evalTotal)
|
|
633
683
|
]);
|
|
634
684
|
}
|
|
635
685
|
if (report.score) {
|
|
636
686
|
rows.push(["Score", formatScoreSummary(report.score)]);
|
|
637
687
|
}
|
|
688
|
+
if (hasUsage(report.usage) || hasUsage(report.judgeUsage)) {
|
|
689
|
+
rows.push(["App usage", formatUsage(report.usage)]);
|
|
690
|
+
rows.push(["Judge usage", formatUsage(report.judgeUsage)]);
|
|
691
|
+
rows.push([
|
|
692
|
+
"Total usage",
|
|
693
|
+
formatUsage(sumUsage(report.usage, report.judgeUsage))
|
|
694
|
+
]);
|
|
695
|
+
}
|
|
638
696
|
if (gate?.enforced) {
|
|
639
|
-
rows.push(["
|
|
697
|
+
rows.push(["Requirements", gate.message]);
|
|
640
698
|
}
|
|
641
699
|
if (nonEvalFailures > 0) {
|
|
642
700
|
rows.push([
|
|
643
|
-
"Other
|
|
644
|
-
`${formatNumber(nonEvalFailures)}
|
|
701
|
+
"Other test failures",
|
|
702
|
+
`${formatNumber(nonEvalFailures)} test failure${nonEvalFailures === 1 ? "" : "s"} outside eval cases`
|
|
645
703
|
]);
|
|
646
704
|
}
|
|
647
705
|
rows.push(["Duration", formatDuration(report.durationMs)]);
|
|
@@ -654,7 +712,53 @@ function renderSummaryTable(report, nonEvalFailures, gate) {
|
|
|
654
712
|
];
|
|
655
713
|
}
|
|
656
714
|
function formatScoreSummary(score) {
|
|
657
|
-
return `
|
|
715
|
+
return `average ${formatScore(score.average)}${score.minimum === void 0 ? "" : `, lowest ${formatScore(score.minimum)}`}`;
|
|
716
|
+
}
|
|
717
|
+
function capitalize(value) {
|
|
718
|
+
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
719
|
+
}
|
|
720
|
+
function hasUsage(usage) {
|
|
721
|
+
return usage.totalTokens > 0 || usage.costUsd !== void 0 || usage.toolCalls > 0;
|
|
722
|
+
}
|
|
723
|
+
function sumUsage(app, judge) {
|
|
724
|
+
const appCostKnown = !hasUsageWithoutCost2(app);
|
|
725
|
+
const judgeCostKnown = !hasUsageWithoutCost2(judge);
|
|
726
|
+
return {
|
|
727
|
+
inputTokens: app.inputTokens + judge.inputTokens,
|
|
728
|
+
outputTokens: app.outputTokens + judge.outputTokens,
|
|
729
|
+
reasoningTokens: app.reasoningTokens + judge.reasoningTokens,
|
|
730
|
+
totalTokens: app.totalTokens + judge.totalTokens,
|
|
731
|
+
...appCostKnown && judgeCostKnown ? { costUsd: (app.costUsd ?? 0) + (judge.costUsd ?? 0) } : {},
|
|
732
|
+
toolCalls: app.toolCalls + judge.toolCalls
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
function hasUsageWithoutCost2(usage) {
|
|
736
|
+
return usage.costUsd === void 0 && (usage.totalTokens > 0 || usage.toolCalls > 0);
|
|
737
|
+
}
|
|
738
|
+
function formatUsage(usage) {
|
|
739
|
+
const parts = [];
|
|
740
|
+
if (usage.totalTokens > 0) {
|
|
741
|
+
parts.push(`${formatNumber(usage.totalTokens)} tokens`);
|
|
742
|
+
}
|
|
743
|
+
if (usage.costUsd !== void 0) {
|
|
744
|
+
parts.push(
|
|
745
|
+
usage.costUsd.toLocaleString("en-US", {
|
|
746
|
+
style: "currency",
|
|
747
|
+
currency: "USD",
|
|
748
|
+
minimumFractionDigits: 2,
|
|
749
|
+
maximumFractionDigits: 6
|
|
750
|
+
})
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
if (hasUsageWithoutCost2(usage)) {
|
|
754
|
+
parts.push("cost unavailable");
|
|
755
|
+
}
|
|
756
|
+
if (usage.toolCalls > 0) {
|
|
757
|
+
parts.push(
|
|
758
|
+
`${formatNumber(usage.toolCalls)} tool call${usage.toolCalls === 1 ? "" : "s"}`
|
|
759
|
+
);
|
|
760
|
+
}
|
|
761
|
+
return parts.join(", ") || "none";
|
|
658
762
|
}
|
|
659
763
|
function escapeTableCell(value) {
|
|
660
764
|
return value.replace(/\r?\n/g, " ").replace(/\\/g, "\\\\").replace(/\|/g, "\\|");
|
|
@@ -734,7 +838,7 @@ function renderFailureBlock(testCase, {
|
|
|
734
838
|
["Case", `${number}. ${testCase.displayName}`],
|
|
735
839
|
["Status", testCase.status],
|
|
736
840
|
["Location", formatLocation(testCase.displayFile, testCase.location)],
|
|
737
|
-
["
|
|
841
|
+
["App runner", testCase.harness?.name ?? "n/a"],
|
|
738
842
|
["Score", formatScore(failure?.score ?? testCase.eval?.avgScore)],
|
|
739
843
|
["Judge", failure?.judgeName ?? "n/a"]
|
|
740
844
|
];
|
|
@@ -772,7 +876,7 @@ function renderFailureBlock(testCase, {
|
|
|
772
876
|
if (finalOutput !== void 0) {
|
|
773
877
|
lines.push(
|
|
774
878
|
...renderAsciiSection(
|
|
775
|
-
"
|
|
879
|
+
"Output",
|
|
776
880
|
stringifyValue(finalOutput, maxOutputChars).split(/\r?\n/)
|
|
777
881
|
),
|
|
778
882
|
""
|
|
@@ -800,7 +904,7 @@ function renderFailureBlock(testCase, {
|
|
|
800
904
|
if (testCase.harness?.errors.length) {
|
|
801
905
|
lines.push(
|
|
802
906
|
...renderAsciiSection(
|
|
803
|
-
"
|
|
907
|
+
"App errors",
|
|
804
908
|
stringifyValue(testCase.harness.errors, maxReasonChars).split(/\r?\n/)
|
|
805
909
|
),
|
|
806
910
|
""
|
|
@@ -849,7 +953,9 @@ function formatCaseUsage(testCase) {
|
|
|
849
953
|
parts.push(`${formatNumber(totalTokens)} tokens`);
|
|
850
954
|
}
|
|
851
955
|
if (toolCalls > 0) {
|
|
852
|
-
parts.push(
|
|
956
|
+
parts.push(
|
|
957
|
+
`${formatNumber(toolCalls)} tool call${toolCalls === 1 ? "" : "s"}`
|
|
958
|
+
);
|
|
853
959
|
}
|
|
854
960
|
if (testCase.harness?.timingMs !== void 0) {
|
|
855
961
|
parts.push(formatDuration(testCase.harness.timingMs));
|
|
@@ -861,10 +967,44 @@ function formatCaseUsage(testCase) {
|
|
|
861
967
|
var DEFAULT_CHECK_NAME = "vitest-evals";
|
|
862
968
|
var MAX_CHECK_SUMMARY_LENGTH = 64e3;
|
|
863
969
|
var CHECK_SUMMARY_TRUNCATION_SUFFIX = "\n\n[truncated for GitHub Check Run]\n";
|
|
970
|
+
function resolveCheckSha(env = process.env, options = {}) {
|
|
971
|
+
const explicit = options.sha?.trim() || env.GITHUB_PR_HEAD_SHA?.trim();
|
|
972
|
+
if (explicit) {
|
|
973
|
+
return explicit;
|
|
974
|
+
}
|
|
975
|
+
const eventPath = options.eventPath?.trim() || env.GITHUB_EVENT_PATH?.trim();
|
|
976
|
+
if (eventPath) {
|
|
977
|
+
try {
|
|
978
|
+
const event = JSON.parse((0, import_node_fs.readFileSync)(eventPath, "utf8"));
|
|
979
|
+
const headSha = event.pull_request?.head?.sha;
|
|
980
|
+
if (typeof headSha === "string" && headSha.trim()) {
|
|
981
|
+
return headSha.trim();
|
|
982
|
+
}
|
|
983
|
+
} catch {
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
return env.GITHUB_SHA?.trim() || void 0;
|
|
987
|
+
}
|
|
988
|
+
function resolveCheckDetailsUrl(env = process.env, options = {}) {
|
|
989
|
+
const explicit = options.detailsUrl?.trim();
|
|
990
|
+
if (explicit) {
|
|
991
|
+
return explicit;
|
|
992
|
+
}
|
|
993
|
+
const server = env.GITHUB_SERVER_URL?.replace(/\/$/, "");
|
|
994
|
+
const repository = env.GITHUB_REPOSITORY?.trim();
|
|
995
|
+
const runId = env.GITHUB_RUN_ID?.trim();
|
|
996
|
+
if (!server || !repository || !runId) {
|
|
997
|
+
return void 0;
|
|
998
|
+
}
|
|
999
|
+
return `${server}/${repository}/actions/runs/${runId}`;
|
|
1000
|
+
}
|
|
864
1001
|
async function publishCheckRun(report, options = {}) {
|
|
865
1002
|
const token = options.token ?? process.env.GITHUB_TOKEN;
|
|
866
1003
|
const repository = options.repository ?? process.env.GITHUB_REPOSITORY;
|
|
867
|
-
const sha = options.sha
|
|
1004
|
+
const sha = resolveCheckSha(process.env, { sha: options.sha });
|
|
1005
|
+
const detailsUrl = resolveCheckDetailsUrl(process.env, {
|
|
1006
|
+
detailsUrl: options.detailsUrl
|
|
1007
|
+
});
|
|
868
1008
|
if (!token) {
|
|
869
1009
|
return { status: "skipped", reason: "missing GITHUB_TOKEN" };
|
|
870
1010
|
}
|
|
@@ -872,7 +1012,10 @@ async function publishCheckRun(report, options = {}) {
|
|
|
872
1012
|
return { status: "skipped", reason: "missing GITHUB_REPOSITORY" };
|
|
873
1013
|
}
|
|
874
1014
|
if (!sha && options.checkRunId === void 0) {
|
|
875
|
-
return {
|
|
1015
|
+
return {
|
|
1016
|
+
status: "skipped",
|
|
1017
|
+
reason: "missing commit SHA (set --sha / options.sha, GITHUB_PR_HEAD_SHA, pull_request.head.sha, or GITHUB_SHA)"
|
|
1018
|
+
};
|
|
876
1019
|
}
|
|
877
1020
|
const [owner, repo] = repository.split("/");
|
|
878
1021
|
if (!owner || !repo) {
|
|
@@ -881,7 +1024,7 @@ async function publishCheckRun(report, options = {}) {
|
|
|
881
1024
|
reason: `invalid GitHub repository: ${repository}`
|
|
882
1025
|
};
|
|
883
1026
|
}
|
|
884
|
-
const payload = buildCheckRunPayload(report, options);
|
|
1027
|
+
const payload = buildCheckRunPayload(report, options, detailsUrl);
|
|
885
1028
|
const apiUrl = options.apiUrl ?? process.env.GITHUB_API_URL ?? "https://api.github.com";
|
|
886
1029
|
const requestUrl = options.checkRunId === void 0 ? `${apiUrl}/repos/${owner}/${repo}/check-runs` : `${apiUrl}/repos/${owner}/${repo}/check-runs/${options.checkRunId}`;
|
|
887
1030
|
const response = await fetch(requestUrl, {
|
|
@@ -896,6 +1039,7 @@ async function publishCheckRun(report, options = {}) {
|
|
|
896
1039
|
options.checkRunId === void 0 ? {
|
|
897
1040
|
name: options.name ?? DEFAULT_CHECK_NAME,
|
|
898
1041
|
head_sha: sha,
|
|
1042
|
+
...options.externalId ? { external_id: options.externalId } : {},
|
|
899
1043
|
...payload
|
|
900
1044
|
} : payload
|
|
901
1045
|
)
|
|
@@ -910,10 +1054,11 @@ async function publishCheckRun(report, options = {}) {
|
|
|
910
1054
|
return {
|
|
911
1055
|
status: options.checkRunId === void 0 ? "created" : "updated",
|
|
912
1056
|
id: data.id,
|
|
913
|
-
htmlUrl: data.html_url
|
|
1057
|
+
htmlUrl: data.html_url,
|
|
1058
|
+
sha
|
|
914
1059
|
};
|
|
915
1060
|
}
|
|
916
|
-
function buildCheckRunPayload(report, options) {
|
|
1061
|
+
function buildCheckRunPayload(report, options, detailsUrl) {
|
|
917
1062
|
const gate = options.gate ?? evaluateEvalGate(report);
|
|
918
1063
|
const annotations = buildCheckAnnotations(report, {
|
|
919
1064
|
maxAnnotations: options.maxAnnotations,
|
|
@@ -923,6 +1068,7 @@ function buildCheckRunPayload(report, options) {
|
|
|
923
1068
|
status: "completed",
|
|
924
1069
|
conclusion: gate.ok ? "success" : "failure",
|
|
925
1070
|
completed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1071
|
+
...detailsUrl ? { details_url: detailsUrl } : {},
|
|
926
1072
|
output: {
|
|
927
1073
|
title: gate.title,
|
|
928
1074
|
summary: truncateCheckSummary(
|
|
@@ -979,16 +1125,22 @@ function mergeEvalReports(reports) {
|
|
|
979
1125
|
minimum: Math.min(...scoredCases)
|
|
980
1126
|
} : void 0,
|
|
981
1127
|
usage: mergeUsage(reports.map((report) => report.usage)),
|
|
1128
|
+
judgeUsage: mergeUsage(reports.map((report) => report.judgeUsage)),
|
|
982
1129
|
cases,
|
|
983
1130
|
failures
|
|
984
1131
|
};
|
|
985
1132
|
}
|
|
986
1133
|
function mergeUsage(usages) {
|
|
1134
|
+
const costs = usages.map((usage) => usage.costUsd).filter((cost) => cost !== void 0);
|
|
1135
|
+
const costComplete = !usages.some(
|
|
1136
|
+
(usage) => usage.costUsd === void 0 && (usage.totalTokens > 0 || usage.toolCalls > 0)
|
|
1137
|
+
);
|
|
987
1138
|
return {
|
|
988
1139
|
inputTokens: sum(usages, (usage) => usage.inputTokens),
|
|
989
1140
|
outputTokens: sum(usages, (usage) => usage.outputTokens),
|
|
990
1141
|
reasoningTokens: sum(usages, (usage) => usage.reasoningTokens),
|
|
991
1142
|
totalTokens: sum(usages, (usage) => usage.totalTokens),
|
|
1143
|
+
...costComplete && costs.length > 0 ? { costUsd: costs.reduce((total, cost) => total + cost, 0) } : {},
|
|
992
1144
|
toolCalls: sum(usages, (usage) => usage.toolCalls)
|
|
993
1145
|
};
|
|
994
1146
|
}
|
|
@@ -1080,11 +1232,17 @@ async function publishEvalReport(options) {
|
|
|
1080
1232
|
name: options.checkName,
|
|
1081
1233
|
repository: options.repository,
|
|
1082
1234
|
sha: options.sha,
|
|
1235
|
+
detailsUrl: options.detailsUrl,
|
|
1236
|
+
externalId: options.externalId,
|
|
1083
1237
|
token: options.token,
|
|
1084
1238
|
gate
|
|
1085
1239
|
});
|
|
1086
1240
|
if (checkRun.status === "skipped") {
|
|
1087
1241
|
options.warn?.(`GitHub Check Run skipped: ${checkRun.reason}`);
|
|
1242
|
+
} else if (checkRun.htmlUrl) {
|
|
1243
|
+
console.log(`published check run: ${checkRun.htmlUrl}`);
|
|
1244
|
+
} else if (checkRun.id !== void 0) {
|
|
1245
|
+
console.log(`published check run id: ${checkRun.id}`);
|
|
1088
1246
|
}
|
|
1089
1247
|
} catch (error) {
|
|
1090
1248
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1094,14 +1252,21 @@ async function publishEvalReport(options) {
|
|
|
1094
1252
|
options.warn?.(message);
|
|
1095
1253
|
}
|
|
1096
1254
|
}
|
|
1255
|
+
const gateFailed = gate.enforced && !gate.ok;
|
|
1256
|
+
const wantsSoftFail = options.softFail ?? options.checkRun === true;
|
|
1257
|
+
const softFail = wantsSoftFail && checkRunPublished(checkRun);
|
|
1258
|
+
const shouldFail = gateFailed && !softFail;
|
|
1097
1259
|
return {
|
|
1098
1260
|
report,
|
|
1099
1261
|
resultFiles,
|
|
1100
1262
|
gate,
|
|
1101
|
-
shouldFail
|
|
1263
|
+
shouldFail,
|
|
1102
1264
|
checkRun
|
|
1103
1265
|
};
|
|
1104
1266
|
}
|
|
1267
|
+
function checkRunPublished(checkRun) {
|
|
1268
|
+
return checkRun?.status === "created" || checkRun?.status === "updated";
|
|
1269
|
+
}
|
|
1105
1270
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1106
1271
|
0 && (module.exports = {
|
|
1107
1272
|
buildCheckAnnotations,
|
|
@@ -1113,6 +1278,8 @@ async function publishEvalReport(options) {
|
|
|
1113
1278
|
publishEvalReport,
|
|
1114
1279
|
renderGateWorkflowCommand,
|
|
1115
1280
|
renderJobSummary,
|
|
1116
|
-
renderWorkflowCommands
|
|
1281
|
+
renderWorkflowCommands,
|
|
1282
|
+
resolveCheckDetailsUrl,
|
|
1283
|
+
resolveCheckSha
|
|
1117
1284
|
});
|
|
1118
1285
|
//# sourceMappingURL=index.js.map
|