@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.mjs
CHANGED
|
@@ -93,7 +93,8 @@ function collectEvalReport(input, options = {}) {
|
|
|
93
93
|
const cases = workspace.cases.map(collectEvalCase);
|
|
94
94
|
const failures = cases.filter((testCase) => testCase.status === "failed");
|
|
95
95
|
const evalScores = cases.map((testCase) => testCase.eval?.avgScore).filter((score) => isFiniteNumber(score));
|
|
96
|
-
const usage =
|
|
96
|
+
const usage = sumAppUsage(cases);
|
|
97
|
+
const judgeUsage = sumJudgeUsage(cases);
|
|
97
98
|
const durationMs = workspace.runs[0]?.durationMs;
|
|
98
99
|
return {
|
|
99
100
|
status: input.success && failures.length === 0 ? "passed" : "failed",
|
|
@@ -113,6 +114,7 @@ function collectEvalReport(input, options = {}) {
|
|
|
113
114
|
minimum: Math.min(...evalScores)
|
|
114
115
|
} : void 0,
|
|
115
116
|
usage,
|
|
117
|
+
judgeUsage,
|
|
116
118
|
cases,
|
|
117
119
|
failures
|
|
118
120
|
};
|
|
@@ -237,24 +239,67 @@ function stringifyReason(value) {
|
|
|
237
239
|
}
|
|
238
240
|
return typeof value === "string" ? value : stringifyValue(value, 4e3);
|
|
239
241
|
}
|
|
240
|
-
function
|
|
241
|
-
|
|
242
|
+
function emptyUsage() {
|
|
243
|
+
return {
|
|
242
244
|
inputTokens: 0,
|
|
243
245
|
outputTokens: 0,
|
|
244
246
|
reasoningTokens: 0,
|
|
245
247
|
totalTokens: 0,
|
|
246
248
|
toolCalls: 0
|
|
247
249
|
};
|
|
250
|
+
}
|
|
251
|
+
function addRunUsage(total, usage) {
|
|
252
|
+
total.inputTokens += usage?.inputTokens ?? 0;
|
|
253
|
+
total.outputTokens += usage?.outputTokens ?? 0;
|
|
254
|
+
total.reasoningTokens += usage?.reasoningTokens ?? 0;
|
|
255
|
+
total.totalTokens += usage?.totalTokens ?? (usage?.inputTokens ?? 0) + (usage?.outputTokens ?? 0) + (usage?.reasoningTokens ?? 0);
|
|
256
|
+
if (usage?.costUsd !== void 0) {
|
|
257
|
+
total.costUsd = (total.costUsd ?? 0) + usage.costUsd;
|
|
258
|
+
}
|
|
259
|
+
total.toolCalls += usage?.toolCalls ?? 0;
|
|
260
|
+
}
|
|
261
|
+
function sumAppUsage(cases) {
|
|
262
|
+
const usage = emptyUsage();
|
|
263
|
+
const runUsages = cases.map(appUsageForCase).filter((item) => item !== void 0);
|
|
264
|
+
for (const runUsage of runUsages) {
|
|
265
|
+
addRunUsage(usage, runUsage);
|
|
266
|
+
}
|
|
267
|
+
omitPartialCost(usage, runUsages);
|
|
268
|
+
return usage;
|
|
269
|
+
}
|
|
270
|
+
function appUsageForCase(testCase) {
|
|
271
|
+
const usage = testCase.harness?.usage;
|
|
272
|
+
const effectiveToolCalls = toolCallCount(testCase);
|
|
273
|
+
if (!usage && effectiveToolCalls === 0) {
|
|
274
|
+
return void 0;
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
...usage,
|
|
278
|
+
...effectiveToolCalls > 0 ? { toolCalls: effectiveToolCalls } : {}
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
function sumJudgeUsage(cases) {
|
|
282
|
+
const usage = emptyUsage();
|
|
283
|
+
const runUsages = [];
|
|
248
284
|
for (const testCase of cases) {
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
285
|
+
for (const score of testCase.eval?.scores ?? []) {
|
|
286
|
+
for (const run of score.judgeRuns ?? []) {
|
|
287
|
+
runUsages.push(run.usage);
|
|
288
|
+
addRunUsage(usage, run.usage);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
255
291
|
}
|
|
292
|
+
omitPartialCost(usage, runUsages);
|
|
256
293
|
return usage;
|
|
257
294
|
}
|
|
295
|
+
function omitPartialCost(total, usages) {
|
|
296
|
+
if (usages.some(hasUsageWithoutCost)) {
|
|
297
|
+
total.costUsd = void 0;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
function hasUsageWithoutCost(usage) {
|
|
301
|
+
return usage?.costUsd === void 0 && ((usage?.totalTokens ?? (usage?.inputTokens ?? 0) + (usage?.outputTokens ?? 0) + (usage?.reasoningTokens ?? 0)) > 0 || (usage?.toolCalls ?? 0) > 0);
|
|
302
|
+
}
|
|
258
303
|
function toolCallCount(testCase) {
|
|
259
304
|
const usageToolCalls = testCase.harness?.usage?.toolCalls;
|
|
260
305
|
if (usageToolCalls !== void 0) {
|
|
@@ -398,8 +443,8 @@ function evaluateEvalGate(report, policy = {}) {
|
|
|
398
443
|
status: "failed",
|
|
399
444
|
enforced: true,
|
|
400
445
|
passRate,
|
|
401
|
-
title: "Eval
|
|
402
|
-
message: `${formatNumber(nonEvalFailures)}
|
|
446
|
+
title: "Eval run failed",
|
|
447
|
+
message: `${formatNumber(nonEvalFailures)} test failure${nonEvalFailures === 1 ? "" : "s"} outside eval cases; ${counts}`
|
|
403
448
|
};
|
|
404
449
|
}
|
|
405
450
|
if (report.totals.evalTotal === 0) {
|
|
@@ -408,7 +453,7 @@ function evaluateEvalGate(report, policy = {}) {
|
|
|
408
453
|
status: "failed",
|
|
409
454
|
enforced: true,
|
|
410
455
|
passRate: null,
|
|
411
|
-
title: "Eval
|
|
456
|
+
title: "Eval run failed",
|
|
412
457
|
message: "no eval cases were reported"
|
|
413
458
|
};
|
|
414
459
|
}
|
|
@@ -418,8 +463,8 @@ function evaluateEvalGate(report, policy = {}) {
|
|
|
418
463
|
status: "failed",
|
|
419
464
|
enforced: true,
|
|
420
465
|
passRate,
|
|
421
|
-
title: "Eval
|
|
422
|
-
message: `
|
|
466
|
+
title: "Eval run failed",
|
|
467
|
+
message: `Vitest failed without reporting a failed test; ${counts}`
|
|
423
468
|
};
|
|
424
469
|
}
|
|
425
470
|
if (minPassRate !== void 0 && (passRate === null || passRate + Number.EPSILON < minPassRate)) {
|
|
@@ -429,7 +474,7 @@ function evaluateEvalGate(report, policy = {}) {
|
|
|
429
474
|
enforced: true,
|
|
430
475
|
passRate,
|
|
431
476
|
title: `Eval pass rate ${formatPercent(passRate)} \u2014 required ${formatPercent(minPassRate)}`,
|
|
432
|
-
message: `
|
|
477
|
+
message: `pass rate is below the minimum: ${counts}; minimum ${formatPercent(minPassRate)}`
|
|
433
478
|
};
|
|
434
479
|
}
|
|
435
480
|
if (minScoreAverage !== void 0) {
|
|
@@ -440,8 +485,8 @@ function evaluateEvalGate(report, policy = {}) {
|
|
|
440
485
|
status: "failed",
|
|
441
486
|
enforced: true,
|
|
442
487
|
passRate,
|
|
443
|
-
title: "
|
|
444
|
-
message: `no score
|
|
488
|
+
title: "Average score unavailable",
|
|
489
|
+
message: `no average score was reported; minimum ${formatScore(minScoreAverage)}`
|
|
445
490
|
};
|
|
446
491
|
}
|
|
447
492
|
if (average + Number.EPSILON < minScoreAverage) {
|
|
@@ -450,8 +495,8 @@ function evaluateEvalGate(report, policy = {}) {
|
|
|
450
495
|
status: "failed",
|
|
451
496
|
enforced: true,
|
|
452
497
|
passRate,
|
|
453
|
-
title: `
|
|
454
|
-
message: `
|
|
498
|
+
title: `Average score ${formatScore(average)} \u2014 required ${formatScore(minScoreAverage)}`,
|
|
499
|
+
message: `average score is below the minimum: ${counts}; minimum ${formatScore(minScoreAverage)}`
|
|
455
500
|
};
|
|
456
501
|
}
|
|
457
502
|
}
|
|
@@ -461,7 +506,7 @@ function evaluateEvalGate(report, policy = {}) {
|
|
|
461
506
|
enforced: true,
|
|
462
507
|
passRate,
|
|
463
508
|
title: enforcedPassTitle(report, passRate, minPassRate, minScoreAverage),
|
|
464
|
-
message: `
|
|
509
|
+
message: `requirements met: ${counts}${formatMinimumSuffix(minPassRate, minScoreAverage)}`
|
|
465
510
|
};
|
|
466
511
|
}
|
|
467
512
|
function computePassRate(report) {
|
|
@@ -494,7 +539,7 @@ function resolveMinPassRate(policy) {
|
|
|
494
539
|
}
|
|
495
540
|
function defaultCheckTitle(report) {
|
|
496
541
|
if (report.failures.length === 0 && report.status === "passed") {
|
|
497
|
-
return "No eval
|
|
542
|
+
return "No eval cases failed";
|
|
498
543
|
}
|
|
499
544
|
if (report.failures.length === 0) {
|
|
500
545
|
return "Vitest run failed";
|
|
@@ -503,10 +548,10 @@ function defaultCheckTitle(report) {
|
|
|
503
548
|
}
|
|
504
549
|
function enforcedPassTitle(report, passRate, minPassRate, minScoreAverage) {
|
|
505
550
|
if (minPassRate !== void 0) {
|
|
506
|
-
return `Eval pass rate ${formatPercent(passRate)} \u2014
|
|
551
|
+
return `Eval pass rate ${formatPercent(passRate)} \u2014 minimum ${formatPercent(minPassRate)}`;
|
|
507
552
|
}
|
|
508
553
|
if (minScoreAverage !== void 0) {
|
|
509
|
-
return `
|
|
554
|
+
return `Average score ${formatScore(report.score?.average)} \u2014 minimum ${formatScore(minScoreAverage)}`;
|
|
510
555
|
}
|
|
511
556
|
return defaultCheckTitle(report);
|
|
512
557
|
}
|
|
@@ -515,19 +560,22 @@ function formatEvalCounts(report, passRate) {
|
|
|
515
560
|
const passRateText = passRate === null ? "n/a" : formatPercent(passRate);
|
|
516
561
|
return `${formatNumber(report.totals.evalPassed)}/${formatNumber(
|
|
517
562
|
report.totals.evalTotal
|
|
518
|
-
)} passed (${passRateText}),
|
|
563
|
+
)} passed (${passRateText}), average score ${scoreText}`;
|
|
519
564
|
}
|
|
520
|
-
function
|
|
565
|
+
function formatMinimumSuffix(minPassRate, minScoreAverage) {
|
|
521
566
|
const parts = [];
|
|
522
567
|
if (minPassRate !== void 0) {
|
|
523
|
-
parts.push(`pass rate
|
|
568
|
+
parts.push(`minimum pass rate ${formatPercent(minPassRate)}`);
|
|
524
569
|
}
|
|
525
570
|
if (minScoreAverage !== void 0) {
|
|
526
|
-
parts.push(`
|
|
571
|
+
parts.push(`minimum average score ${formatScore(minScoreAverage)}`);
|
|
527
572
|
}
|
|
528
573
|
return parts.length === 0 ? "" : `; ${parts.join(", ")}`;
|
|
529
574
|
}
|
|
530
575
|
|
|
576
|
+
// src/github.ts
|
|
577
|
+
import { readFileSync } from "fs";
|
|
578
|
+
|
|
531
579
|
// src/summary.ts
|
|
532
580
|
var DEFAULT_MAX_FAILURES = 20;
|
|
533
581
|
var DEFAULT_MAX_REASON_CHARS = 8e3;
|
|
@@ -555,23 +603,23 @@ function renderJobSummary(report, options = {}) {
|
|
|
555
603
|
""
|
|
556
604
|
];
|
|
557
605
|
if (report.failures.length > 0) {
|
|
558
|
-
const failureHeading = options.gate?.ok === true ? "###
|
|
606
|
+
const failureHeading = options.gate?.ok === true ? "### Cases Below Target" : "### Failures";
|
|
559
607
|
lines.push(failureHeading, "");
|
|
560
608
|
failures.forEach((testCase, index) => {
|
|
561
609
|
lines.push(...renderFailureDetails(testCase, index + 1, options), "");
|
|
562
610
|
});
|
|
563
611
|
if (report.failures.length > failures.length) {
|
|
564
|
-
const omittedLabel = options.gate?.ok === true ? "
|
|
612
|
+
const omittedLabel = options.gate?.ok === true ? "cases below target" : "failures";
|
|
565
613
|
lines.push(
|
|
566
614
|
`${report.failures.length - failures.length} more ${omittedLabel} omitted from this summary.`,
|
|
567
615
|
""
|
|
568
616
|
);
|
|
569
617
|
}
|
|
570
618
|
} else if (report.totals.evalTotal > 0) {
|
|
571
|
-
lines.push("### Failures", "", "No eval
|
|
619
|
+
lines.push("### Failures", "", "No eval cases failed.", "");
|
|
572
620
|
}
|
|
573
621
|
if (report.totals.evalTotal === 0) {
|
|
574
|
-
lines.push("No eval
|
|
622
|
+
lines.push("No eval results were found in the Vitest JSON report.", "");
|
|
575
623
|
}
|
|
576
624
|
return `${lines.join("\n")}
|
|
577
625
|
`;
|
|
@@ -581,9 +629,9 @@ function formatCountLine(passed, failed, total) {
|
|
|
581
629
|
}
|
|
582
630
|
function renderSummaryTable(report, nonEvalFailures, gate) {
|
|
583
631
|
const rows = [
|
|
584
|
-
["Status", gate?.status ?? report.status],
|
|
632
|
+
["Status", capitalize(gate?.status ?? report.status)],
|
|
585
633
|
[
|
|
586
|
-
"
|
|
634
|
+
"Eval cases",
|
|
587
635
|
formatCountLine(
|
|
588
636
|
report.totals.evalPassed,
|
|
589
637
|
report.totals.evalFailed,
|
|
@@ -592,23 +640,31 @@ function renderSummaryTable(report, nonEvalFailures, gate) {
|
|
|
592
640
|
]
|
|
593
641
|
];
|
|
594
642
|
if (gate?.passRate !== void 0 && gate.passRate !== null) {
|
|
595
|
-
rows.push(["Pass
|
|
643
|
+
rows.push(["Pass rate", formatPercent(gate.passRate)]);
|
|
596
644
|
} else if (report.totals.evalTotal > 0) {
|
|
597
645
|
rows.push([
|
|
598
|
-
"Pass
|
|
646
|
+
"Pass rate",
|
|
599
647
|
formatPercent(report.totals.evalPassed / report.totals.evalTotal)
|
|
600
648
|
]);
|
|
601
649
|
}
|
|
602
650
|
if (report.score) {
|
|
603
651
|
rows.push(["Score", formatScoreSummary(report.score)]);
|
|
604
652
|
}
|
|
653
|
+
if (hasUsage(report.usage) || hasUsage(report.judgeUsage)) {
|
|
654
|
+
rows.push(["App usage", formatUsage(report.usage)]);
|
|
655
|
+
rows.push(["Judge usage", formatUsage(report.judgeUsage)]);
|
|
656
|
+
rows.push([
|
|
657
|
+
"Total usage",
|
|
658
|
+
formatUsage(sumUsage(report.usage, report.judgeUsage))
|
|
659
|
+
]);
|
|
660
|
+
}
|
|
605
661
|
if (gate?.enforced) {
|
|
606
|
-
rows.push(["
|
|
662
|
+
rows.push(["Requirements", gate.message]);
|
|
607
663
|
}
|
|
608
664
|
if (nonEvalFailures > 0) {
|
|
609
665
|
rows.push([
|
|
610
|
-
"Other
|
|
611
|
-
`${formatNumber(nonEvalFailures)}
|
|
666
|
+
"Other test failures",
|
|
667
|
+
`${formatNumber(nonEvalFailures)} test failure${nonEvalFailures === 1 ? "" : "s"} outside eval cases`
|
|
612
668
|
]);
|
|
613
669
|
}
|
|
614
670
|
rows.push(["Duration", formatDuration(report.durationMs)]);
|
|
@@ -621,7 +677,53 @@ function renderSummaryTable(report, nonEvalFailures, gate) {
|
|
|
621
677
|
];
|
|
622
678
|
}
|
|
623
679
|
function formatScoreSummary(score) {
|
|
624
|
-
return `
|
|
680
|
+
return `average ${formatScore(score.average)}${score.minimum === void 0 ? "" : `, lowest ${formatScore(score.minimum)}`}`;
|
|
681
|
+
}
|
|
682
|
+
function capitalize(value) {
|
|
683
|
+
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
684
|
+
}
|
|
685
|
+
function hasUsage(usage) {
|
|
686
|
+
return usage.totalTokens > 0 || usage.costUsd !== void 0 || usage.toolCalls > 0;
|
|
687
|
+
}
|
|
688
|
+
function sumUsage(app, judge) {
|
|
689
|
+
const appCostKnown = !hasUsageWithoutCost2(app);
|
|
690
|
+
const judgeCostKnown = !hasUsageWithoutCost2(judge);
|
|
691
|
+
return {
|
|
692
|
+
inputTokens: app.inputTokens + judge.inputTokens,
|
|
693
|
+
outputTokens: app.outputTokens + judge.outputTokens,
|
|
694
|
+
reasoningTokens: app.reasoningTokens + judge.reasoningTokens,
|
|
695
|
+
totalTokens: app.totalTokens + judge.totalTokens,
|
|
696
|
+
...appCostKnown && judgeCostKnown ? { costUsd: (app.costUsd ?? 0) + (judge.costUsd ?? 0) } : {},
|
|
697
|
+
toolCalls: app.toolCalls + judge.toolCalls
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
function hasUsageWithoutCost2(usage) {
|
|
701
|
+
return usage.costUsd === void 0 && (usage.totalTokens > 0 || usage.toolCalls > 0);
|
|
702
|
+
}
|
|
703
|
+
function formatUsage(usage) {
|
|
704
|
+
const parts = [];
|
|
705
|
+
if (usage.totalTokens > 0) {
|
|
706
|
+
parts.push(`${formatNumber(usage.totalTokens)} tokens`);
|
|
707
|
+
}
|
|
708
|
+
if (usage.costUsd !== void 0) {
|
|
709
|
+
parts.push(
|
|
710
|
+
usage.costUsd.toLocaleString("en-US", {
|
|
711
|
+
style: "currency",
|
|
712
|
+
currency: "USD",
|
|
713
|
+
minimumFractionDigits: 2,
|
|
714
|
+
maximumFractionDigits: 6
|
|
715
|
+
})
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
if (hasUsageWithoutCost2(usage)) {
|
|
719
|
+
parts.push("cost unavailable");
|
|
720
|
+
}
|
|
721
|
+
if (usage.toolCalls > 0) {
|
|
722
|
+
parts.push(
|
|
723
|
+
`${formatNumber(usage.toolCalls)} tool call${usage.toolCalls === 1 ? "" : "s"}`
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
return parts.join(", ") || "none";
|
|
625
727
|
}
|
|
626
728
|
function escapeTableCell(value) {
|
|
627
729
|
return value.replace(/\r?\n/g, " ").replace(/\\/g, "\\\\").replace(/\|/g, "\\|");
|
|
@@ -701,7 +803,7 @@ function renderFailureBlock(testCase, {
|
|
|
701
803
|
["Case", `${number}. ${testCase.displayName}`],
|
|
702
804
|
["Status", testCase.status],
|
|
703
805
|
["Location", formatLocation(testCase.displayFile, testCase.location)],
|
|
704
|
-
["
|
|
806
|
+
["App runner", testCase.harness?.name ?? "n/a"],
|
|
705
807
|
["Score", formatScore(failure?.score ?? testCase.eval?.avgScore)],
|
|
706
808
|
["Judge", failure?.judgeName ?? "n/a"]
|
|
707
809
|
];
|
|
@@ -739,7 +841,7 @@ function renderFailureBlock(testCase, {
|
|
|
739
841
|
if (finalOutput !== void 0) {
|
|
740
842
|
lines.push(
|
|
741
843
|
...renderAsciiSection(
|
|
742
|
-
"
|
|
844
|
+
"Output",
|
|
743
845
|
stringifyValue(finalOutput, maxOutputChars).split(/\r?\n/)
|
|
744
846
|
),
|
|
745
847
|
""
|
|
@@ -767,7 +869,7 @@ function renderFailureBlock(testCase, {
|
|
|
767
869
|
if (testCase.harness?.errors.length) {
|
|
768
870
|
lines.push(
|
|
769
871
|
...renderAsciiSection(
|
|
770
|
-
"
|
|
872
|
+
"App errors",
|
|
771
873
|
stringifyValue(testCase.harness.errors, maxReasonChars).split(/\r?\n/)
|
|
772
874
|
),
|
|
773
875
|
""
|
|
@@ -816,7 +918,9 @@ function formatCaseUsage(testCase) {
|
|
|
816
918
|
parts.push(`${formatNumber(totalTokens)} tokens`);
|
|
817
919
|
}
|
|
818
920
|
if (toolCalls > 0) {
|
|
819
|
-
parts.push(
|
|
921
|
+
parts.push(
|
|
922
|
+
`${formatNumber(toolCalls)} tool call${toolCalls === 1 ? "" : "s"}`
|
|
923
|
+
);
|
|
820
924
|
}
|
|
821
925
|
if (testCase.harness?.timingMs !== void 0) {
|
|
822
926
|
parts.push(formatDuration(testCase.harness.timingMs));
|
|
@@ -828,10 +932,44 @@ function formatCaseUsage(testCase) {
|
|
|
828
932
|
var DEFAULT_CHECK_NAME = "vitest-evals";
|
|
829
933
|
var MAX_CHECK_SUMMARY_LENGTH = 64e3;
|
|
830
934
|
var CHECK_SUMMARY_TRUNCATION_SUFFIX = "\n\n[truncated for GitHub Check Run]\n";
|
|
935
|
+
function resolveCheckSha(env = process.env, options = {}) {
|
|
936
|
+
const explicit = options.sha?.trim() || env.GITHUB_PR_HEAD_SHA?.trim();
|
|
937
|
+
if (explicit) {
|
|
938
|
+
return explicit;
|
|
939
|
+
}
|
|
940
|
+
const eventPath = options.eventPath?.trim() || env.GITHUB_EVENT_PATH?.trim();
|
|
941
|
+
if (eventPath) {
|
|
942
|
+
try {
|
|
943
|
+
const event = JSON.parse(readFileSync(eventPath, "utf8"));
|
|
944
|
+
const headSha = event.pull_request?.head?.sha;
|
|
945
|
+
if (typeof headSha === "string" && headSha.trim()) {
|
|
946
|
+
return headSha.trim();
|
|
947
|
+
}
|
|
948
|
+
} catch {
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
return env.GITHUB_SHA?.trim() || void 0;
|
|
952
|
+
}
|
|
953
|
+
function resolveCheckDetailsUrl(env = process.env, options = {}) {
|
|
954
|
+
const explicit = options.detailsUrl?.trim();
|
|
955
|
+
if (explicit) {
|
|
956
|
+
return explicit;
|
|
957
|
+
}
|
|
958
|
+
const server = env.GITHUB_SERVER_URL?.replace(/\/$/, "");
|
|
959
|
+
const repository = env.GITHUB_REPOSITORY?.trim();
|
|
960
|
+
const runId = env.GITHUB_RUN_ID?.trim();
|
|
961
|
+
if (!server || !repository || !runId) {
|
|
962
|
+
return void 0;
|
|
963
|
+
}
|
|
964
|
+
return `${server}/${repository}/actions/runs/${runId}`;
|
|
965
|
+
}
|
|
831
966
|
async function publishCheckRun(report, options = {}) {
|
|
832
967
|
const token = options.token ?? process.env.GITHUB_TOKEN;
|
|
833
968
|
const repository = options.repository ?? process.env.GITHUB_REPOSITORY;
|
|
834
|
-
const sha = options.sha
|
|
969
|
+
const sha = resolveCheckSha(process.env, { sha: options.sha });
|
|
970
|
+
const detailsUrl = resolveCheckDetailsUrl(process.env, {
|
|
971
|
+
detailsUrl: options.detailsUrl
|
|
972
|
+
});
|
|
835
973
|
if (!token) {
|
|
836
974
|
return { status: "skipped", reason: "missing GITHUB_TOKEN" };
|
|
837
975
|
}
|
|
@@ -839,7 +977,10 @@ async function publishCheckRun(report, options = {}) {
|
|
|
839
977
|
return { status: "skipped", reason: "missing GITHUB_REPOSITORY" };
|
|
840
978
|
}
|
|
841
979
|
if (!sha && options.checkRunId === void 0) {
|
|
842
|
-
return {
|
|
980
|
+
return {
|
|
981
|
+
status: "skipped",
|
|
982
|
+
reason: "missing commit SHA (set --sha / options.sha, GITHUB_PR_HEAD_SHA, pull_request.head.sha, or GITHUB_SHA)"
|
|
983
|
+
};
|
|
843
984
|
}
|
|
844
985
|
const [owner, repo] = repository.split("/");
|
|
845
986
|
if (!owner || !repo) {
|
|
@@ -848,7 +989,7 @@ async function publishCheckRun(report, options = {}) {
|
|
|
848
989
|
reason: `invalid GitHub repository: ${repository}`
|
|
849
990
|
};
|
|
850
991
|
}
|
|
851
|
-
const payload = buildCheckRunPayload(report, options);
|
|
992
|
+
const payload = buildCheckRunPayload(report, options, detailsUrl);
|
|
852
993
|
const apiUrl = options.apiUrl ?? process.env.GITHUB_API_URL ?? "https://api.github.com";
|
|
853
994
|
const requestUrl = options.checkRunId === void 0 ? `${apiUrl}/repos/${owner}/${repo}/check-runs` : `${apiUrl}/repos/${owner}/${repo}/check-runs/${options.checkRunId}`;
|
|
854
995
|
const response = await fetch(requestUrl, {
|
|
@@ -863,6 +1004,7 @@ async function publishCheckRun(report, options = {}) {
|
|
|
863
1004
|
options.checkRunId === void 0 ? {
|
|
864
1005
|
name: options.name ?? DEFAULT_CHECK_NAME,
|
|
865
1006
|
head_sha: sha,
|
|
1007
|
+
...options.externalId ? { external_id: options.externalId } : {},
|
|
866
1008
|
...payload
|
|
867
1009
|
} : payload
|
|
868
1010
|
)
|
|
@@ -877,10 +1019,11 @@ async function publishCheckRun(report, options = {}) {
|
|
|
877
1019
|
return {
|
|
878
1020
|
status: options.checkRunId === void 0 ? "created" : "updated",
|
|
879
1021
|
id: data.id,
|
|
880
|
-
htmlUrl: data.html_url
|
|
1022
|
+
htmlUrl: data.html_url,
|
|
1023
|
+
sha
|
|
881
1024
|
};
|
|
882
1025
|
}
|
|
883
|
-
function buildCheckRunPayload(report, options) {
|
|
1026
|
+
function buildCheckRunPayload(report, options, detailsUrl) {
|
|
884
1027
|
const gate = options.gate ?? evaluateEvalGate(report);
|
|
885
1028
|
const annotations = buildCheckAnnotations(report, {
|
|
886
1029
|
maxAnnotations: options.maxAnnotations,
|
|
@@ -890,6 +1033,7 @@ function buildCheckRunPayload(report, options) {
|
|
|
890
1033
|
status: "completed",
|
|
891
1034
|
conclusion: gate.ok ? "success" : "failure",
|
|
892
1035
|
completed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1036
|
+
...detailsUrl ? { details_url: detailsUrl } : {},
|
|
893
1037
|
output: {
|
|
894
1038
|
title: gate.title,
|
|
895
1039
|
summary: truncateCheckSummary(
|
|
@@ -949,16 +1093,22 @@ function mergeEvalReports(reports) {
|
|
|
949
1093
|
minimum: Math.min(...scoredCases)
|
|
950
1094
|
} : void 0,
|
|
951
1095
|
usage: mergeUsage(reports.map((report) => report.usage)),
|
|
1096
|
+
judgeUsage: mergeUsage(reports.map((report) => report.judgeUsage)),
|
|
952
1097
|
cases,
|
|
953
1098
|
failures
|
|
954
1099
|
};
|
|
955
1100
|
}
|
|
956
1101
|
function mergeUsage(usages) {
|
|
1102
|
+
const costs = usages.map((usage) => usage.costUsd).filter((cost) => cost !== void 0);
|
|
1103
|
+
const costComplete = !usages.some(
|
|
1104
|
+
(usage) => usage.costUsd === void 0 && (usage.totalTokens > 0 || usage.toolCalls > 0)
|
|
1105
|
+
);
|
|
957
1106
|
return {
|
|
958
1107
|
inputTokens: sum(usages, (usage) => usage.inputTokens),
|
|
959
1108
|
outputTokens: sum(usages, (usage) => usage.outputTokens),
|
|
960
1109
|
reasoningTokens: sum(usages, (usage) => usage.reasoningTokens),
|
|
961
1110
|
totalTokens: sum(usages, (usage) => usage.totalTokens),
|
|
1111
|
+
...costComplete && costs.length > 0 ? { costUsd: costs.reduce((total, cost) => total + cost, 0) } : {},
|
|
962
1112
|
toolCalls: sum(usages, (usage) => usage.toolCalls)
|
|
963
1113
|
};
|
|
964
1114
|
}
|
|
@@ -1050,11 +1200,17 @@ async function publishEvalReport(options) {
|
|
|
1050
1200
|
name: options.checkName,
|
|
1051
1201
|
repository: options.repository,
|
|
1052
1202
|
sha: options.sha,
|
|
1203
|
+
detailsUrl: options.detailsUrl,
|
|
1204
|
+
externalId: options.externalId,
|
|
1053
1205
|
token: options.token,
|
|
1054
1206
|
gate
|
|
1055
1207
|
});
|
|
1056
1208
|
if (checkRun.status === "skipped") {
|
|
1057
1209
|
options.warn?.(`GitHub Check Run skipped: ${checkRun.reason}`);
|
|
1210
|
+
} else if (checkRun.htmlUrl) {
|
|
1211
|
+
console.log(`published check run: ${checkRun.htmlUrl}`);
|
|
1212
|
+
} else if (checkRun.id !== void 0) {
|
|
1213
|
+
console.log(`published check run id: ${checkRun.id}`);
|
|
1058
1214
|
}
|
|
1059
1215
|
} catch (error) {
|
|
1060
1216
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1064,14 +1220,21 @@ async function publishEvalReport(options) {
|
|
|
1064
1220
|
options.warn?.(message);
|
|
1065
1221
|
}
|
|
1066
1222
|
}
|
|
1223
|
+
const gateFailed = gate.enforced && !gate.ok;
|
|
1224
|
+
const wantsSoftFail = options.softFail ?? options.checkRun === true;
|
|
1225
|
+
const softFail = wantsSoftFail && checkRunPublished(checkRun);
|
|
1226
|
+
const shouldFail = gateFailed && !softFail;
|
|
1067
1227
|
return {
|
|
1068
1228
|
report,
|
|
1069
1229
|
resultFiles,
|
|
1070
1230
|
gate,
|
|
1071
|
-
shouldFail
|
|
1231
|
+
shouldFail,
|
|
1072
1232
|
checkRun
|
|
1073
1233
|
};
|
|
1074
1234
|
}
|
|
1235
|
+
function checkRunPublished(checkRun) {
|
|
1236
|
+
return checkRun?.status === "created" || checkRun?.status === "updated";
|
|
1237
|
+
}
|
|
1075
1238
|
export {
|
|
1076
1239
|
buildCheckAnnotations,
|
|
1077
1240
|
collectEvalReport,
|
|
@@ -1082,6 +1245,8 @@ export {
|
|
|
1082
1245
|
publishEvalReport,
|
|
1083
1246
|
renderGateWorkflowCommand,
|
|
1084
1247
|
renderJobSummary,
|
|
1085
|
-
renderWorkflowCommands
|
|
1248
|
+
renderWorkflowCommands,
|
|
1249
|
+
resolveCheckDetailsUrl,
|
|
1250
|
+
resolveCheckSha
|
|
1086
1251
|
};
|
|
1087
1252
|
//# sourceMappingURL=index.mjs.map
|