agent-inspect 2.0.0 → 2.2.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/CHANGELOG.md +24 -0
- package/README.md +27 -4
- package/docs/ADAPTERS.md +4 -0
- package/docs/API.md +75 -10
- package/docs/CLI.md +124 -9
- package/docs/COMPARE.md +5 -4
- package/docs/GETTING-STARTED.md +24 -6
- package/docs/KNOWN-ISSUES.md +7 -0
- package/docs/LIMITATIONS.md +3 -1
- package/package.json +12 -2
- package/packages/cli/dist/index.cjs +2040 -142
- package/packages/cli/dist/index.cjs.map +1 -1
- package/packages/cli/dist/index.mjs +2039 -141
- package/packages/cli/dist/index.mjs.map +1 -1
- package/packages/core/dist/reporters.cjs +185 -0
- package/packages/core/dist/reporters.cjs.map +1 -0
- package/packages/core/dist/reporters.d.cts +91 -0
- package/packages/core/dist/reporters.d.ts +91 -0
- package/packages/core/dist/reporters.mjs +175 -0
- package/packages/core/dist/reporters.mjs.map +1 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
var fs = require('fs');
|
|
5
|
-
var
|
|
5
|
+
var path13 = require('path');
|
|
6
6
|
var url = require('url');
|
|
7
7
|
var commander = require('commander');
|
|
8
8
|
var async_hooks = require('async_hooks');
|
|
@@ -16,14 +16,14 @@ var readline = require('readline');
|
|
|
16
16
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
17
17
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
18
18
|
|
|
19
|
-
var
|
|
19
|
+
var path13__default = /*#__PURE__*/_interopDefault(path13);
|
|
20
20
|
var crypto__default = /*#__PURE__*/_interopDefault(crypto);
|
|
21
21
|
var os__default = /*#__PURE__*/_interopDefault(os);
|
|
22
22
|
var process2__default = /*#__PURE__*/_interopDefault(process2);
|
|
23
23
|
var tty__default = /*#__PURE__*/_interopDefault(tty);
|
|
24
24
|
|
|
25
25
|
// package.json
|
|
26
|
-
var version = "2.
|
|
26
|
+
var version = "2.2.0";
|
|
27
27
|
|
|
28
28
|
// packages/core/src/correlation-metadata.ts
|
|
29
29
|
var TRACE_CORRELATION_KEYS = [
|
|
@@ -755,7 +755,7 @@ function formatDuration(ms) {
|
|
|
755
755
|
// packages/core/src/utils.ts
|
|
756
756
|
var DEFAULT_TRACE_DIR_NAME = ".agent-inspect";
|
|
757
757
|
var RUNS_DIR_NAME = "runs";
|
|
758
|
-
var FALLBACK_TRACE_DIR =
|
|
758
|
+
var FALLBACK_TRACE_DIR = path13__default.default.join(
|
|
759
759
|
os__default.default.tmpdir(),
|
|
760
760
|
"agent-inspect",
|
|
761
761
|
RUNS_DIR_NAME
|
|
@@ -790,7 +790,7 @@ function getDefaultTraceDir() {
|
|
|
790
790
|
if (typeof home !== "string" || home.trim() === "") {
|
|
791
791
|
return FALLBACK_TRACE_DIR;
|
|
792
792
|
}
|
|
793
|
-
return
|
|
793
|
+
return path13__default.default.join(home, DEFAULT_TRACE_DIR_NAME, RUNS_DIR_NAME);
|
|
794
794
|
} catch {
|
|
795
795
|
return FALLBACK_TRACE_DIR;
|
|
796
796
|
}
|
|
@@ -798,11 +798,11 @@ function getDefaultTraceDir() {
|
|
|
798
798
|
function getTraceFilePath(runId, traceDir) {
|
|
799
799
|
const baseDir = traceDir ?? getDefaultTraceDir();
|
|
800
800
|
let safeId = typeof runId === "string" && runId.trim() !== "" ? runId.trim() : "run_unknown";
|
|
801
|
-
safeId =
|
|
801
|
+
safeId = path13__default.default.basename(safeId);
|
|
802
802
|
if (safeId === "" || safeId === "." || safeId === "..") {
|
|
803
803
|
safeId = "run_unknown";
|
|
804
804
|
}
|
|
805
|
-
return
|
|
805
|
+
return path13__default.default.join(baseDir, `${safeId}.jsonl`);
|
|
806
806
|
}
|
|
807
807
|
function formatError(error) {
|
|
808
808
|
if (error instanceof Error) {
|
|
@@ -1564,7 +1564,7 @@ var TraceDirectory = class {
|
|
|
1564
1564
|
this.#dir = resolveTraceDir(options);
|
|
1565
1565
|
}
|
|
1566
1566
|
getPath(filename) {
|
|
1567
|
-
return filename ?
|
|
1567
|
+
return filename ? path13__default.default.join(this.#dir, filename) : this.#dir;
|
|
1568
1568
|
}
|
|
1569
1569
|
async list() {
|
|
1570
1570
|
try {
|
|
@@ -1591,7 +1591,7 @@ function parseIsoToMs2(value) {
|
|
|
1591
1591
|
}
|
|
1592
1592
|
async function extractMetadata(filePath, _quickScan) {
|
|
1593
1593
|
const stats = await promises.stat(filePath);
|
|
1594
|
-
let runIdFromFile =
|
|
1594
|
+
let runIdFromFile = path13__default.default.basename(filePath);
|
|
1595
1595
|
if (runIdFromFile.endsWith(".jsonl")) {
|
|
1596
1596
|
runIdFromFile = runIdFromFile.slice(0, -".jsonl".length);
|
|
1597
1597
|
}
|
|
@@ -3562,7 +3562,7 @@ function findReaderByFormat(format, readers) {
|
|
|
3562
3562
|
}
|
|
3563
3563
|
async function jsonlFilesInDirectory(dirPath) {
|
|
3564
3564
|
const entries = await promises.readdir(dirPath, { withFileTypes: true });
|
|
3565
|
-
return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map((entry) =>
|
|
3565
|
+
return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map((entry) => path13__default.default.join(dirPath, entry.name)).sort((a, b) => a.localeCompare(b));
|
|
3566
3566
|
}
|
|
3567
3567
|
async function resolveInput(input3) {
|
|
3568
3568
|
const cached = resolvedInputCache.get(input3);
|
|
@@ -5305,11 +5305,11 @@ function isRecord9(v) {
|
|
|
5305
5305
|
function isNonEmptyStringArray(v) {
|
|
5306
5306
|
return Array.isArray(v) && v.length > 0 && v.every((x) => typeof x === "string" && x.trim() !== "");
|
|
5307
5307
|
}
|
|
5308
|
-
function validateRedact(
|
|
5309
|
-
if (!Array.isArray(
|
|
5308
|
+
function validateRedact(redact2) {
|
|
5309
|
+
if (!Array.isArray(redact2)) {
|
|
5310
5310
|
throw new Error("Invalid config: redact must be an array");
|
|
5311
5311
|
}
|
|
5312
|
-
for (const r of
|
|
5312
|
+
for (const r of redact2) {
|
|
5313
5313
|
if (typeof r === "string") continue;
|
|
5314
5314
|
if (!isRecord9(r)) {
|
|
5315
5315
|
throw new Error("Invalid config: redact entries must be strings or objects");
|
|
@@ -7688,9 +7688,9 @@ Trace directory: ${traceDir}`);
|
|
|
7688
7688
|
if (validation !== void 0 && !validation.ok) {
|
|
7689
7689
|
process.exitCode = 1;
|
|
7690
7690
|
}
|
|
7691
|
-
const outPath = options.output !== void 0 && options.output.trim() !== "" ?
|
|
7691
|
+
const outPath = options.output !== void 0 && options.output.trim() !== "" ? path13__default.default.resolve(options.output.trim()) : void 0;
|
|
7692
7692
|
if (outPath !== void 0) {
|
|
7693
|
-
await promises.mkdir(
|
|
7693
|
+
await promises.mkdir(path13__default.default.dirname(outPath), { recursive: true });
|
|
7694
7694
|
await promises.writeFile(outPath, result.content, "utf-8");
|
|
7695
7695
|
const vlabel = validation !== void 0 ? validation.ok ? "ok" : "failed" : "skipped";
|
|
7696
7696
|
console.log(`Wrote ${result.fileExtension} export to ${outPath} (validation: ${vlabel})`);
|
|
@@ -7867,13 +7867,13 @@ function pairSteps(left, right) {
|
|
|
7867
7867
|
return pairs;
|
|
7868
7868
|
}
|
|
7869
7869
|
function compareLeafSteps(L, R, segments, opts, out) {
|
|
7870
|
-
const
|
|
7870
|
+
const path15 = buildPath(segments);
|
|
7871
7871
|
if (L.name !== R.name) {
|
|
7872
7872
|
out.push({
|
|
7873
7873
|
kind: "structure",
|
|
7874
7874
|
severity: "warning",
|
|
7875
7875
|
message: "Step name differs",
|
|
7876
|
-
path:
|
|
7876
|
+
path: path15,
|
|
7877
7877
|
left: L.name,
|
|
7878
7878
|
right: R.name
|
|
7879
7879
|
});
|
|
@@ -7883,7 +7883,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
|
|
|
7883
7883
|
kind: "step-type",
|
|
7884
7884
|
severity: "warning",
|
|
7885
7885
|
message: "Step type differs",
|
|
7886
|
-
path:
|
|
7886
|
+
path: path15,
|
|
7887
7887
|
left: L.type,
|
|
7888
7888
|
right: R.type
|
|
7889
7889
|
});
|
|
@@ -7893,7 +7893,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
|
|
|
7893
7893
|
kind: "step-status",
|
|
7894
7894
|
severity: "warning",
|
|
7895
7895
|
message: "Step status differs",
|
|
7896
|
-
path:
|
|
7896
|
+
path: path15,
|
|
7897
7897
|
left: L.status,
|
|
7898
7898
|
right: R.status
|
|
7899
7899
|
});
|
|
@@ -7905,7 +7905,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
|
|
|
7905
7905
|
kind: "error",
|
|
7906
7906
|
severity: "error",
|
|
7907
7907
|
message: "Step error message differs",
|
|
7908
|
-
path:
|
|
7908
|
+
path: path15,
|
|
7909
7909
|
left: le || void 0,
|
|
7910
7910
|
right: re || void 0
|
|
7911
7911
|
});
|
|
@@ -7923,7 +7923,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
|
|
|
7923
7923
|
kind: "duration",
|
|
7924
7924
|
severity: "info",
|
|
7925
7925
|
message: "Step duration differs",
|
|
7926
|
-
path:
|
|
7926
|
+
path: path15,
|
|
7927
7927
|
left: ld,
|
|
7928
7928
|
right: rd
|
|
7929
7929
|
});
|
|
@@ -7936,7 +7936,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
|
|
|
7936
7936
|
kind: "metadata",
|
|
7937
7937
|
severity: "info",
|
|
7938
7938
|
message: "Step metadata differs",
|
|
7939
|
-
path:
|
|
7939
|
+
path: path15,
|
|
7940
7940
|
left: L.metadata,
|
|
7941
7941
|
right: R.metadata
|
|
7942
7942
|
});
|
|
@@ -7948,7 +7948,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
|
|
|
7948
7948
|
kind: "output",
|
|
7949
7949
|
severity: "info",
|
|
7950
7950
|
message: "Output preview differs",
|
|
7951
|
-
path:
|
|
7951
|
+
path: path15,
|
|
7952
7952
|
left: L.outputPreview,
|
|
7953
7953
|
right: R.outputPreview
|
|
7954
7954
|
});
|
|
@@ -8108,11 +8108,11 @@ function diffRuns(left, right, options) {
|
|
|
8108
8108
|
}
|
|
8109
8109
|
|
|
8110
8110
|
// packages/core/src/diff/renderer.ts
|
|
8111
|
-
function formatPath(
|
|
8112
|
-
if (
|
|
8111
|
+
function formatPath(path15) {
|
|
8112
|
+
if (path15 === void 0 || path15.path.length === 0) {
|
|
8113
8113
|
return "(run)";
|
|
8114
8114
|
}
|
|
8115
|
-
return
|
|
8115
|
+
return path15.path.map((s) => s.name).join(" > ");
|
|
8116
8116
|
}
|
|
8117
8117
|
function formatValue(v, verbose) {
|
|
8118
8118
|
if (v === void 0) return "(undefined)";
|
|
@@ -8543,9 +8543,9 @@ async function reportCommand(runId, options = {}) {
|
|
|
8543
8543
|
redactionProfile,
|
|
8544
8544
|
correlation: !options.noCorrelation
|
|
8545
8545
|
});
|
|
8546
|
-
const outPath = options.output !== void 0 && options.output.trim() !== "" ?
|
|
8546
|
+
const outPath = options.output !== void 0 && options.output.trim() !== "" ? path13__default.default.resolve(options.output.trim()) : void 0;
|
|
8547
8547
|
if (outPath !== void 0) {
|
|
8548
|
-
await promises.mkdir(
|
|
8548
|
+
await promises.mkdir(path13__default.default.dirname(outPath), { recursive: true });
|
|
8549
8549
|
await promises.writeFile(outPath, result.content, "utf-8");
|
|
8550
8550
|
console.log(`Wrote ${result.fileExtension} report to ${outPath}`);
|
|
8551
8551
|
}
|
|
@@ -8565,6 +8565,383 @@ async function reportCommand(runId, options = {}) {
|
|
|
8565
8565
|
console.log(result.content);
|
|
8566
8566
|
}
|
|
8567
8567
|
}
|
|
8568
|
+
var DEFAULT_REDACT_KEYS2 = [
|
|
8569
|
+
"authorization",
|
|
8570
|
+
"cookie",
|
|
8571
|
+
"token",
|
|
8572
|
+
"apiKey",
|
|
8573
|
+
"password",
|
|
8574
|
+
"secret",
|
|
8575
|
+
"email"
|
|
8576
|
+
];
|
|
8577
|
+
var SHARE_PROFILE_EXTRA_KEYS2 = [
|
|
8578
|
+
"userEmail",
|
|
8579
|
+
"customerEmail",
|
|
8580
|
+
"phone",
|
|
8581
|
+
"phoneNumber",
|
|
8582
|
+
"address",
|
|
8583
|
+
"ip",
|
|
8584
|
+
"ipAddress",
|
|
8585
|
+
"sessionId",
|
|
8586
|
+
"requestId",
|
|
8587
|
+
"correlationId",
|
|
8588
|
+
"decisionId",
|
|
8589
|
+
"groupId",
|
|
8590
|
+
"customerId",
|
|
8591
|
+
"userId",
|
|
8592
|
+
"accountId",
|
|
8593
|
+
"tenantId",
|
|
8594
|
+
"orgId",
|
|
8595
|
+
"organizationId",
|
|
8596
|
+
"traceId",
|
|
8597
|
+
"spanId",
|
|
8598
|
+
"parentSpanId"
|
|
8599
|
+
];
|
|
8600
|
+
var STRICT_PROFILE_EXTRA_KEYS2 = [
|
|
8601
|
+
"prompt",
|
|
8602
|
+
"completion",
|
|
8603
|
+
"input",
|
|
8604
|
+
"output",
|
|
8605
|
+
"inputPreview",
|
|
8606
|
+
"outputPreview",
|
|
8607
|
+
"message",
|
|
8608
|
+
"messages",
|
|
8609
|
+
"transcript",
|
|
8610
|
+
"context",
|
|
8611
|
+
"document",
|
|
8612
|
+
"documents",
|
|
8613
|
+
"chunk",
|
|
8614
|
+
"chunks",
|
|
8615
|
+
"retrieval",
|
|
8616
|
+
"query"
|
|
8617
|
+
];
|
|
8618
|
+
function isRecord13(value) {
|
|
8619
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8620
|
+
}
|
|
8621
|
+
function toKey2(key) {
|
|
8622
|
+
return key.toLowerCase();
|
|
8623
|
+
}
|
|
8624
|
+
function stableHash2(value) {
|
|
8625
|
+
const hash = crypto__default.default.createHash("sha256").update(value, "utf8").digest("hex");
|
|
8626
|
+
return hash.slice(0, 8);
|
|
8627
|
+
}
|
|
8628
|
+
function stringifyScalar(value) {
|
|
8629
|
+
if (typeof value === "string") return value;
|
|
8630
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
8631
|
+
return String(value);
|
|
8632
|
+
}
|
|
8633
|
+
return void 0;
|
|
8634
|
+
}
|
|
8635
|
+
function patternDetector(options) {
|
|
8636
|
+
return {
|
|
8637
|
+
id: options.id,
|
|
8638
|
+
severity: options.severity ?? "warning",
|
|
8639
|
+
matchKind: "value",
|
|
8640
|
+
detect(input3) {
|
|
8641
|
+
if (typeof input3.value !== "string") return [];
|
|
8642
|
+
options.pattern.lastIndex = 0;
|
|
8643
|
+
return options.pattern.test(input3.value) ? [{ action: "replace", severity: options.severity ?? "warning", matchKind: "value" }] : [];
|
|
8644
|
+
}
|
|
8645
|
+
};
|
|
8646
|
+
}
|
|
8647
|
+
function digitsOnly(value) {
|
|
8648
|
+
return value.replace(/\D/g, "");
|
|
8649
|
+
}
|
|
8650
|
+
function passesLuhn(value) {
|
|
8651
|
+
const digits = digitsOnly(value);
|
|
8652
|
+
if (digits.length < 13 || digits.length > 19) return false;
|
|
8653
|
+
let sum = 0;
|
|
8654
|
+
let double = false;
|
|
8655
|
+
for (let i = digits.length - 1; i >= 0; i -= 1) {
|
|
8656
|
+
let digit = Number(digits[i]);
|
|
8657
|
+
if (double) {
|
|
8658
|
+
digit *= 2;
|
|
8659
|
+
if (digit > 9) digit -= 9;
|
|
8660
|
+
}
|
|
8661
|
+
sum += digit;
|
|
8662
|
+
double = !double;
|
|
8663
|
+
}
|
|
8664
|
+
return sum % 10 === 0;
|
|
8665
|
+
}
|
|
8666
|
+
var credentialDetectors = [
|
|
8667
|
+
patternDetector({
|
|
8668
|
+
id: "value.authorizationHeader",
|
|
8669
|
+
pattern: /^(?:basic|bearer|digest|apikey)\s+[a-z0-9._~+/=-]+$/i,
|
|
8670
|
+
severity: "error"
|
|
8671
|
+
}),
|
|
8672
|
+
patternDetector({
|
|
8673
|
+
id: "value.bearerToken",
|
|
8674
|
+
pattern: /\bbearer\s+[a-z0-9._~+/=-]{12,}\b/i,
|
|
8675
|
+
severity: "error"
|
|
8676
|
+
}),
|
|
8677
|
+
patternDetector({
|
|
8678
|
+
id: "value.cookie",
|
|
8679
|
+
pattern: /\b[a-z0-9_.-]+=[^;\s]+(?:;\s*[a-z0-9_.-]+=[^;\s]+)+/i,
|
|
8680
|
+
severity: "error"
|
|
8681
|
+
}),
|
|
8682
|
+
patternDetector({
|
|
8683
|
+
id: "value.jwt",
|
|
8684
|
+
pattern: /\beyJ[a-zA-Z0-9_-]{8,}\.[a-zA-Z0-9_-]{8,}\.[a-zA-Z0-9_-]{8,}\b/,
|
|
8685
|
+
severity: "error"
|
|
8686
|
+
}),
|
|
8687
|
+
patternDetector({
|
|
8688
|
+
id: "value.providerApiKey",
|
|
8689
|
+
pattern: /\b(?:sk-(?:proj-)?[a-zA-Z0-9_-]{16,}|sk-ant-[a-zA-Z0-9_-]{16,}|AIza[0-9A-Za-z_-]{20,})\b/,
|
|
8690
|
+
severity: "error"
|
|
8691
|
+
}),
|
|
8692
|
+
patternDetector({
|
|
8693
|
+
id: "value.githubToken",
|
|
8694
|
+
pattern: /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{20,}\b/,
|
|
8695
|
+
severity: "error"
|
|
8696
|
+
}),
|
|
8697
|
+
patternDetector({
|
|
8698
|
+
id: "value.awsAccessKey",
|
|
8699
|
+
pattern: /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/,
|
|
8700
|
+
severity: "error"
|
|
8701
|
+
}),
|
|
8702
|
+
patternDetector({
|
|
8703
|
+
id: "value.privateKey",
|
|
8704
|
+
pattern: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*-----END [A-Z ]*PRIVATE KEY-----/,
|
|
8705
|
+
severity: "error"
|
|
8706
|
+
}),
|
|
8707
|
+
{
|
|
8708
|
+
id: "value.creditCard",
|
|
8709
|
+
severity: "error",
|
|
8710
|
+
matchKind: "value",
|
|
8711
|
+
detect(input3) {
|
|
8712
|
+
if (typeof input3.value !== "string") return [];
|
|
8713
|
+
const candidatePattern = /(?:\d[ -]?){13,19}/g;
|
|
8714
|
+
for (const match of input3.value.matchAll(candidatePattern)) {
|
|
8715
|
+
const candidate = match[0] ?? "";
|
|
8716
|
+
if (passesLuhn(candidate)) {
|
|
8717
|
+
return [{ action: "replace", severity: "error", matchKind: "value" }];
|
|
8718
|
+
}
|
|
8719
|
+
}
|
|
8720
|
+
return [];
|
|
8721
|
+
}
|
|
8722
|
+
}
|
|
8723
|
+
];
|
|
8724
|
+
var identifierDetectors = [
|
|
8725
|
+
patternDetector({
|
|
8726
|
+
id: "value.email",
|
|
8727
|
+
pattern: /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i
|
|
8728
|
+
}),
|
|
8729
|
+
patternDetector({
|
|
8730
|
+
id: "value.phone",
|
|
8731
|
+
pattern: /\b(?:\+\d{1,3}[\s.-]?)?(?:\(?\d{3}\)?[\s.-])\d{3}[\s.-]\d{4}\b/
|
|
8732
|
+
}),
|
|
8733
|
+
patternDetector({
|
|
8734
|
+
id: "value.ipv4",
|
|
8735
|
+
pattern: /\b(?:(?:25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|1?\d?\d)\b/
|
|
8736
|
+
}),
|
|
8737
|
+
patternDetector({
|
|
8738
|
+
id: "value.ipv6",
|
|
8739
|
+
pattern: /\b(?:[0-9a-f]{1,4}:){2,7}[0-9a-f]{1,4}\b/i
|
|
8740
|
+
})
|
|
8741
|
+
];
|
|
8742
|
+
function builtInDetectorsForProfile(profile) {
|
|
8743
|
+
if (profile === "local") return credentialDetectors;
|
|
8744
|
+
return [...credentialDetectors, ...identifierDetectors];
|
|
8745
|
+
}
|
|
8746
|
+
function compileRules2(rules, extraKeys) {
|
|
8747
|
+
const out = /* @__PURE__ */ new Map();
|
|
8748
|
+
const set = (rule) => {
|
|
8749
|
+
const key = toKey2(rule.key);
|
|
8750
|
+
out.set(key, { ...rule, key });
|
|
8751
|
+
};
|
|
8752
|
+
for (const key of DEFAULT_REDACT_KEYS2) {
|
|
8753
|
+
set({ key, strategy: "full" });
|
|
8754
|
+
}
|
|
8755
|
+
for (const key of extraKeys ?? []) {
|
|
8756
|
+
if (typeof key === "string" && key.length > 0) {
|
|
8757
|
+
set({ key, strategy: "full" });
|
|
8758
|
+
}
|
|
8759
|
+
}
|
|
8760
|
+
for (const rule of rules ?? []) {
|
|
8761
|
+
if (typeof rule === "string") {
|
|
8762
|
+
set({ key: rule, strategy: "full" });
|
|
8763
|
+
continue;
|
|
8764
|
+
}
|
|
8765
|
+
if (rule.strategy === "full") set({ key: rule.key, strategy: "full" });
|
|
8766
|
+
if (rule.strategy === "hash") set({ key: rule.key, strategy: "hash" });
|
|
8767
|
+
if (rule.strategy === "prefix") {
|
|
8768
|
+
set({
|
|
8769
|
+
key: rule.key,
|
|
8770
|
+
strategy: "prefix",
|
|
8771
|
+
keep: typeof rule.keep === "number" ? rule.keep : 8
|
|
8772
|
+
});
|
|
8773
|
+
}
|
|
8774
|
+
}
|
|
8775
|
+
return [...out.values()];
|
|
8776
|
+
}
|
|
8777
|
+
function actionForRule(rule) {
|
|
8778
|
+
if (rule.strategy === "full") return "replace";
|
|
8779
|
+
return rule.strategy;
|
|
8780
|
+
}
|
|
8781
|
+
function applyRule(rule, value, replacement) {
|
|
8782
|
+
if (rule.strategy === "full") return replacement;
|
|
8783
|
+
const asString = stringifyScalar(value);
|
|
8784
|
+
if (rule.strategy === "prefix") {
|
|
8785
|
+
if (asString === void 0) return replacement;
|
|
8786
|
+
const keep = Math.max(0, Math.floor(rule.keep));
|
|
8787
|
+
return asString.length <= keep ? `${asString}\u2026` : `${asString.slice(0, keep)}\u2026`;
|
|
8788
|
+
}
|
|
8789
|
+
if (rule.strategy === "hash") {
|
|
8790
|
+
if (asString === void 0) return "[HASH:unknown]";
|
|
8791
|
+
return `[HASH:${stableHash2(asString)}]`;
|
|
8792
|
+
}
|
|
8793
|
+
return value;
|
|
8794
|
+
}
|
|
8795
|
+
function childPath(path15, key) {
|
|
8796
|
+
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) {
|
|
8797
|
+
return path15 ? `${path15}.${key}` : key;
|
|
8798
|
+
}
|
|
8799
|
+
return `${path15 || "$"}[${JSON.stringify(key)}]`;
|
|
8800
|
+
}
|
|
8801
|
+
function indexPath(path15, index) {
|
|
8802
|
+
return `${path15 || "$"}[${index}]`;
|
|
8803
|
+
}
|
|
8804
|
+
function makeFinding(path15, detector, action, matchKind, severity = "warning", preview) {
|
|
8805
|
+
return preview === void 0 ? { path: path15, detector, action, severity, matchKind } : { path: path15, detector, action, severity, matchKind, preview };
|
|
8806
|
+
}
|
|
8807
|
+
function createRedactionProfile(profile = "local") {
|
|
8808
|
+
switch (profile) {
|
|
8809
|
+
case "local":
|
|
8810
|
+
return { profile: "local", extraKeys: [] };
|
|
8811
|
+
case "share":
|
|
8812
|
+
return {
|
|
8813
|
+
profile: "share",
|
|
8814
|
+
extraKeys: SHARE_PROFILE_EXTRA_KEYS2,
|
|
8815
|
+
maxMetadataValueLengthCap: 500,
|
|
8816
|
+
maxPreviewLengthCap: 200
|
|
8817
|
+
};
|
|
8818
|
+
case "strict":
|
|
8819
|
+
return {
|
|
8820
|
+
profile: "strict",
|
|
8821
|
+
extraKeys: [...SHARE_PROFILE_EXTRA_KEYS2, ...STRICT_PROFILE_EXTRA_KEYS2],
|
|
8822
|
+
maxMetadataValueLengthCap: 200,
|
|
8823
|
+
maxPreviewLengthCap: 80
|
|
8824
|
+
};
|
|
8825
|
+
}
|
|
8826
|
+
}
|
|
8827
|
+
var Redactor2 = class {
|
|
8828
|
+
#rules;
|
|
8829
|
+
#detectors;
|
|
8830
|
+
#profile;
|
|
8831
|
+
#replacement;
|
|
8832
|
+
#maxDepth;
|
|
8833
|
+
#collectFindings;
|
|
8834
|
+
constructor(options) {
|
|
8835
|
+
const resolved = createRedactionProfile(options?.profile ?? "local");
|
|
8836
|
+
this.#profile = resolved.profile;
|
|
8837
|
+
this.#rules = compileRules2(options?.rules, [
|
|
8838
|
+
...resolved.extraKeys,
|
|
8839
|
+
...options?.extraKeys ?? []
|
|
8840
|
+
]);
|
|
8841
|
+
this.#detectors = [
|
|
8842
|
+
...builtInDetectorsForProfile(this.#profile),
|
|
8843
|
+
...options?.detectors ?? []
|
|
8844
|
+
];
|
|
8845
|
+
this.#replacement = options?.replacement ?? "[REDACTED]";
|
|
8846
|
+
this.#maxDepth = options?.maxDepth ?? 32;
|
|
8847
|
+
this.#collectFindings = options?.collectFindings ?? true;
|
|
8848
|
+
}
|
|
8849
|
+
redactValue(key, value) {
|
|
8850
|
+
return this.#redactValue(value, key, key, 0, {
|
|
8851
|
+
findings: [],
|
|
8852
|
+
seen: /* @__PURE__ */ new WeakMap()
|
|
8853
|
+
});
|
|
8854
|
+
}
|
|
8855
|
+
redactRecord(record) {
|
|
8856
|
+
return this.redact(record).value;
|
|
8857
|
+
}
|
|
8858
|
+
redact(value) {
|
|
8859
|
+
const state = {
|
|
8860
|
+
findings: [],
|
|
8861
|
+
seen: /* @__PURE__ */ new WeakMap()
|
|
8862
|
+
};
|
|
8863
|
+
const redacted = this.#redactValue(value, void 0, "$", 0, state);
|
|
8864
|
+
return {
|
|
8865
|
+
value: redacted,
|
|
8866
|
+
findings: state.findings,
|
|
8867
|
+
redacted: state.findings.some((finding) => finding.action !== "keep"),
|
|
8868
|
+
profile: this.#profile
|
|
8869
|
+
};
|
|
8870
|
+
}
|
|
8871
|
+
#recordFinding(state, finding) {
|
|
8872
|
+
if (this.#collectFindings) state.findings.push(finding);
|
|
8873
|
+
}
|
|
8874
|
+
#redactValue(value, key, path15, depth, state) {
|
|
8875
|
+
if (depth > this.#maxDepth) {
|
|
8876
|
+
this.#recordFinding(
|
|
8877
|
+
state,
|
|
8878
|
+
makeFinding(path15, "structure.maxDepth", "truncate", "value", "warning")
|
|
8879
|
+
);
|
|
8880
|
+
return "[Truncated]";
|
|
8881
|
+
}
|
|
8882
|
+
if (key !== void 0) {
|
|
8883
|
+
const rule = this.#rules.find((candidate) => candidate.key === toKey2(key));
|
|
8884
|
+
if (rule) {
|
|
8885
|
+
this.#recordFinding(
|
|
8886
|
+
state,
|
|
8887
|
+
makeFinding(path15, `key.${rule.key}`, actionForRule(rule), "key", "warning")
|
|
8888
|
+
);
|
|
8889
|
+
return applyRule(rule, value, this.#replacement);
|
|
8890
|
+
}
|
|
8891
|
+
}
|
|
8892
|
+
for (const detector of this.#detectors) {
|
|
8893
|
+
const detections = detector.detect({ path: path15, key, value });
|
|
8894
|
+
for (const detection of detections) {
|
|
8895
|
+
const action = detection.action ?? "replace";
|
|
8896
|
+
this.#recordFinding(
|
|
8897
|
+
state,
|
|
8898
|
+
makeFinding(
|
|
8899
|
+
path15,
|
|
8900
|
+
detector.id,
|
|
8901
|
+
action,
|
|
8902
|
+
detection.matchKind ?? detector.matchKind ?? "custom",
|
|
8903
|
+
detection.severity ?? detector.severity ?? "warning",
|
|
8904
|
+
detection.preview
|
|
8905
|
+
)
|
|
8906
|
+
);
|
|
8907
|
+
if (action !== "keep") {
|
|
8908
|
+
return detection.replacement ?? this.#replacement;
|
|
8909
|
+
}
|
|
8910
|
+
}
|
|
8911
|
+
}
|
|
8912
|
+
if (Array.isArray(value)) {
|
|
8913
|
+
if (state.seen.has(value)) return state.seen.get(value);
|
|
8914
|
+
const out = [];
|
|
8915
|
+
state.seen.set(value, out);
|
|
8916
|
+
value.forEach((item, index) => {
|
|
8917
|
+
out[index] = this.#redactValue(item, void 0, indexPath(path15, index), depth + 1, state);
|
|
8918
|
+
});
|
|
8919
|
+
return out;
|
|
8920
|
+
}
|
|
8921
|
+
if (isRecord13(value)) {
|
|
8922
|
+
if (state.seen.has(value)) return state.seen.get(value);
|
|
8923
|
+
const out = {};
|
|
8924
|
+
state.seen.set(value, out);
|
|
8925
|
+
for (const [entryKey, entryValue] of Object.entries(value)) {
|
|
8926
|
+
out[entryKey] = this.#redactValue(
|
|
8927
|
+
entryValue,
|
|
8928
|
+
entryKey,
|
|
8929
|
+
childPath(path15 === "$" ? "" : path15, entryKey),
|
|
8930
|
+
depth + 1,
|
|
8931
|
+
state
|
|
8932
|
+
);
|
|
8933
|
+
}
|
|
8934
|
+
return out;
|
|
8935
|
+
}
|
|
8936
|
+
return value;
|
|
8937
|
+
}
|
|
8938
|
+
};
|
|
8939
|
+
function createRedactor(options) {
|
|
8940
|
+
return new Redactor2(options);
|
|
8941
|
+
}
|
|
8942
|
+
function redact(value, options) {
|
|
8943
|
+
return createRedactor(options).redact(value);
|
|
8944
|
+
}
|
|
8568
8945
|
async function readStdin(stdin) {
|
|
8569
8946
|
stdin.setEncoding("utf8");
|
|
8570
8947
|
let content = "";
|
|
@@ -8593,8 +8970,119 @@ async function inputFromTarget(target, options, stdin) {
|
|
|
8593
8970
|
return { type: "file", path: runPath };
|
|
8594
8971
|
}
|
|
8595
8972
|
|
|
8596
|
-
// packages/cli/src/
|
|
8973
|
+
// packages/cli/src/redact.ts
|
|
8597
8974
|
function parseRedactionProfile3(value) {
|
|
8975
|
+
if (value === void 0 || value === "local" || value === "share" || value === "strict") {
|
|
8976
|
+
return value ?? "share";
|
|
8977
|
+
}
|
|
8978
|
+
throw new Error(`Unsupported --profile "${value}". Use local, share, or strict.`);
|
|
8979
|
+
}
|
|
8980
|
+
function stable(value) {
|
|
8981
|
+
if (Array.isArray(value)) return value.map(stable);
|
|
8982
|
+
if (value === null || typeof value !== "object") return value;
|
|
8983
|
+
const record = value;
|
|
8984
|
+
return Object.fromEntries(
|
|
8985
|
+
Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable(record[key])])
|
|
8986
|
+
);
|
|
8987
|
+
}
|
|
8988
|
+
function isMissingFileError3(error) {
|
|
8989
|
+
return error !== null && typeof error === "object" && "code" in error && error.code === "ENOENT";
|
|
8990
|
+
}
|
|
8991
|
+
async function contentFromTarget(target, options, stdin) {
|
|
8992
|
+
if (target === "-") {
|
|
8993
|
+
return { content: await readStdin(stdin), source: "stdin" };
|
|
8994
|
+
}
|
|
8995
|
+
try {
|
|
8996
|
+
const stats2 = await promises.stat(target);
|
|
8997
|
+
if (stats2.isDirectory()) {
|
|
8998
|
+
throw new Error("redact requires a trace file, JSON file, stdin, or run id.");
|
|
8999
|
+
}
|
|
9000
|
+
return { content: await promises.readFile(target, "utf-8"), source: target };
|
|
9001
|
+
} catch (error) {
|
|
9002
|
+
if (!isMissingFileError3(error)) throw error;
|
|
9003
|
+
}
|
|
9004
|
+
const runPath = getTraceFilePath(target, resolveTraceDir({ dir: options.dir }));
|
|
9005
|
+
const stats = await promises.stat(runPath);
|
|
9006
|
+
if (stats.isDirectory()) {
|
|
9007
|
+
throw new Error("redact requires a trace file, JSON file, stdin, or run id.");
|
|
9008
|
+
}
|
|
9009
|
+
return { content: await promises.readFile(runPath, "utf-8"), source: runPath };
|
|
9010
|
+
}
|
|
9011
|
+
function redactJsonText(content, profile) {
|
|
9012
|
+
const parsed = JSON.parse(content);
|
|
9013
|
+
const result = redact(parsed, { profile });
|
|
9014
|
+
return {
|
|
9015
|
+
content: `${JSON.stringify(result.value, null, 2)}
|
|
9016
|
+
`,
|
|
9017
|
+
findings: result.findings
|
|
9018
|
+
};
|
|
9019
|
+
}
|
|
9020
|
+
function redactJsonlText(content, profile) {
|
|
9021
|
+
const lines = content.split(/\r?\n/);
|
|
9022
|
+
const out = [];
|
|
9023
|
+
const findings = [];
|
|
9024
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
9025
|
+
const line = lines[index] ?? "";
|
|
9026
|
+
if (line.trim() === "") continue;
|
|
9027
|
+
let parsed;
|
|
9028
|
+
try {
|
|
9029
|
+
parsed = JSON.parse(line);
|
|
9030
|
+
} catch {
|
|
9031
|
+
throw new Error(`Input is not valid JSON or JSONL at line ${index + 1}.`);
|
|
9032
|
+
}
|
|
9033
|
+
const result = redact(parsed, { profile });
|
|
9034
|
+
out.push(JSON.stringify(result.value));
|
|
9035
|
+
findings.push(
|
|
9036
|
+
...result.findings.map((finding) => ({
|
|
9037
|
+
...finding,
|
|
9038
|
+
path: `line:${index + 1}:${finding.path}`
|
|
9039
|
+
}))
|
|
9040
|
+
);
|
|
9041
|
+
}
|
|
9042
|
+
return {
|
|
9043
|
+
content: out.length === 0 ? "" : `${out.join("\n")}
|
|
9044
|
+
`,
|
|
9045
|
+
findings
|
|
9046
|
+
};
|
|
9047
|
+
}
|
|
9048
|
+
function redactDocument(content, profile) {
|
|
9049
|
+
try {
|
|
9050
|
+
return redactJsonText(content, profile);
|
|
9051
|
+
} catch {
|
|
9052
|
+
return redactJsonlText(content, profile);
|
|
9053
|
+
}
|
|
9054
|
+
}
|
|
9055
|
+
async function redactCommand(target, options = {}, stdin = process.stdin) {
|
|
9056
|
+
const profile = parseRedactionProfile3(options.profile);
|
|
9057
|
+
const source = await contentFromTarget(target, options, stdin);
|
|
9058
|
+
const redacted = redactDocument(source.content, profile);
|
|
9059
|
+
if (options.output !== void 0) {
|
|
9060
|
+
await promises.writeFile(options.output, redacted.content, "utf-8");
|
|
9061
|
+
}
|
|
9062
|
+
if (options.json) {
|
|
9063
|
+
console.log(
|
|
9064
|
+
JSON.stringify(
|
|
9065
|
+
stable({
|
|
9066
|
+
ok: true,
|
|
9067
|
+
profile,
|
|
9068
|
+
source: source.source,
|
|
9069
|
+
output: options.output,
|
|
9070
|
+
findings: redacted.findings,
|
|
9071
|
+
content: options.output === void 0 ? redacted.content : void 0
|
|
9072
|
+
}),
|
|
9073
|
+
null,
|
|
9074
|
+
2
|
|
9075
|
+
)
|
|
9076
|
+
);
|
|
9077
|
+
return;
|
|
9078
|
+
}
|
|
9079
|
+
if (options.output === void 0) {
|
|
9080
|
+
process.stdout.write(redacted.content);
|
|
9081
|
+
}
|
|
9082
|
+
}
|
|
9083
|
+
|
|
9084
|
+
// packages/cli/src/explain.ts
|
|
9085
|
+
function parseRedactionProfile4(value) {
|
|
8598
9086
|
const profile = (value ?? "local").trim().toLowerCase();
|
|
8599
9087
|
if (profile === "local" || profile === "share" || profile === "strict") {
|
|
8600
9088
|
return profile;
|
|
@@ -8665,7 +9153,7 @@ async function explainCommand(target, options = {}, stdin = process.stdin) {
|
|
|
8665
9153
|
}
|
|
8666
9154
|
let redactionProfile;
|
|
8667
9155
|
try {
|
|
8668
|
-
redactionProfile =
|
|
9156
|
+
redactionProfile = parseRedactionProfile4(options.redactionProfile);
|
|
8669
9157
|
} catch (error) {
|
|
8670
9158
|
process.exitCode = 1;
|
|
8671
9159
|
console.error(error instanceof Error ? error.message : String(error));
|
|
@@ -8863,7 +9351,7 @@ async function openCommand(input3, options = {}, stdin = process.stdin) {
|
|
|
8863
9351
|
}
|
|
8864
9352
|
}
|
|
8865
9353
|
}
|
|
8866
|
-
function
|
|
9354
|
+
function isRecord14(value) {
|
|
8867
9355
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8868
9356
|
}
|
|
8869
9357
|
function parseTarget(value) {
|
|
@@ -8872,7 +9360,7 @@ function parseTarget(value) {
|
|
|
8872
9360
|
throw new Error('Unsupported migration target. Use "--to 1.0".');
|
|
8873
9361
|
}
|
|
8874
9362
|
function formatOf(value) {
|
|
8875
|
-
if (!
|
|
9363
|
+
if (!isRecord14(value)) return "unknown";
|
|
8876
9364
|
if (value.schemaVersion === "0.1") return "0.1";
|
|
8877
9365
|
if (value.schemaVersion === "0.2") return "0.2";
|
|
8878
9366
|
if (value.schemaVersion === "1.0") return "1.0";
|
|
@@ -8885,14 +9373,14 @@ function uniqueSorted(values) {
|
|
|
8885
9373
|
return [...new Set(values)].sort();
|
|
8886
9374
|
}
|
|
8887
9375
|
function isWithinDirectory(child, parent) {
|
|
8888
|
-
const relative =
|
|
8889
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
9376
|
+
const relative = path13__default.default.relative(parent, child);
|
|
9377
|
+
return relative === "" || !relative.startsWith("..") && !path13__default.default.isAbsolute(relative);
|
|
8890
9378
|
}
|
|
8891
9379
|
async function resolveOutputPath(inputPath, output2, force) {
|
|
8892
9380
|
if (output2 === void 0 || output2.trim() === "") return void 0;
|
|
8893
|
-
const inputAbs =
|
|
8894
|
-
const outputAbs =
|
|
8895
|
-
const inputDir =
|
|
9381
|
+
const inputAbs = path13__default.default.resolve(inputPath);
|
|
9382
|
+
const outputAbs = path13__default.default.resolve(output2.trim());
|
|
9383
|
+
const inputDir = path13__default.default.dirname(inputAbs);
|
|
8896
9384
|
if (!isWithinDirectory(outputAbs, inputDir)) {
|
|
8897
9385
|
throw new Error("Refusing to write migrated output outside the input directory.");
|
|
8898
9386
|
}
|
|
@@ -9013,7 +9501,7 @@ async function migrateCommand(input3, options = {}) {
|
|
|
9013
9501
|
process.exitCode = 1;
|
|
9014
9502
|
return;
|
|
9015
9503
|
}
|
|
9016
|
-
const inputPath =
|
|
9504
|
+
const inputPath = path13__default.default.resolve(input3.trim());
|
|
9017
9505
|
const dryRun = options.dryRun === true;
|
|
9018
9506
|
if (!dryRun && (options.output === void 0 || options.output.trim() === "")) {
|
|
9019
9507
|
console.error("migrate requires --dry-run or --output <path>.");
|
|
@@ -9032,7 +9520,7 @@ async function migrateCommand(input3, options = {}) {
|
|
|
9032
9520
|
);
|
|
9033
9521
|
const result = await buildMigration(inputPath, outputPath);
|
|
9034
9522
|
if (!dryRun && outputPath !== void 0) {
|
|
9035
|
-
await promises.mkdir(
|
|
9523
|
+
await promises.mkdir(path13__default.default.dirname(outputPath), { recursive: true });
|
|
9036
9524
|
await promises.writeFile(outputPath, result.content, "utf-8");
|
|
9037
9525
|
}
|
|
9038
9526
|
printSummary2(result, dryRun);
|
|
@@ -9310,7 +9798,7 @@ function stripPrefix(name, prefixes) {
|
|
|
9310
9798
|
}
|
|
9311
9799
|
return name;
|
|
9312
9800
|
}
|
|
9313
|
-
function eventEvidence(event,
|
|
9801
|
+
function eventEvidence(event, path15) {
|
|
9314
9802
|
return {
|
|
9315
9803
|
runId: event.runId,
|
|
9316
9804
|
eventId: event.eventId,
|
|
@@ -9320,7 +9808,7 @@ function eventEvidence(event, path12) {
|
|
|
9320
9808
|
kind: event.kind,
|
|
9321
9809
|
name: event.name,
|
|
9322
9810
|
status: event.status,
|
|
9323
|
-
...
|
|
9811
|
+
...path15 ? { path: path15 } : {}
|
|
9324
9812
|
};
|
|
9325
9813
|
}
|
|
9326
9814
|
function runEvidence(run) {
|
|
@@ -9357,7 +9845,7 @@ function finishedEvents(context, kind) {
|
|
|
9357
9845
|
(event) => (kind === void 0 || event.kind === kind) && event.status !== "running"
|
|
9358
9846
|
);
|
|
9359
9847
|
}
|
|
9360
|
-
function
|
|
9848
|
+
function isRecord15(value) {
|
|
9361
9849
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9362
9850
|
}
|
|
9363
9851
|
function eventMap(events) {
|
|
@@ -9383,9 +9871,9 @@ function eventEndMs(event) {
|
|
|
9383
9871
|
function normalizedKey(value) {
|
|
9384
9872
|
return value.toLowerCase().replace(/[^a-z0-9_]/g, "");
|
|
9385
9873
|
}
|
|
9386
|
-
function lastPathSegment(
|
|
9387
|
-
const parts =
|
|
9388
|
-
return parts[parts.length - 1] ??
|
|
9874
|
+
function lastPathSegment(path15) {
|
|
9875
|
+
const parts = path15.split(".");
|
|
9876
|
+
return parts[parts.length - 1] ?? path15;
|
|
9389
9877
|
}
|
|
9390
9878
|
function valueType(value) {
|
|
9391
9879
|
if (Array.isArray(value)) return "array";
|
|
@@ -9399,22 +9887,22 @@ function serializedByteLength(value) {
|
|
|
9399
9887
|
return void 0;
|
|
9400
9888
|
}
|
|
9401
9889
|
}
|
|
9402
|
-
function pushValueEntries(entries, event, value,
|
|
9403
|
-
entries.push({ event, path:
|
|
9890
|
+
function pushValueEntries(entries, event, value, path15, key, depth = 0) {
|
|
9891
|
+
entries.push({ event, path: path15, key, value });
|
|
9404
9892
|
if (depth >= 8) return;
|
|
9405
9893
|
if (Array.isArray(value)) {
|
|
9406
9894
|
for (const [index, item] of value.entries()) {
|
|
9407
|
-
pushValueEntries(entries, event, item, `${
|
|
9895
|
+
pushValueEntries(entries, event, item, `${path15}.${index}`, String(index), depth + 1);
|
|
9408
9896
|
}
|
|
9409
9897
|
return;
|
|
9410
9898
|
}
|
|
9411
|
-
if (!
|
|
9899
|
+
if (!isRecord15(value)) return;
|
|
9412
9900
|
for (const nestedKey of Object.keys(value).sort((a, b) => a.localeCompare(b))) {
|
|
9413
9901
|
pushValueEntries(
|
|
9414
9902
|
entries,
|
|
9415
9903
|
event,
|
|
9416
9904
|
value[nestedKey],
|
|
9417
|
-
`${
|
|
9905
|
+
`${path15}.${nestedKey}`,
|
|
9418
9906
|
nestedKey,
|
|
9419
9907
|
depth + 1
|
|
9420
9908
|
);
|
|
@@ -9495,9 +9983,9 @@ function eventDurationMs(event) {
|
|
|
9495
9983
|
}
|
|
9496
9984
|
function treeShape(nodes) {
|
|
9497
9985
|
const lines = [];
|
|
9498
|
-
const visit = (node,
|
|
9499
|
-
lines.push(`${
|
|
9500
|
-
node.children.forEach((child, index) => visit(child, `${
|
|
9986
|
+
const visit = (node, path15) => {
|
|
9987
|
+
lines.push(`${path15}:${node.event.kind}:${node.event.name}:${node.event.status ?? "unknown"}`);
|
|
9988
|
+
node.children.forEach((child, index) => visit(child, `${path15}.${index}`));
|
|
9501
9989
|
};
|
|
9502
9990
|
nodes.forEach((node, index) => visit(node, String(index)));
|
|
9503
9991
|
return lines;
|
|
@@ -9546,9 +10034,9 @@ function retrievalShape(context) {
|
|
|
9546
10034
|
function guardrailShape(context) {
|
|
9547
10035
|
return guardrailEvents(context).map((event) => signalName(event, ["guardrailName", "guardrail", "guardrailId"], ["guardrail:"])).sort((a, b) => a.localeCompare(b));
|
|
9548
10036
|
}
|
|
9549
|
-
function firstEvidenceForKind(context, kind,
|
|
10037
|
+
function firstEvidenceForKind(context, kind, path15) {
|
|
9550
10038
|
const event = context.events.find((candidate) => candidate.kind === kind);
|
|
9551
|
-
return event ? [eventEvidence(event,
|
|
10039
|
+
return event ? [eventEvidence(event, path15)] : runEvidence(context.selectedRun);
|
|
9552
10040
|
}
|
|
9553
10041
|
function baselineDiffFinding(message, evidence, expected, actual) {
|
|
9554
10042
|
return failFinding("baseline.regression", message, evidence, expected, actual);
|
|
@@ -9796,13 +10284,13 @@ function createStructureCycleRule() {
|
|
|
9796
10284
|
const seenCycles = /* @__PURE__ */ new Set();
|
|
9797
10285
|
const findings = [];
|
|
9798
10286
|
for (const event of [...context.events].sort((a, b) => a.eventId.localeCompare(b.eventId))) {
|
|
9799
|
-
const
|
|
10287
|
+
const path15 = [];
|
|
9800
10288
|
const seenAt = /* @__PURE__ */ new Map();
|
|
9801
10289
|
let current = event;
|
|
9802
10290
|
while (current) {
|
|
9803
10291
|
const existing = seenAt.get(current.eventId);
|
|
9804
10292
|
if (existing !== void 0) {
|
|
9805
|
-
const cycle =
|
|
10293
|
+
const cycle = path15.slice(existing);
|
|
9806
10294
|
const key = cycle.map((item) => item.eventId).sort().join("\0");
|
|
9807
10295
|
if (!seenCycles.has(key)) {
|
|
9808
10296
|
seenCycles.add(key);
|
|
@@ -9818,8 +10306,8 @@ function createStructureCycleRule() {
|
|
|
9818
10306
|
}
|
|
9819
10307
|
break;
|
|
9820
10308
|
}
|
|
9821
|
-
seenAt.set(current.eventId,
|
|
9822
|
-
|
|
10309
|
+
seenAt.set(current.eventId, path15.length);
|
|
10310
|
+
path15.push(current);
|
|
9823
10311
|
current = current.parentId ? byId.get(current.parentId) : void 0;
|
|
9824
10312
|
}
|
|
9825
10313
|
}
|
|
@@ -10093,7 +10581,7 @@ function createSafetyOversizedAttributeRule(options) {
|
|
|
10093
10581
|
)
|
|
10094
10582
|
);
|
|
10095
10583
|
}
|
|
10096
|
-
if (
|
|
10584
|
+
if (isRecord15(entry.value) && options.maxObjectKeys !== void 0 && Object.keys(entry.value).length > options.maxObjectKeys) {
|
|
10097
10585
|
findings.push(
|
|
10098
10586
|
failFinding(
|
|
10099
10587
|
"safety.oversizedAttribute",
|
|
@@ -10344,7 +10832,7 @@ function asConfig(value) {
|
|
|
10344
10832
|
}
|
|
10345
10833
|
async function loadConfig(configPath) {
|
|
10346
10834
|
if (configPath === void 0) return {};
|
|
10347
|
-
const extension =
|
|
10835
|
+
const extension = path13__default.default.extname(configPath);
|
|
10348
10836
|
if (TS_CONFIG_EXTENSIONS.has(extension)) {
|
|
10349
10837
|
throw new Error(
|
|
10350
10838
|
"TypeScript check configs require an explicit precompiled JavaScript config or future --config-loader support."
|
|
@@ -10353,7 +10841,7 @@ async function loadConfig(configPath) {
|
|
|
10353
10841
|
if (!CONFIG_EXTENSIONS.has(extension)) {
|
|
10354
10842
|
throw new Error("Unsupported check config extension. Use .json, .js, .mjs, or .cjs.");
|
|
10355
10843
|
}
|
|
10356
|
-
const absolute =
|
|
10844
|
+
const absolute = path13__default.default.resolve(configPath);
|
|
10357
10845
|
if (extension === ".json") {
|
|
10358
10846
|
const raw = await promises.readFile(absolute, "utf-8");
|
|
10359
10847
|
return asConfig(JSON.parse(raw));
|
|
@@ -10370,12 +10858,12 @@ function normalizeConfig(config) {
|
|
|
10370
10858
|
}
|
|
10371
10859
|
function buildRules(config, options) {
|
|
10372
10860
|
const diagnostics = [];
|
|
10373
|
-
const
|
|
10374
|
-
const run =
|
|
10375
|
-
const tool =
|
|
10376
|
-
const llm =
|
|
10377
|
-
const structure =
|
|
10378
|
-
const safety =
|
|
10861
|
+
const checks2 = normalizeConfig(config);
|
|
10862
|
+
const run = checks2.run ?? {};
|
|
10863
|
+
const tool = checks2.tool ?? {};
|
|
10864
|
+
const llm = checks2.llm ?? {};
|
|
10865
|
+
const structure = checks2.structure ?? {};
|
|
10866
|
+
const safety = checks2.safety ?? {};
|
|
10379
10867
|
const maxDurationMs = parseNumber(options.maxDurationMs, "--max-duration-ms") ?? run.maxDurationMs;
|
|
10380
10868
|
const maxTotalTokens = parseNumber(options.maxTotalTokens, "--max-total-tokens") ?? llm.maxTotalTokens;
|
|
10381
10869
|
const rules = [
|
|
@@ -10423,7 +10911,7 @@ function buildRules(config, options) {
|
|
|
10423
10911
|
rules.push(createSafetyOversizedAttributeRule(safety));
|
|
10424
10912
|
}
|
|
10425
10913
|
const select = [
|
|
10426
|
-
...asStringArray(
|
|
10914
|
+
...asStringArray(checks2.select) ?? [],
|
|
10427
10915
|
...options.rule ?? []
|
|
10428
10916
|
];
|
|
10429
10917
|
return {
|
|
@@ -10453,16 +10941,16 @@ function exitCodeFor(result) {
|
|
|
10453
10941
|
}
|
|
10454
10942
|
return 1;
|
|
10455
10943
|
}
|
|
10456
|
-
function
|
|
10457
|
-
if (Array.isArray(value)) return value.map(
|
|
10944
|
+
function stable2(value) {
|
|
10945
|
+
if (Array.isArray(value)) return value.map(stable2);
|
|
10458
10946
|
if (value === null || typeof value !== "object") return value;
|
|
10459
10947
|
const record = value;
|
|
10460
10948
|
return Object.fromEntries(
|
|
10461
|
-
Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key,
|
|
10949
|
+
Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable2(record[key])])
|
|
10462
10950
|
);
|
|
10463
10951
|
}
|
|
10464
10952
|
function printJson(result) {
|
|
10465
|
-
console.log(JSON.stringify(
|
|
10953
|
+
console.log(JSON.stringify(stable2(result), null, 2));
|
|
10466
10954
|
}
|
|
10467
10955
|
function printHuman(result) {
|
|
10468
10956
|
console.log(`Check status: ${result.status}`);
|
|
@@ -10471,12 +10959,12 @@ function printHuman(result) {
|
|
|
10471
10959
|
console.log(
|
|
10472
10960
|
`Summary: ${result.summary.failed} failed, ${result.summary.warnings} warning(s), ${result.summary.errors} error(s)`
|
|
10473
10961
|
);
|
|
10474
|
-
for (const
|
|
10475
|
-
console.log(`- ${
|
|
10962
|
+
for (const diagnostic4 of result.diagnostics) {
|
|
10963
|
+
console.log(`- ${diagnostic4.code}: ${diagnostic4.message}`);
|
|
10476
10964
|
}
|
|
10477
10965
|
for (const finding of result.findings) {
|
|
10478
|
-
const
|
|
10479
|
-
console.log(`- ${finding.ruleId}: ${finding.message}${
|
|
10966
|
+
const path15 = finding.evidence[0]?.path;
|
|
10967
|
+
console.log(`- ${finding.ruleId}: ${finding.message}${path15 ? ` (${path15})` : ""}`);
|
|
10480
10968
|
}
|
|
10481
10969
|
}
|
|
10482
10970
|
function readErrorResult(error) {
|
|
@@ -10530,42 +11018,996 @@ async function checkCommand(target, options = {}, stdin = process.stdin) {
|
|
|
10530
11018
|
else printHuman(result);
|
|
10531
11019
|
}
|
|
10532
11020
|
|
|
10533
|
-
// packages/
|
|
10534
|
-
|
|
10535
|
-
|
|
10536
|
-
|
|
10537
|
-
|
|
10538
|
-
|
|
10539
|
-
|
|
10540
|
-
|
|
10541
|
-
|
|
10542
|
-
|
|
10543
|
-
|
|
11021
|
+
// packages/eval/src/index.ts
|
|
11022
|
+
function isTraceReadResult(value) {
|
|
11023
|
+
return value !== null && typeof value === "object" && "runs" in value && "events" in value && "format" in value;
|
|
11024
|
+
}
|
|
11025
|
+
function traceInputFrom(value) {
|
|
11026
|
+
return { type: "file", path: value instanceof URL ? value.pathname : value };
|
|
11027
|
+
}
|
|
11028
|
+
async function resolveRead(input3, options) {
|
|
11029
|
+
try {
|
|
11030
|
+
if (typeof input3 === "string") {
|
|
11031
|
+
const read2 = await openTrace(traceInputFrom(input3), {
|
|
11032
|
+
...options.format !== void 0 && options.format !== "auto" ? { format: options.format } : {}
|
|
11033
|
+
});
|
|
11034
|
+
return { read: read2, runId: options.runId, diagnostics: [] };
|
|
11035
|
+
}
|
|
11036
|
+
if (isTraceReadResult(input3)) {
|
|
11037
|
+
return { read: input3, runId: options.runId, diagnostics: [] };
|
|
11038
|
+
}
|
|
11039
|
+
if (isTraceReadResult(input3.trace)) {
|
|
11040
|
+
return { read: input3.trace, runId: options.runId ?? input3.runId, diagnostics: [] };
|
|
11041
|
+
}
|
|
11042
|
+
const format = options.format ?? input3.format;
|
|
11043
|
+
const read = await openTrace(traceInputFrom(input3.trace), {
|
|
11044
|
+
...format !== void 0 && format !== "auto" ? { format } : {}
|
|
11045
|
+
});
|
|
11046
|
+
return { read, runId: options.runId ?? input3.runId, diagnostics: [] };
|
|
11047
|
+
} catch (error) {
|
|
11048
|
+
return { diagnostics: [diagnosticFromError(error)] };
|
|
10544
11049
|
}
|
|
10545
|
-
return parsed;
|
|
10546
11050
|
}
|
|
10547
|
-
function
|
|
10548
|
-
if (
|
|
10549
|
-
|
|
10550
|
-
|
|
10551
|
-
|
|
10552
|
-
|
|
10553
|
-
|
|
11051
|
+
function diagnosticFromError(error) {
|
|
11052
|
+
if (error instanceof TraceReadError) {
|
|
11053
|
+
const code = error.code === "unsupported_format" ? "AI_EVAL_UNSUPPORTED_FORMAT" : error.code === "ambiguous_format" ? "AI_EVAL_AMBIGUOUS_FORMAT" : "AI_EVAL_TRACE_UNREADABLE";
|
|
11054
|
+
return { code, severity: "error", message: error.message };
|
|
11055
|
+
}
|
|
11056
|
+
return {
|
|
11057
|
+
code: "AI_EVAL_TRACE_UNREADABLE",
|
|
11058
|
+
severity: "error",
|
|
11059
|
+
message: error instanceof Error ? error.message : String(error)
|
|
11060
|
+
};
|
|
10554
11061
|
}
|
|
10555
|
-
function
|
|
10556
|
-
return
|
|
11062
|
+
function flatten2(nodes) {
|
|
11063
|
+
return nodes.flatMap((node) => [node, ...flatten2(node.children)]);
|
|
10557
11064
|
}
|
|
10558
|
-
function
|
|
10559
|
-
|
|
10560
|
-
|
|
10561
|
-
|
|
10562
|
-
|
|
10563
|
-
|
|
10564
|
-
|
|
10565
|
-
|
|
10566
|
-
|
|
10567
|
-
|
|
10568
|
-
|
|
11065
|
+
function selectRun3(read, runId) {
|
|
11066
|
+
if (runId !== void 0) {
|
|
11067
|
+
const run = read.runs.find((candidate) => candidate.runId === runId);
|
|
11068
|
+
return run === void 0 ? {
|
|
11069
|
+
diagnostics: [
|
|
11070
|
+
{
|
|
11071
|
+
code: "AI_EVAL_RUN_SELECTION_REQUIRED",
|
|
11072
|
+
severity: "error",
|
|
11073
|
+
message: `Run not found: ${runId}.`
|
|
11074
|
+
}
|
|
11075
|
+
]
|
|
11076
|
+
} : { run, diagnostics: [] };
|
|
11077
|
+
}
|
|
11078
|
+
if (read.runs.length === 1) {
|
|
11079
|
+
return { run: read.runs[0], diagnostics: [] };
|
|
11080
|
+
}
|
|
11081
|
+
if (read.runs.length === 0) {
|
|
11082
|
+
return {
|
|
11083
|
+
diagnostics: [
|
|
11084
|
+
{
|
|
11085
|
+
code: "AI_EVAL_RUN_SELECTION_REQUIRED",
|
|
11086
|
+
severity: "error",
|
|
11087
|
+
message: "No runs are available for eval."
|
|
11088
|
+
}
|
|
11089
|
+
]
|
|
11090
|
+
};
|
|
11091
|
+
}
|
|
11092
|
+
return {
|
|
11093
|
+
diagnostics: [
|
|
11094
|
+
{
|
|
11095
|
+
code: "AI_EVAL_RUN_SELECTION_REQUIRED",
|
|
11096
|
+
severity: "error",
|
|
11097
|
+
message: "Multiple runs are available; select a run before eval."
|
|
11098
|
+
}
|
|
11099
|
+
]
|
|
11100
|
+
};
|
|
11101
|
+
}
|
|
11102
|
+
function errorResult3(format, diagnostics, runId) {
|
|
11103
|
+
const errors = diagnostics.filter((item) => item.severity === "error").length;
|
|
11104
|
+
return {
|
|
11105
|
+
ok: false,
|
|
11106
|
+
status: "error",
|
|
11107
|
+
format,
|
|
11108
|
+
...runId !== void 0 ? { runId } : {},
|
|
11109
|
+
summary: { passed: 0, failed: 0, warnings: 0, errors },
|
|
11110
|
+
findings: [],
|
|
11111
|
+
diagnostics: [...diagnostics]
|
|
11112
|
+
};
|
|
11113
|
+
}
|
|
11114
|
+
function normalizeFinding2(rule, finding) {
|
|
11115
|
+
return {
|
|
11116
|
+
...finding,
|
|
11117
|
+
ruleId: finding.ruleId || rule.id,
|
|
11118
|
+
severity: finding.severity ?? rule.severity ?? "error",
|
|
11119
|
+
evidence: [...finding.evidence]
|
|
11120
|
+
};
|
|
11121
|
+
}
|
|
11122
|
+
function compareFindings2(a, b) {
|
|
11123
|
+
const aEvidence = a.evidence[0];
|
|
11124
|
+
const bEvidence = b.evidence[0];
|
|
11125
|
+
return a.ruleId.localeCompare(b.ruleId) || (aEvidence?.runId ?? "").localeCompare(bEvidence?.runId ?? "") || (aEvidence?.eventId ?? "").localeCompare(bEvidence?.eventId ?? "") || (aEvidence?.path ?? "").localeCompare(bEvidence?.path ?? "") || a.message.localeCompare(b.message);
|
|
11126
|
+
}
|
|
11127
|
+
function summarize2(findings, ruleCount) {
|
|
11128
|
+
const failed = findings.filter((item) => item.status === "fail").length;
|
|
11129
|
+
const warnings = findings.filter((item) => item.status === "warning").length;
|
|
11130
|
+
const errors = findings.filter((item) => item.severity === "error").length;
|
|
11131
|
+
return {
|
|
11132
|
+
passed: Math.max(0, ruleCount - failed - warnings),
|
|
11133
|
+
failed,
|
|
11134
|
+
warnings,
|
|
11135
|
+
errors
|
|
11136
|
+
};
|
|
11137
|
+
}
|
|
11138
|
+
function defaultRules() {
|
|
11139
|
+
return [checks.requireSuccess()];
|
|
11140
|
+
}
|
|
11141
|
+
async function evalRun(input3, options = {}) {
|
|
11142
|
+
const resolved = await resolveRead(input3, options);
|
|
11143
|
+
if (resolved.read === void 0) {
|
|
11144
|
+
return errorResult3("unknown", resolved.diagnostics);
|
|
11145
|
+
}
|
|
11146
|
+
const selected = selectRun3(resolved.read, resolved.runId);
|
|
11147
|
+
if (selected.run === void 0) {
|
|
11148
|
+
return errorResult3(resolved.read.format, selected.diagnostics, resolved.runId);
|
|
11149
|
+
}
|
|
11150
|
+
const rules = [...options.checks ?? defaultRules()].sort(
|
|
11151
|
+
(a, b) => a.id.localeCompare(b.id)
|
|
11152
|
+
);
|
|
11153
|
+
const context = {
|
|
11154
|
+
format: resolved.read.format,
|
|
11155
|
+
run: selected.run,
|
|
11156
|
+
nodes: flatten2(selected.run.children),
|
|
11157
|
+
events: resolved.read.events.filter((event) => event.runId === selected.run?.runId)
|
|
11158
|
+
};
|
|
11159
|
+
const diagnostics = [];
|
|
11160
|
+
const findings = [];
|
|
11161
|
+
for (const rule of rules) {
|
|
11162
|
+
try {
|
|
11163
|
+
findings.push(...rule.evaluate(context).map((finding) => normalizeFinding2(rule, finding)));
|
|
11164
|
+
} catch (error) {
|
|
11165
|
+
diagnostics.push({
|
|
11166
|
+
code: "AI_EVAL_RULE_FAILED",
|
|
11167
|
+
severity: "error",
|
|
11168
|
+
ruleId: rule.id,
|
|
11169
|
+
message: `Eval rule ${rule.id} failed: ${error instanceof Error ? error.message : String(error)}`
|
|
11170
|
+
});
|
|
11171
|
+
}
|
|
11172
|
+
}
|
|
11173
|
+
if (diagnostics.length > 0) {
|
|
11174
|
+
return errorResult3(resolved.read.format, diagnostics, selected.run.runId);
|
|
11175
|
+
}
|
|
11176
|
+
const sortedFindings = findings.sort(compareFindings2);
|
|
11177
|
+
const summary = summarize2(sortedFindings, rules.length);
|
|
11178
|
+
const status = summary.failed > 0 ? "fail" : "pass";
|
|
11179
|
+
return {
|
|
11180
|
+
ok: status === "pass",
|
|
11181
|
+
status,
|
|
11182
|
+
format: resolved.read.format,
|
|
11183
|
+
runId: selected.run.runId,
|
|
11184
|
+
summary,
|
|
11185
|
+
findings: sortedFindings,
|
|
11186
|
+
diagnostics: []
|
|
11187
|
+
};
|
|
11188
|
+
}
|
|
11189
|
+
function evidenceForRun(run, path15) {
|
|
11190
|
+
return [{ runId: run.runId, ...path15 !== void 0 ? { path: path15 } : {} }];
|
|
11191
|
+
}
|
|
11192
|
+
function evidenceForEvent(event, path15) {
|
|
11193
|
+
return [
|
|
11194
|
+
{
|
|
11195
|
+
runId: event.runId,
|
|
11196
|
+
eventId: event.eventId,
|
|
11197
|
+
...event.parentId !== void 0 ? { parentId: event.parentId } : {},
|
|
11198
|
+
kind: event.kind,
|
|
11199
|
+
name: event.name,
|
|
11200
|
+
...path15 !== void 0 ? { path: path15 } : {}
|
|
11201
|
+
}
|
|
11202
|
+
];
|
|
11203
|
+
}
|
|
11204
|
+
function fail(ruleId, message, evidence, expected, actual) {
|
|
11205
|
+
return {
|
|
11206
|
+
ruleId,
|
|
11207
|
+
status: "fail",
|
|
11208
|
+
severity: "error",
|
|
11209
|
+
message,
|
|
11210
|
+
...expected !== void 0 ? { expected } : {},
|
|
11211
|
+
...actual !== void 0 ? { actual } : {},
|
|
11212
|
+
evidence
|
|
11213
|
+
};
|
|
11214
|
+
}
|
|
11215
|
+
function nodeNames(nodes, kind) {
|
|
11216
|
+
return nodes.filter((node) => node.event.kind === kind).map((node) => node.event.name).sort((a, b) => a.localeCompare(b));
|
|
11217
|
+
}
|
|
11218
|
+
function hasAttribute(node, key) {
|
|
11219
|
+
return node.event.attributes !== void 0 && Object.prototype.hasOwnProperty.call(node.event.attributes, key);
|
|
11220
|
+
}
|
|
11221
|
+
function numericAttribute(node, keys) {
|
|
11222
|
+
const attrs = node.event.attributes;
|
|
11223
|
+
if (attrs === void 0) return void 0;
|
|
11224
|
+
for (const key of keys) {
|
|
11225
|
+
const value = attrs[key];
|
|
11226
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
11227
|
+
}
|
|
11228
|
+
return void 0;
|
|
11229
|
+
}
|
|
11230
|
+
function totalTokenCount(events) {
|
|
11231
|
+
return events.reduce((total, event) => {
|
|
11232
|
+
const usage = event.tokenUsage;
|
|
11233
|
+
if (usage?.total !== void 0) return total + usage.total;
|
|
11234
|
+
return total + (usage?.input ?? 0) + (usage?.output ?? 0);
|
|
11235
|
+
}, 0);
|
|
11236
|
+
}
|
|
11237
|
+
function createRule(id, category, evaluate) {
|
|
11238
|
+
return { id, category, severity: "error", evaluate };
|
|
11239
|
+
}
|
|
11240
|
+
var DEFAULT_ANSWER_KEYS = [
|
|
11241
|
+
"answer",
|
|
11242
|
+
"finalAnswer",
|
|
11243
|
+
"final",
|
|
11244
|
+
"response",
|
|
11245
|
+
"result",
|
|
11246
|
+
"output",
|
|
11247
|
+
"outputPreview",
|
|
11248
|
+
"completion",
|
|
11249
|
+
"text"
|
|
11250
|
+
];
|
|
11251
|
+
var DEFAULT_CONTEXT_KEYS = [
|
|
11252
|
+
"context",
|
|
11253
|
+
"contexts",
|
|
11254
|
+
"document",
|
|
11255
|
+
"documents",
|
|
11256
|
+
"retrieved",
|
|
11257
|
+
"retrieval",
|
|
11258
|
+
"chunks",
|
|
11259
|
+
"chunk",
|
|
11260
|
+
"source",
|
|
11261
|
+
"sources",
|
|
11262
|
+
"sourceText"
|
|
11263
|
+
];
|
|
11264
|
+
var DEFAULT_CITATION_KEYS = [
|
|
11265
|
+
"citation",
|
|
11266
|
+
"citations",
|
|
11267
|
+
"reference",
|
|
11268
|
+
"references",
|
|
11269
|
+
"sourceId",
|
|
11270
|
+
"sourceIds",
|
|
11271
|
+
"source_id",
|
|
11272
|
+
"source_ids"
|
|
11273
|
+
];
|
|
11274
|
+
var DEFAULT_SOURCE_ID_KEYS = [
|
|
11275
|
+
...DEFAULT_CITATION_KEYS,
|
|
11276
|
+
"id",
|
|
11277
|
+
"ids",
|
|
11278
|
+
"documentId",
|
|
11279
|
+
"documentIds",
|
|
11280
|
+
"docId",
|
|
11281
|
+
"docIds"
|
|
11282
|
+
];
|
|
11283
|
+
var DEFAULT_BANNED_UNSUPPORTED_PHRASES = [
|
|
11284
|
+
"i don't have enough information",
|
|
11285
|
+
"i do not have enough information",
|
|
11286
|
+
"not enough context",
|
|
11287
|
+
"cannot determine from the context",
|
|
11288
|
+
"unable to determine from the provided context"
|
|
11289
|
+
];
|
|
11290
|
+
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
11291
|
+
"a",
|
|
11292
|
+
"an",
|
|
11293
|
+
"and",
|
|
11294
|
+
"are",
|
|
11295
|
+
"but",
|
|
11296
|
+
"for",
|
|
11297
|
+
"from",
|
|
11298
|
+
"have",
|
|
11299
|
+
"into",
|
|
11300
|
+
"not",
|
|
11301
|
+
"that",
|
|
11302
|
+
"the",
|
|
11303
|
+
"their",
|
|
11304
|
+
"this",
|
|
11305
|
+
"was",
|
|
11306
|
+
"were",
|
|
11307
|
+
"with",
|
|
11308
|
+
"you",
|
|
11309
|
+
"your"
|
|
11310
|
+
]);
|
|
11311
|
+
function normalizeKey(key) {
|
|
11312
|
+
return key.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
11313
|
+
}
|
|
11314
|
+
function keySet(keys) {
|
|
11315
|
+
return new Set(keys.map(normalizeKey));
|
|
11316
|
+
}
|
|
11317
|
+
function valuesAsStrings(value, depth = 0) {
|
|
11318
|
+
if (depth > 5) return [];
|
|
11319
|
+
if (typeof value === "string") {
|
|
11320
|
+
const trimmed = value.trim();
|
|
11321
|
+
return trimmed.length > 0 ? [trimmed] : [];
|
|
11322
|
+
}
|
|
11323
|
+
if (typeof value === "number" || typeof value === "boolean") return [String(value)];
|
|
11324
|
+
if (Array.isArray(value)) {
|
|
11325
|
+
return value.flatMap((item) => valuesAsStrings(item, depth + 1));
|
|
11326
|
+
}
|
|
11327
|
+
if (value !== null && typeof value === "object") {
|
|
11328
|
+
return Object.values(value).flatMap(
|
|
11329
|
+
(item) => valuesAsStrings(item, depth + 1)
|
|
11330
|
+
);
|
|
11331
|
+
}
|
|
11332
|
+
return [];
|
|
11333
|
+
}
|
|
11334
|
+
function collectTextFields(nodes, keys, preferredKinds = []) {
|
|
11335
|
+
const wanted = keySet(keys);
|
|
11336
|
+
const preferred = new Set(preferredKinds);
|
|
11337
|
+
const orderedNodes = [...nodes].sort((a, b) => {
|
|
11338
|
+
const aPreferred = preferred.has(a.event.kind) ? 0 : 1;
|
|
11339
|
+
const bPreferred = preferred.has(b.event.kind) ? 0 : 1;
|
|
11340
|
+
return aPreferred - bPreferred || a.event.eventId.localeCompare(b.event.eventId);
|
|
11341
|
+
});
|
|
11342
|
+
const fields = [];
|
|
11343
|
+
for (const node of orderedNodes) {
|
|
11344
|
+
const attrs = node.event.attributes;
|
|
11345
|
+
if (attrs === void 0) continue;
|
|
11346
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
11347
|
+
if (!wanted.has(normalizeKey(key))) continue;
|
|
11348
|
+
for (const text of valuesAsStrings(value)) {
|
|
11349
|
+
fields.push({ text, node, path: `attributes.${key}` });
|
|
11350
|
+
}
|
|
11351
|
+
}
|
|
11352
|
+
}
|
|
11353
|
+
return fields;
|
|
11354
|
+
}
|
|
11355
|
+
function tokenize(text) {
|
|
11356
|
+
return [...text.toLowerCase().matchAll(/[a-z0-9][a-z0-9'-]{2,}/g)].map((match) => match[0].replace(/^['-]+|['-]+$/g, "")).filter((token) => token.length > 2 && !STOP_WORDS.has(token));
|
|
11357
|
+
}
|
|
11358
|
+
function firstEvidence(fields, run, path15) {
|
|
11359
|
+
const first = fields[0];
|
|
11360
|
+
return first === void 0 ? evidenceForRun(run, path15) : evidenceForEvent(first.node.event, first.path);
|
|
11361
|
+
}
|
|
11362
|
+
function collectSourceIds(nodes, keys) {
|
|
11363
|
+
const wanted = keySet(keys);
|
|
11364
|
+
const ids = /* @__PURE__ */ new Set();
|
|
11365
|
+
for (const node of nodes) {
|
|
11366
|
+
const attrs = node.event.attributes;
|
|
11367
|
+
if (attrs === void 0) continue;
|
|
11368
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
11369
|
+
if (!wanted.has(normalizeKey(key))) continue;
|
|
11370
|
+
for (const candidate of valuesAsStrings(value)) {
|
|
11371
|
+
const trimmed = candidate.trim();
|
|
11372
|
+
if (trimmed.length > 0 && trimmed.length <= 128) ids.add(trimmed);
|
|
11373
|
+
}
|
|
11374
|
+
}
|
|
11375
|
+
}
|
|
11376
|
+
return [...ids].sort((a, b) => a.localeCompare(b));
|
|
11377
|
+
}
|
|
11378
|
+
function citationCount(answer, citationFields) {
|
|
11379
|
+
const inline = answer.match(/\[[^\]\n]{1,40}\]|\([A-Za-z][A-Za-z0-9_-]{0,39}\)/g)?.length ?? 0;
|
|
11380
|
+
return inline + citationFields.length;
|
|
11381
|
+
}
|
|
11382
|
+
function quotedSnippets(text, minLength) {
|
|
11383
|
+
const snippets = /* @__PURE__ */ new Set();
|
|
11384
|
+
const pattern = /"([^"\n]+)"|'([^'\n]+)'|“([^”\n]+)”/g;
|
|
11385
|
+
for (const match of text.matchAll(pattern)) {
|
|
11386
|
+
const snippet = (match[1] ?? match[2] ?? match[3] ?? "").trim();
|
|
11387
|
+
if (snippet.length >= minLength) snippets.add(snippet);
|
|
11388
|
+
}
|
|
11389
|
+
return [...snippets].sort((a, b) => a.localeCompare(b));
|
|
11390
|
+
}
|
|
11391
|
+
function wordCount(text) {
|
|
11392
|
+
return tokenize(text).length;
|
|
11393
|
+
}
|
|
11394
|
+
var checks = {
|
|
11395
|
+
requireSuccess() {
|
|
11396
|
+
return createRule(
|
|
11397
|
+
"eval.requireSuccess",
|
|
11398
|
+
"run",
|
|
11399
|
+
(context) => context.run.status === "ok" ? [] : [
|
|
11400
|
+
fail(
|
|
11401
|
+
"eval.requireSuccess",
|
|
11402
|
+
"Run did not complete successfully.",
|
|
11403
|
+
evidenceForRun(context.run, "status"),
|
|
11404
|
+
"ok",
|
|
11405
|
+
context.run.status ?? "unknown"
|
|
11406
|
+
)
|
|
11407
|
+
]
|
|
11408
|
+
);
|
|
11409
|
+
},
|
|
11410
|
+
requiredTools(required) {
|
|
11411
|
+
const expected = [...required].sort((a, b) => a.localeCompare(b));
|
|
11412
|
+
return createRule("eval.requiredTools", "tool", (context) => {
|
|
11413
|
+
const tools = new Set(nodeNames(context.nodes, "TOOL"));
|
|
11414
|
+
return expected.filter((name) => !tools.has(name)).map(
|
|
11415
|
+
(name) => fail(
|
|
11416
|
+
"eval.requiredTools",
|
|
11417
|
+
`Required tool ${name} did not appear.`,
|
|
11418
|
+
evidenceForRun(context.run, "children"),
|
|
11419
|
+
name,
|
|
11420
|
+
[...tools].sort((a, b) => a.localeCompare(b))
|
|
11421
|
+
)
|
|
11422
|
+
);
|
|
11423
|
+
});
|
|
11424
|
+
},
|
|
11425
|
+
forbiddenTools(forbidden) {
|
|
11426
|
+
const blocked = [...forbidden].sort((a, b) => a.localeCompare(b));
|
|
11427
|
+
return createRule(
|
|
11428
|
+
"eval.forbiddenTools",
|
|
11429
|
+
"tool",
|
|
11430
|
+
(context) => context.nodes.filter((node) => node.event.kind === "TOOL" && blocked.includes(node.event.name)).map(
|
|
11431
|
+
(node) => fail(
|
|
11432
|
+
"eval.forbiddenTools",
|
|
11433
|
+
`Forbidden tool ${node.event.name} appeared.`,
|
|
11434
|
+
evidenceForEvent(node.event, "name"),
|
|
11435
|
+
"tool absent",
|
|
11436
|
+
node.event.name
|
|
11437
|
+
)
|
|
11438
|
+
)
|
|
11439
|
+
);
|
|
11440
|
+
},
|
|
11441
|
+
maxDurationMs(maxDurationMs) {
|
|
11442
|
+
return createRule(
|
|
11443
|
+
"eval.maxDurationMs",
|
|
11444
|
+
"run",
|
|
11445
|
+
(context) => context.run.durationMs !== void 0 && context.run.durationMs > maxDurationMs ? [
|
|
11446
|
+
fail(
|
|
11447
|
+
"eval.maxDurationMs",
|
|
11448
|
+
`Run duration exceeded ${maxDurationMs}ms.`,
|
|
11449
|
+
evidenceForRun(context.run, "durationMs"),
|
|
11450
|
+
{ maxDurationMs },
|
|
11451
|
+
context.run.durationMs
|
|
11452
|
+
)
|
|
11453
|
+
] : []
|
|
11454
|
+
);
|
|
11455
|
+
},
|
|
11456
|
+
maxDepth(maxDepth) {
|
|
11457
|
+
return createRule("eval.maxDepth", "structure", (context) => {
|
|
11458
|
+
const deepest = context.nodes.reduce(
|
|
11459
|
+
(current, node) => current === void 0 || node.depth > current.depth ? node : current,
|
|
11460
|
+
void 0
|
|
11461
|
+
);
|
|
11462
|
+
return deepest !== void 0 && deepest.depth > maxDepth ? [
|
|
11463
|
+
fail(
|
|
11464
|
+
"eval.maxDepth",
|
|
11465
|
+
`Run tree depth exceeded ${maxDepth}.`,
|
|
11466
|
+
evidenceForEvent(deepest.event, "depth"),
|
|
11467
|
+
{ maxDepth },
|
|
11468
|
+
deepest.depth
|
|
11469
|
+
)
|
|
11470
|
+
] : [];
|
|
11471
|
+
});
|
|
11472
|
+
},
|
|
11473
|
+
maxRetries(maxRetries) {
|
|
11474
|
+
return createRule(
|
|
11475
|
+
"eval.maxRetries",
|
|
11476
|
+
"structure",
|
|
11477
|
+
(context) => context.nodes.flatMap((node) => {
|
|
11478
|
+
const retries = numericAttribute(node, ["retryCount", "retries", "attempt"]);
|
|
11479
|
+
return retries !== void 0 && retries > maxRetries ? [
|
|
11480
|
+
fail(
|
|
11481
|
+
"eval.maxRetries",
|
|
11482
|
+
`Retry count exceeded ${maxRetries}.`,
|
|
11483
|
+
evidenceForEvent(node.event, "attributes.retryCount"),
|
|
11484
|
+
{ maxRetries },
|
|
11485
|
+
retries
|
|
11486
|
+
)
|
|
11487
|
+
] : [];
|
|
11488
|
+
})
|
|
11489
|
+
);
|
|
11490
|
+
},
|
|
11491
|
+
maxTotalTokens(maxTotalTokens) {
|
|
11492
|
+
return createRule("eval.maxTotalTokens", "llm", (context) => {
|
|
11493
|
+
const total = totalTokenCount(context.events);
|
|
11494
|
+
return total > maxTotalTokens ? [
|
|
11495
|
+
fail(
|
|
11496
|
+
"eval.maxTotalTokens",
|
|
11497
|
+
`Total token usage exceeded ${maxTotalTokens}.`,
|
|
11498
|
+
evidenceForRun(context.run, "tokenUsage.total"),
|
|
11499
|
+
{ maxTotalTokens },
|
|
11500
|
+
total
|
|
11501
|
+
)
|
|
11502
|
+
] : [];
|
|
11503
|
+
});
|
|
11504
|
+
},
|
|
11505
|
+
noFailedSteps() {
|
|
11506
|
+
return createRule(
|
|
11507
|
+
"eval.noFailedSteps",
|
|
11508
|
+
"run",
|
|
11509
|
+
(context) => context.nodes.filter((node) => node.event.status === "error" || node.event.kind === "ERROR").map(
|
|
11510
|
+
(node) => fail(
|
|
11511
|
+
"eval.noFailedSteps",
|
|
11512
|
+
"Run contains a failed step or error node.",
|
|
11513
|
+
evidenceForEvent(node.event, "status"),
|
|
11514
|
+
"no failed nodes",
|
|
11515
|
+
node.event.status ?? node.event.kind
|
|
11516
|
+
)
|
|
11517
|
+
)
|
|
11518
|
+
);
|
|
11519
|
+
},
|
|
11520
|
+
requiredRetrievalBeforeGeneration() {
|
|
11521
|
+
return createRule("eval.requiredRetrievalBeforeGeneration", "retrieval", (context) => {
|
|
11522
|
+
const firstLlmIndex = context.nodes.findIndex((node) => node.event.kind === "LLM");
|
|
11523
|
+
if (firstLlmIndex === -1) return [];
|
|
11524
|
+
const retrievalIndex = context.nodes.findIndex(
|
|
11525
|
+
(node, index) => index < firstLlmIndex && node.event.kind === "RETRIEVER"
|
|
11526
|
+
);
|
|
11527
|
+
return retrievalIndex === -1 ? [
|
|
11528
|
+
fail(
|
|
11529
|
+
"eval.requiredRetrievalBeforeGeneration",
|
|
11530
|
+
"No retrieval step appeared before the first LLM generation.",
|
|
11531
|
+
evidenceForEvent(context.nodes[firstLlmIndex].event, "kind"),
|
|
11532
|
+
"RETRIEVER before LLM",
|
|
11533
|
+
"LLM before RETRIEVER"
|
|
11534
|
+
)
|
|
11535
|
+
] : [];
|
|
11536
|
+
});
|
|
11537
|
+
},
|
|
11538
|
+
requiredDecisionMetadata(keys) {
|
|
11539
|
+
const required = [...keys].sort((a, b) => a.localeCompare(b));
|
|
11540
|
+
return createRule("eval.requiredDecisionMetadata", "structure", (context) => {
|
|
11541
|
+
const decisions = context.nodes.filter((node) => node.event.kind === "DECISION");
|
|
11542
|
+
if (decisions.length === 0) {
|
|
11543
|
+
return [
|
|
11544
|
+
fail(
|
|
11545
|
+
"eval.requiredDecisionMetadata",
|
|
11546
|
+
"No decision node is available for required metadata.",
|
|
11547
|
+
evidenceForRun(context.run, "children"),
|
|
11548
|
+
{ decisionMetadata: required },
|
|
11549
|
+
"no decision nodes"
|
|
11550
|
+
)
|
|
11551
|
+
];
|
|
11552
|
+
}
|
|
11553
|
+
return decisions.flatMap(
|
|
11554
|
+
(node) => required.filter((key) => !hasAttribute(node, key)).map(
|
|
11555
|
+
(key) => fail(
|
|
11556
|
+
"eval.requiredDecisionMetadata",
|
|
11557
|
+
`Decision metadata ${key} is missing.`,
|
|
11558
|
+
evidenceForEvent(node.event, `attributes.${key}`),
|
|
11559
|
+
key,
|
|
11560
|
+
"missing"
|
|
11561
|
+
)
|
|
11562
|
+
)
|
|
11563
|
+
);
|
|
11564
|
+
});
|
|
11565
|
+
},
|
|
11566
|
+
contextOverlap(options = {}) {
|
|
11567
|
+
const minOverlap = options.minOverlap ?? 0.1;
|
|
11568
|
+
const minSharedTerms = options.minSharedTerms ?? 1;
|
|
11569
|
+
const answerKeys = options.answerKeys ?? DEFAULT_ANSWER_KEYS;
|
|
11570
|
+
const contextKeys = options.contextKeys ?? DEFAULT_CONTEXT_KEYS;
|
|
11571
|
+
return createRule("eval.contextOverlap", "retrieval", (context) => {
|
|
11572
|
+
const answers = collectTextFields(context.nodes, answerKeys, ["RESULT", "LLM", "AGENT"]);
|
|
11573
|
+
const contexts = collectTextFields(context.nodes, contextKeys, ["RETRIEVER", "TOOL"]);
|
|
11574
|
+
if (answers.length === 0 || contexts.length === 0) {
|
|
11575
|
+
return [
|
|
11576
|
+
fail(
|
|
11577
|
+
"eval.contextOverlap",
|
|
11578
|
+
"Answer and context text are required for overlap evaluation.",
|
|
11579
|
+
firstEvidence(answers.length > 0 ? answers : contexts, context.run, "children"),
|
|
11580
|
+
{ answer: "present", context: "present" },
|
|
11581
|
+
{ answerFields: answers.length, contextFields: contexts.length }
|
|
11582
|
+
)
|
|
11583
|
+
];
|
|
11584
|
+
}
|
|
11585
|
+
const answerTerms = new Set(tokenize(answers.map((field) => field.text).join(" ")));
|
|
11586
|
+
const contextTerms = new Set(tokenize(contexts.map((field) => field.text).join(" ")));
|
|
11587
|
+
const sharedTerms = [...answerTerms].filter((term) => contextTerms.has(term)).length;
|
|
11588
|
+
const overlap = answerTerms.size === 0 ? 0 : sharedTerms / answerTerms.size;
|
|
11589
|
+
return sharedTerms < minSharedTerms || overlap < minOverlap ? [
|
|
11590
|
+
fail(
|
|
11591
|
+
"eval.contextOverlap",
|
|
11592
|
+
"Answer text did not sufficiently overlap retrieved context.",
|
|
11593
|
+
firstEvidence(answers, context.run, "attributes.answer"),
|
|
11594
|
+
{ minOverlap, minSharedTerms },
|
|
11595
|
+
{
|
|
11596
|
+
answerTerms: answerTerms.size,
|
|
11597
|
+
contextTerms: contextTerms.size,
|
|
11598
|
+
sharedTerms,
|
|
11599
|
+
overlap: Number(overlap.toFixed(4))
|
|
11600
|
+
}
|
|
11601
|
+
)
|
|
11602
|
+
] : [];
|
|
11603
|
+
});
|
|
11604
|
+
},
|
|
11605
|
+
quoteOverlap(options = {}) {
|
|
11606
|
+
const answerKeys = options.answerKeys ?? DEFAULT_ANSWER_KEYS;
|
|
11607
|
+
const contextKeys = options.contextKeys ?? DEFAULT_CONTEXT_KEYS;
|
|
11608
|
+
const minQuoteLength = options.minQuoteLength ?? 6;
|
|
11609
|
+
const requireQuote = options.requireQuote ?? true;
|
|
11610
|
+
return createRule("eval.quoteOverlap", "retrieval", (context) => {
|
|
11611
|
+
const answers = collectTextFields(context.nodes, answerKeys, ["RESULT", "LLM", "AGENT"]);
|
|
11612
|
+
const contexts = collectTextFields(context.nodes, contextKeys, ["RETRIEVER", "TOOL"]);
|
|
11613
|
+
const answerText = answers.map((field) => field.text).join(" ");
|
|
11614
|
+
const contextText = contexts.map((field) => field.text).join(" ").toLowerCase();
|
|
11615
|
+
const quotes = quotedSnippets(answerText, minQuoteLength);
|
|
11616
|
+
if (quotes.length === 0) {
|
|
11617
|
+
return requireQuote ? [
|
|
11618
|
+
fail(
|
|
11619
|
+
"eval.quoteOverlap",
|
|
11620
|
+
"Answer did not contain a quote for overlap evaluation.",
|
|
11621
|
+
firstEvidence(answers, context.run, "attributes.answer"),
|
|
11622
|
+
{ quotedText: "present" },
|
|
11623
|
+
{ quoteCount: 0 }
|
|
11624
|
+
)
|
|
11625
|
+
] : [];
|
|
11626
|
+
}
|
|
11627
|
+
const missing = quotes.filter((quote) => !contextText.includes(quote.toLowerCase()));
|
|
11628
|
+
return missing.length > 0 ? [
|
|
11629
|
+
fail(
|
|
11630
|
+
"eval.quoteOverlap",
|
|
11631
|
+
"Quoted answer text did not appear in retrieved context.",
|
|
11632
|
+
firstEvidence(answers, context.run, "attributes.answer"),
|
|
11633
|
+
{ allQuotesInContext: true },
|
|
11634
|
+
{ quoteCount: quotes.length, missingQuotes: missing.length }
|
|
11635
|
+
)
|
|
11636
|
+
] : [];
|
|
11637
|
+
});
|
|
11638
|
+
},
|
|
11639
|
+
citationPresence(options = {}) {
|
|
11640
|
+
const answerKeys = options.answerKeys ?? DEFAULT_ANSWER_KEYS;
|
|
11641
|
+
const citationKeys = options.citationKeys ?? DEFAULT_CITATION_KEYS;
|
|
11642
|
+
return createRule("eval.citationPresence", "retrieval", (context) => {
|
|
11643
|
+
const answers = collectTextFields(context.nodes, answerKeys, ["RESULT", "LLM", "AGENT"]);
|
|
11644
|
+
const citations = collectTextFields(context.nodes, citationKeys);
|
|
11645
|
+
const count = citationCount(answers.map((field) => field.text).join(" "), citations);
|
|
11646
|
+
return count === 0 ? [
|
|
11647
|
+
fail(
|
|
11648
|
+
"eval.citationPresence",
|
|
11649
|
+
"Answer did not include citations or source references.",
|
|
11650
|
+
firstEvidence(answers, context.run, "attributes.answer"),
|
|
11651
|
+
{ citationCount: ">= 1" },
|
|
11652
|
+
{ citationCount: 0 }
|
|
11653
|
+
)
|
|
11654
|
+
] : [];
|
|
11655
|
+
});
|
|
11656
|
+
},
|
|
11657
|
+
requiredSourceIds(requiredIds, options = {}) {
|
|
11658
|
+
const expected = [...requiredIds].sort((a, b) => a.localeCompare(b));
|
|
11659
|
+
const sourceIdKeys = options.sourceIdKeys ?? DEFAULT_SOURCE_ID_KEYS;
|
|
11660
|
+
return createRule("eval.requiredSourceIds", "retrieval", (context) => {
|
|
11661
|
+
const available = collectSourceIds(context.nodes, sourceIdKeys);
|
|
11662
|
+
const availableSet = new Set(available);
|
|
11663
|
+
const missing = expected.filter((id) => !availableSet.has(id));
|
|
11664
|
+
return missing.length > 0 ? [
|
|
11665
|
+
fail(
|
|
11666
|
+
"eval.requiredSourceIds",
|
|
11667
|
+
"Required source IDs were not present in trace context or citations.",
|
|
11668
|
+
evidenceForRun(context.run, "children"),
|
|
11669
|
+
{ sourceIds: expected },
|
|
11670
|
+
{ missingSourceIds: missing, availableSourceIds: available.slice(0, 20) }
|
|
11671
|
+
)
|
|
11672
|
+
] : [];
|
|
11673
|
+
});
|
|
11674
|
+
},
|
|
11675
|
+
answerLengthBounds(options) {
|
|
11676
|
+
const answerKeys = options.answerKeys ?? DEFAULT_ANSWER_KEYS;
|
|
11677
|
+
return createRule("eval.answerLengthBounds", "llm", (context) => {
|
|
11678
|
+
const answers = collectTextFields(context.nodes, answerKeys, ["RESULT", "LLM", "AGENT"]);
|
|
11679
|
+
const answer = answers.map((field) => field.text).join(" ").trim();
|
|
11680
|
+
const characters = answer.length;
|
|
11681
|
+
const words = wordCount(answer);
|
|
11682
|
+
const tooShort = options.minCharacters !== void 0 && characters < options.minCharacters || options.minWords !== void 0 && words < options.minWords;
|
|
11683
|
+
const tooLong = options.maxCharacters !== void 0 && characters > options.maxCharacters || options.maxWords !== void 0 && words > options.maxWords;
|
|
11684
|
+
return answer.length === 0 || tooShort || tooLong ? [
|
|
11685
|
+
fail(
|
|
11686
|
+
"eval.answerLengthBounds",
|
|
11687
|
+
"Answer length fell outside required bounds.",
|
|
11688
|
+
firstEvidence(answers, context.run, "attributes.answer"),
|
|
11689
|
+
{
|
|
11690
|
+
minCharacters: options.minCharacters,
|
|
11691
|
+
maxCharacters: options.maxCharacters,
|
|
11692
|
+
minWords: options.minWords,
|
|
11693
|
+
maxWords: options.maxWords
|
|
11694
|
+
},
|
|
11695
|
+
{ characters, words }
|
|
11696
|
+
)
|
|
11697
|
+
] : [];
|
|
11698
|
+
});
|
|
11699
|
+
},
|
|
11700
|
+
bannedUnsupportedPhrases(phrases = DEFAULT_BANNED_UNSUPPORTED_PHRASES, options = {}) {
|
|
11701
|
+
const answerKeys = options.answerKeys ?? DEFAULT_ANSWER_KEYS;
|
|
11702
|
+
const banned = [...phrases].map((phrase) => phrase.toLowerCase()).sort();
|
|
11703
|
+
return createRule("eval.bannedUnsupportedPhrases", "safety", (context) => {
|
|
11704
|
+
const answers = collectTextFields(context.nodes, answerKeys, ["RESULT", "LLM", "AGENT"]);
|
|
11705
|
+
const answer = answers.map((field) => field.text).join(" ").toLowerCase();
|
|
11706
|
+
const matches = banned.filter((phrase) => answer.includes(phrase));
|
|
11707
|
+
return matches.length > 0 ? [
|
|
11708
|
+
fail(
|
|
11709
|
+
"eval.bannedUnsupportedPhrases",
|
|
11710
|
+
"Answer contained banned unsupported-answer phrasing.",
|
|
11711
|
+
firstEvidence(answers, context.run, "attributes.answer"),
|
|
11712
|
+
{ bannedPhraseCount: banned.length },
|
|
11713
|
+
{ matchedPhraseCount: matches.length }
|
|
11714
|
+
)
|
|
11715
|
+
] : [];
|
|
11716
|
+
});
|
|
11717
|
+
}
|
|
11718
|
+
};
|
|
11719
|
+
function renderEvalMarkdown(result) {
|
|
11720
|
+
const lines = [
|
|
11721
|
+
`# AgentInspect Eval`,
|
|
11722
|
+
"",
|
|
11723
|
+
`Status: ${result.status}`,
|
|
11724
|
+
`Format: ${result.format}`,
|
|
11725
|
+
...result.runId !== void 0 ? [`Run: ${result.runId}`] : [],
|
|
11726
|
+
`Summary: ${result.summary.passed} passed, ${result.summary.failed} failed, ${result.summary.warnings} warnings, ${result.summary.errors} errors`
|
|
11727
|
+
];
|
|
11728
|
+
if (result.diagnostics.length > 0) {
|
|
11729
|
+
lines.push("", "## Diagnostics");
|
|
11730
|
+
for (const diagnostic4 of result.diagnostics) {
|
|
11731
|
+
lines.push(`- ${diagnostic4.code}: ${diagnostic4.message}`);
|
|
11732
|
+
}
|
|
11733
|
+
}
|
|
11734
|
+
if (result.findings.length > 0) {
|
|
11735
|
+
lines.push("", "## Findings");
|
|
11736
|
+
for (const finding of result.findings) {
|
|
11737
|
+
const path15 = finding.evidence[0]?.path;
|
|
11738
|
+
lines.push(`- ${finding.ruleId}: ${finding.message}${path15 ? ` (${path15})` : ""}`);
|
|
11739
|
+
}
|
|
11740
|
+
}
|
|
11741
|
+
return `${lines.join("\n")}
|
|
11742
|
+
`;
|
|
11743
|
+
}
|
|
11744
|
+
|
|
11745
|
+
// packages/cli/src/eval.ts
|
|
11746
|
+
var CONFIG_EXTENSIONS2 = /* @__PURE__ */ new Set([".json", ".js", ".mjs", ".cjs"]);
|
|
11747
|
+
var TS_CONFIG_EXTENSIONS2 = /* @__PURE__ */ new Set([".ts", ".mts", ".cts"]);
|
|
11748
|
+
function diagnostic3(code, message, severity = "error") {
|
|
11749
|
+
return { code, message, severity };
|
|
11750
|
+
}
|
|
11751
|
+
function errorResult4(code, message, format = "unknown") {
|
|
11752
|
+
return {
|
|
11753
|
+
ok: false,
|
|
11754
|
+
status: "error",
|
|
11755
|
+
format,
|
|
11756
|
+
summary: { passed: 0, failed: 0, warnings: 0, errors: 1 },
|
|
11757
|
+
findings: [],
|
|
11758
|
+
diagnostics: [diagnostic3(code, message)]
|
|
11759
|
+
};
|
|
11760
|
+
}
|
|
11761
|
+
function parseNumber2(value, label) {
|
|
11762
|
+
if (value === void 0) return void 0;
|
|
11763
|
+
const parsed = Number(value);
|
|
11764
|
+
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
11765
|
+
throw new Error(`${label} must be a non-negative number.`);
|
|
11766
|
+
}
|
|
11767
|
+
return parsed;
|
|
11768
|
+
}
|
|
11769
|
+
function asStringArray2(value, label) {
|
|
11770
|
+
if (value === void 0) return void 0;
|
|
11771
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
|
|
11772
|
+
throw new Error(`${label} must be an array of strings.`);
|
|
11773
|
+
}
|
|
11774
|
+
return value;
|
|
11775
|
+
}
|
|
11776
|
+
function asConfig2(value) {
|
|
11777
|
+
if (value === void 0 || value === null) return {};
|
|
11778
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
11779
|
+
throw new Error("Config must export an object.");
|
|
11780
|
+
}
|
|
11781
|
+
return value;
|
|
11782
|
+
}
|
|
11783
|
+
async function loadConfig2(configPath) {
|
|
11784
|
+
if (configPath === void 0) return {};
|
|
11785
|
+
const extension = path13__default.default.extname(configPath);
|
|
11786
|
+
if (TS_CONFIG_EXTENSIONS2.has(extension)) {
|
|
11787
|
+
throw new Error(
|
|
11788
|
+
"TypeScript eval configs require an explicit precompiled JavaScript config or future --config-loader support."
|
|
11789
|
+
);
|
|
11790
|
+
}
|
|
11791
|
+
if (!CONFIG_EXTENSIONS2.has(extension)) {
|
|
11792
|
+
throw new Error("Unsupported eval config extension. Use .json, .js, .mjs, or .cjs.");
|
|
11793
|
+
}
|
|
11794
|
+
const absolute = path13__default.default.resolve(configPath);
|
|
11795
|
+
if (extension === ".json") {
|
|
11796
|
+
const raw = await promises.readFile(absolute, "utf-8");
|
|
11797
|
+
return asConfig2(JSON.parse(raw));
|
|
11798
|
+
}
|
|
11799
|
+
const mod = await import(url.pathToFileURL(absolute).href);
|
|
11800
|
+
return asConfig2("default" in mod ? mod.default : mod);
|
|
11801
|
+
}
|
|
11802
|
+
function normalizeConfig2(config) {
|
|
11803
|
+
if (config.eval === void 0) return {};
|
|
11804
|
+
if (typeof config.eval !== "object" || Array.isArray(config.eval)) {
|
|
11805
|
+
throw new Error("eval config must be an object.");
|
|
11806
|
+
}
|
|
11807
|
+
return config.eval;
|
|
11808
|
+
}
|
|
11809
|
+
function maybeAdd(rules, value, makeRule) {
|
|
11810
|
+
if (value !== void 0) rules.push(makeRule(value));
|
|
11811
|
+
}
|
|
11812
|
+
function optionObject(value) {
|
|
11813
|
+
if (value === void 0 || value === false) return void 0;
|
|
11814
|
+
if (value === true) return {};
|
|
11815
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
11816
|
+
throw new Error("Eval heuristic config must be a boolean or object.");
|
|
11817
|
+
}
|
|
11818
|
+
return value;
|
|
11819
|
+
}
|
|
11820
|
+
function sourceIdsFromConfig(value) {
|
|
11821
|
+
if (value === void 0) return void 0;
|
|
11822
|
+
if (Array.isArray(value)) return { ids: asStringArray2(value, "eval.requiredSourceIds") ?? [] };
|
|
11823
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
11824
|
+
throw new Error("eval.requiredSourceIds must be an array or object.");
|
|
11825
|
+
}
|
|
11826
|
+
const ids = asStringArray2(value.ids, "eval.requiredSourceIds.ids") ?? [];
|
|
11827
|
+
return { ids, options: { sourceIdKeys: value.sourceIdKeys } };
|
|
11828
|
+
}
|
|
11829
|
+
function buildRules2(config, options) {
|
|
11830
|
+
const evalConfig = normalizeConfig2(config);
|
|
11831
|
+
const rules = [];
|
|
11832
|
+
if (evalConfig.requireSuccess || options.requireSuccess) rules.push(checks.requireSuccess());
|
|
11833
|
+
const requiredTools = [
|
|
11834
|
+
...asStringArray2(evalConfig.requiredTools, "eval.requiredTools") ?? [],
|
|
11835
|
+
...options.requiredTool ?? []
|
|
11836
|
+
];
|
|
11837
|
+
if (requiredTools.length > 0) rules.push(checks.requiredTools(requiredTools));
|
|
11838
|
+
const forbiddenTools = [
|
|
11839
|
+
...asStringArray2(evalConfig.forbiddenTools, "eval.forbiddenTools") ?? [],
|
|
11840
|
+
...options.forbidTool ?? [],
|
|
11841
|
+
...options.forbiddenTool ?? []
|
|
11842
|
+
];
|
|
11843
|
+
if (forbiddenTools.length > 0) rules.push(checks.forbiddenTools(forbiddenTools));
|
|
11844
|
+
const maxDurationMs = parseNumber2(options.maxDurationMs, "--max-duration-ms") ?? evalConfig.maxDurationMs;
|
|
11845
|
+
maybeAdd(rules, maxDurationMs, checks.maxDurationMs);
|
|
11846
|
+
const maxDepth = parseNumber2(options.maxDepth, "--max-depth") ?? evalConfig.maxDepth;
|
|
11847
|
+
maybeAdd(rules, maxDepth, checks.maxDepth);
|
|
11848
|
+
const maxRetries = parseNumber2(options.maxRetries, "--max-retries") ?? evalConfig.maxRetries;
|
|
11849
|
+
maybeAdd(rules, maxRetries, checks.maxRetries);
|
|
11850
|
+
const maxTotalTokens = parseNumber2(options.maxTotalTokens, "--max-total-tokens") ?? evalConfig.maxTotalTokens;
|
|
11851
|
+
maybeAdd(rules, maxTotalTokens, checks.maxTotalTokens);
|
|
11852
|
+
if (evalConfig.requiredRetrievalBeforeGeneration || options.requireRetrievalBeforeGeneration) {
|
|
11853
|
+
rules.push(checks.requiredRetrievalBeforeGeneration());
|
|
11854
|
+
}
|
|
11855
|
+
const requiredDecisionMetadata = [
|
|
11856
|
+
...asStringArray2(
|
|
11857
|
+
evalConfig.requiredDecisionMetadata,
|
|
11858
|
+
"eval.requiredDecisionMetadata"
|
|
11859
|
+
) ?? [],
|
|
11860
|
+
...options.requiredDecisionMetadata ?? []
|
|
11861
|
+
];
|
|
11862
|
+
if (requiredDecisionMetadata.length > 0) {
|
|
11863
|
+
rules.push(checks.requiredDecisionMetadata(requiredDecisionMetadata));
|
|
11864
|
+
}
|
|
11865
|
+
const contextOverlap = optionObject(evalConfig.contextOverlap);
|
|
11866
|
+
const minOverlap = parseNumber2(options.minContextOverlap, "--min-context-overlap");
|
|
11867
|
+
const minSharedTerms = parseNumber2(options.minSharedTerms, "--min-shared-terms");
|
|
11868
|
+
if (contextOverlap !== void 0 || options.contextOverlap || minOverlap !== void 0 || minSharedTerms !== void 0) {
|
|
11869
|
+
rules.push(checks.contextOverlap({ ...contextOverlap, minOverlap, minSharedTerms }));
|
|
11870
|
+
}
|
|
11871
|
+
const quoteOverlap = optionObject(evalConfig.quoteOverlap);
|
|
11872
|
+
if (quoteOverlap !== void 0 || options.quoteOverlap) {
|
|
11873
|
+
rules.push(checks.quoteOverlap(quoteOverlap));
|
|
11874
|
+
}
|
|
11875
|
+
const citationPresence = optionObject(evalConfig.citationPresence);
|
|
11876
|
+
if (citationPresence !== void 0 || options.citationPresence) {
|
|
11877
|
+
rules.push(checks.citationPresence(citationPresence));
|
|
11878
|
+
}
|
|
11879
|
+
const sourceIds = sourceIdsFromConfig(evalConfig.requiredSourceIds);
|
|
11880
|
+
const requiredSourceIds = [...sourceIds?.ids ?? [], ...options.requiredSourceId ?? []];
|
|
11881
|
+
if (requiredSourceIds.length > 0) {
|
|
11882
|
+
rules.push(checks.requiredSourceIds(requiredSourceIds, sourceIds?.options));
|
|
11883
|
+
}
|
|
11884
|
+
const cliAnswerLength = {
|
|
11885
|
+
minCharacters: parseNumber2(options.minAnswerCharacters, "--min-answer-characters"),
|
|
11886
|
+
maxCharacters: parseNumber2(options.maxAnswerCharacters, "--max-answer-characters"),
|
|
11887
|
+
minWords: parseNumber2(options.minAnswerWords, "--min-answer-words"),
|
|
11888
|
+
maxWords: parseNumber2(options.maxAnswerWords, "--max-answer-words")
|
|
11889
|
+
};
|
|
11890
|
+
const hasCliAnswerLength = Object.values(cliAnswerLength).some((value) => value !== void 0);
|
|
11891
|
+
if (evalConfig.answerLengthBounds !== void 0 || hasCliAnswerLength) {
|
|
11892
|
+
rules.push(checks.answerLengthBounds({ ...evalConfig.answerLengthBounds, ...cliAnswerLength }));
|
|
11893
|
+
}
|
|
11894
|
+
const bannedPhrases = [
|
|
11895
|
+
...asStringArray2(
|
|
11896
|
+
evalConfig.bannedUnsupportedPhrases,
|
|
11897
|
+
"eval.bannedUnsupportedPhrases"
|
|
11898
|
+
) ?? [],
|
|
11899
|
+
...options.bannedPhrase ?? []
|
|
11900
|
+
];
|
|
11901
|
+
if (bannedPhrases.length > 0) rules.push(checks.bannedUnsupportedPhrases(bannedPhrases));
|
|
11902
|
+
return rules.length > 0 ? rules : void 0;
|
|
11903
|
+
}
|
|
11904
|
+
function exitCodeFor2(result) {
|
|
11905
|
+
if (result.status === "pass") return 0;
|
|
11906
|
+
if (result.status === "fail") return 1;
|
|
11907
|
+
return 2;
|
|
11908
|
+
}
|
|
11909
|
+
function stable3(value) {
|
|
11910
|
+
if (Array.isArray(value)) return value.map(stable3);
|
|
11911
|
+
if (value === null || typeof value !== "object") return value;
|
|
11912
|
+
const record = value;
|
|
11913
|
+
return Object.fromEntries(
|
|
11914
|
+
Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable3(record[key])])
|
|
11915
|
+
);
|
|
11916
|
+
}
|
|
11917
|
+
function printJson2(result) {
|
|
11918
|
+
console.log(JSON.stringify(stable3(result), null, 2));
|
|
11919
|
+
}
|
|
11920
|
+
function printHuman2(result) {
|
|
11921
|
+
console.log(`Eval status: ${result.status}`);
|
|
11922
|
+
console.log(`Format: ${result.format}`);
|
|
11923
|
+
if (result.runId !== void 0) console.log(`Run: ${result.runId}`);
|
|
11924
|
+
console.log(
|
|
11925
|
+
`Summary: ${result.summary.failed} failed, ${result.summary.warnings} warning(s), ${result.summary.errors} error(s)`
|
|
11926
|
+
);
|
|
11927
|
+
for (const diagnostic4 of result.diagnostics) {
|
|
11928
|
+
console.log(`- ${diagnostic4.code}: ${diagnostic4.message}`);
|
|
11929
|
+
}
|
|
11930
|
+
for (const finding of result.findings) {
|
|
11931
|
+
const path15 = finding.evidence[0]?.path;
|
|
11932
|
+
console.log(`- ${finding.ruleId}: ${finding.message}${path15 ? ` (${path15})` : ""}`);
|
|
11933
|
+
}
|
|
11934
|
+
}
|
|
11935
|
+
function readErrorResult2(error) {
|
|
11936
|
+
if (error instanceof TraceReadError) {
|
|
11937
|
+
const code = error.code === "unsupported_format" ? "AI_EVAL_UNSUPPORTED_FORMAT" : error.code === "ambiguous_format" ? "AI_EVAL_AMBIGUOUS_FORMAT" : "AI_EVAL_TRACE_UNREADABLE";
|
|
11938
|
+
return errorResult4(code, error.message);
|
|
11939
|
+
}
|
|
11940
|
+
return errorResult4(
|
|
11941
|
+
"AI_EVAL_TRACE_UNREADABLE",
|
|
11942
|
+
error instanceof Error ? error.message : String(error)
|
|
11943
|
+
);
|
|
11944
|
+
}
|
|
11945
|
+
async function evalCommand(target, options = {}, stdin = process.stdin) {
|
|
11946
|
+
let result;
|
|
11947
|
+
let phase = "config";
|
|
11948
|
+
try {
|
|
11949
|
+
const config = await loadConfig2(options.config);
|
|
11950
|
+
const rules = buildRules2(config, options);
|
|
11951
|
+
phase = "read";
|
|
11952
|
+
const input3 = await inputFromTarget(target, options, stdin);
|
|
11953
|
+
const read = await openTrace(input3, {
|
|
11954
|
+
...options.format !== void 0 ? { format: options.format } : {}
|
|
11955
|
+
});
|
|
11956
|
+
result = await evalRun(read, {
|
|
11957
|
+
...rules !== void 0 ? { checks: rules } : {},
|
|
11958
|
+
...options.run !== void 0 ? { runId: options.run } : {}
|
|
11959
|
+
});
|
|
11960
|
+
} catch (error) {
|
|
11961
|
+
if (phase === "config") {
|
|
11962
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
11963
|
+
const code = message.startsWith("--") ? "AI_EVAL_INVALID_ARGUMENTS" : error instanceof SyntaxError || message.includes("Unsupported eval config extension") || message.includes("TypeScript eval configs") || message.includes("Config must") || message.includes("eval config") || message.includes("must be an array") ? "AI_EVAL_INVALID_CONFIG" : "AI_EVAL_CONFIG_LOAD_FAILED";
|
|
11964
|
+
result = errorResult4(code, message);
|
|
11965
|
+
} else {
|
|
11966
|
+
result = readErrorResult2(error);
|
|
11967
|
+
}
|
|
11968
|
+
}
|
|
11969
|
+
process.exitCode = exitCodeFor2(result);
|
|
11970
|
+
if (options.json) printJson2(result);
|
|
11971
|
+
else if (options.markdown) console.log(renderEvalMarkdown(result).trimEnd());
|
|
11972
|
+
else printHuman2(result);
|
|
11973
|
+
}
|
|
11974
|
+
|
|
11975
|
+
// packages/cli/src/safety.ts
|
|
11976
|
+
var BEST_EFFORT_NOTE = "Best-effort local safety verification only; not a compliance, privacy, security, or regulatory certification.";
|
|
11977
|
+
var DEFAULT_MAX_STRING_LENGTH = 16384;
|
|
11978
|
+
var DEFAULT_MAX_ARRAY_LENGTH = 1e3;
|
|
11979
|
+
var DEFAULT_MAX_OBJECT_KEYS = 200;
|
|
11980
|
+
var DEFAULT_MAX_SERIALIZED_BYTES = 128 * 1024;
|
|
11981
|
+
function parseLimit3(value, label) {
|
|
11982
|
+
if (value === void 0) return void 0;
|
|
11983
|
+
const parsed = Number(value);
|
|
11984
|
+
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
11985
|
+
throw new Error(`${label} must be a non-negative number.`);
|
|
11986
|
+
}
|
|
11987
|
+
return parsed;
|
|
11988
|
+
}
|
|
11989
|
+
function stable4(value) {
|
|
11990
|
+
if (Array.isArray(value)) return value.map(stable4);
|
|
11991
|
+
if (value === null || typeof value !== "object") return value;
|
|
11992
|
+
const record = value;
|
|
11993
|
+
return Object.fromEntries(
|
|
11994
|
+
Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable4(record[key])])
|
|
11995
|
+
);
|
|
11996
|
+
}
|
|
11997
|
+
function safetyDiagnostic(code, message, severity = "error") {
|
|
11998
|
+
return { code, message, severity };
|
|
11999
|
+
}
|
|
12000
|
+
function warningDiagnostics(warnings, unsupportedFields) {
|
|
12001
|
+
return [
|
|
12002
|
+
...warnings.map(
|
|
12003
|
+
(warning) => safetyDiagnostic(
|
|
12004
|
+
warning.code,
|
|
12005
|
+
warning.message,
|
|
12006
|
+
warning.severity === "error" ? "error" : "warning"
|
|
12007
|
+
)
|
|
12008
|
+
),
|
|
12009
|
+
...unsupportedFields.map(
|
|
12010
|
+
(field) => safetyDiagnostic(
|
|
10569
12011
|
"unsupported_field",
|
|
10570
12012
|
`Reader reported unsupported field: ${field}`,
|
|
10571
12013
|
"warning"
|
|
@@ -10607,7 +12049,7 @@ function resultFromParts(parts) {
|
|
|
10607
12049
|
note: BEST_EFFORT_NOTE
|
|
10608
12050
|
};
|
|
10609
12051
|
}
|
|
10610
|
-
function
|
|
12052
|
+
function readErrorResult3(command, error) {
|
|
10611
12053
|
if (error instanceof TraceReadError) {
|
|
10612
12054
|
const code = error.code === "unsupported_format" ? "AI_SAFETY_UNSUPPORTED_FORMAT" : error.code === "ambiguous_format" ? "AI_SAFETY_AMBIGUOUS_FORMAT" : "AI_SAFETY_TRACE_UNREADABLE";
|
|
10613
12055
|
return resultFromParts({
|
|
@@ -10657,27 +12099,76 @@ function buildSafetyRules(options) {
|
|
|
10657
12099
|
})
|
|
10658
12100
|
];
|
|
10659
12101
|
}
|
|
10660
|
-
function
|
|
12102
|
+
function flattenNodes2(nodes) {
|
|
12103
|
+
return nodes.flatMap((node) => [
|
|
12104
|
+
node,
|
|
12105
|
+
...flattenNodes2(
|
|
12106
|
+
node.children
|
|
12107
|
+
)
|
|
12108
|
+
]);
|
|
12109
|
+
}
|
|
12110
|
+
function detectorSeverity(finding) {
|
|
12111
|
+
return finding.severity;
|
|
12112
|
+
}
|
|
12113
|
+
function redactionDetectorFindings(read, runId) {
|
|
12114
|
+
const runs = runId === void 0 ? read.runs : read.runs.filter((run) => run.runId === runId);
|
|
12115
|
+
const out = [];
|
|
12116
|
+
for (const run of runs) {
|
|
12117
|
+
for (const node of flattenNodes2(run.children)) {
|
|
12118
|
+
const attrs = node.event.attributes;
|
|
12119
|
+
if (attrs === void 0) continue;
|
|
12120
|
+
const result = redact(attrs, { profile: "share" });
|
|
12121
|
+
for (const finding of result.findings) {
|
|
12122
|
+
if (finding.action === "keep") continue;
|
|
12123
|
+
out.push({
|
|
12124
|
+
ruleId: "safety.redactDetector",
|
|
12125
|
+
severity: detectorSeverity(finding),
|
|
12126
|
+
status: finding.severity === "error" ? "fail" : "warning",
|
|
12127
|
+
message: `Redaction detector ${finding.detector} matched ${finding.matchKind} at ${finding.path}.`,
|
|
12128
|
+
expected: "redacted trace content",
|
|
12129
|
+
actual: finding.detector,
|
|
12130
|
+
evidence: [
|
|
12131
|
+
{
|
|
12132
|
+
runId: node.event.runId,
|
|
12133
|
+
eventId: node.event.eventId,
|
|
12134
|
+
...node.event.parentId !== void 0 ? { parentId: node.event.parentId } : {},
|
|
12135
|
+
kind: node.event.kind,
|
|
12136
|
+
name: node.event.name,
|
|
12137
|
+
...node.event.status !== void 0 ? { status: node.event.status } : {},
|
|
12138
|
+
path: `attributes.${finding.path.replace(/^\$\.?/, "")}`
|
|
12139
|
+
}
|
|
12140
|
+
]
|
|
12141
|
+
});
|
|
12142
|
+
}
|
|
12143
|
+
}
|
|
12144
|
+
}
|
|
12145
|
+
return out.sort((a, b) => {
|
|
12146
|
+
const aEvidence = a.evidence[0];
|
|
12147
|
+
const bEvidence = b.evidence[0];
|
|
12148
|
+
return (aEvidence?.runId ?? "").localeCompare(bEvidence?.runId ?? "") || (aEvidence?.eventId ?? "").localeCompare(bEvidence?.eventId ?? "") || (aEvidence?.path ?? "").localeCompare(bEvidence?.path ?? "") || a.message.localeCompare(b.message);
|
|
12149
|
+
});
|
|
12150
|
+
}
|
|
12151
|
+
function exitCodeFor3(result) {
|
|
10661
12152
|
if (result.status === "SAFE" || result.status === "SAFE WITH WARNINGS") return 0;
|
|
10662
12153
|
if (result.status === "UNSAFE") return 1;
|
|
10663
12154
|
return 2;
|
|
10664
12155
|
}
|
|
10665
|
-
function
|
|
10666
|
-
console.log(JSON.stringify(
|
|
12156
|
+
function printJson3(result) {
|
|
12157
|
+
console.log(JSON.stringify(stable4(result), null, 2));
|
|
10667
12158
|
}
|
|
10668
|
-
function
|
|
12159
|
+
function printHuman3(result) {
|
|
10669
12160
|
console.log(`Safety status: ${result.status}`);
|
|
10670
12161
|
console.log(`Format: ${result.format}`);
|
|
10671
12162
|
if (result.runId !== void 0) console.log(`Run: ${result.runId}`);
|
|
10672
12163
|
console.log(
|
|
10673
12164
|
`Summary: ${result.summary.findings} finding(s), ${result.summary.warnings} warning(s), ${result.summary.errors} error(s)`
|
|
10674
12165
|
);
|
|
10675
|
-
for (const
|
|
10676
|
-
console.log(`- ${
|
|
12166
|
+
for (const diagnostic4 of result.diagnostics) {
|
|
12167
|
+
console.log(`- ${diagnostic4.code}: ${diagnostic4.message}`);
|
|
10677
12168
|
}
|
|
10678
12169
|
for (const finding of result.findings) {
|
|
10679
|
-
const
|
|
10680
|
-
console.log(`- ${finding.ruleId}: ${finding.message}${
|
|
12170
|
+
const path15 = finding.evidence[0]?.path;
|
|
12171
|
+
console.log(`- ${finding.ruleId}: ${finding.message}${path15 ? ` (${path15})` : ""}`);
|
|
10681
12172
|
}
|
|
10682
12173
|
console.log(`Note: ${result.note}`);
|
|
10683
12174
|
}
|
|
@@ -10696,11 +12187,12 @@ async function safetyCommand(command, target, options, stdin) {
|
|
|
10696
12187
|
...options.run !== void 0 ? { runId: options.run } : {}
|
|
10697
12188
|
}
|
|
10698
12189
|
);
|
|
12190
|
+
const detectorFindings = checkResult.diagnostics.length === 0 ? redactionDetectorFindings(read, checkResult.runId) : [];
|
|
10699
12191
|
result = resultFromParts({
|
|
10700
12192
|
command,
|
|
10701
12193
|
format: checkResult.format,
|
|
10702
12194
|
runId: checkResult.runId,
|
|
10703
|
-
findings: checkResult.findings,
|
|
12195
|
+
findings: [...checkResult.findings, ...detectorFindings],
|
|
10704
12196
|
diagnostics: [
|
|
10705
12197
|
...checkResult.diagnostics.map(diagnosticFromCheck),
|
|
10706
12198
|
...warningDiagnostics(read.warnings, read.unsupportedFields)
|
|
@@ -10710,11 +12202,11 @@ async function safetyCommand(command, target, options, stdin) {
|
|
|
10710
12202
|
});
|
|
10711
12203
|
} catch (error) {
|
|
10712
12204
|
const message = error instanceof Error ? error.message : String(error);
|
|
10713
|
-
result = message.startsWith("--") ? invalidArgumentResult(command, error) :
|
|
12205
|
+
result = message.startsWith("--") ? invalidArgumentResult(command, error) : readErrorResult3(command, error);
|
|
10714
12206
|
}
|
|
10715
|
-
process.exitCode =
|
|
10716
|
-
if (options.json)
|
|
10717
|
-
else
|
|
12207
|
+
process.exitCode = exitCodeFor3(result);
|
|
12208
|
+
if (options.json) printJson3(result);
|
|
12209
|
+
else printHuman3(result);
|
|
10718
12210
|
}
|
|
10719
12211
|
function scanCommand(target, options = {}, stdin = process.stdin) {
|
|
10720
12212
|
return safetyCommand("scan", target, options, stdin);
|
|
@@ -10734,16 +12226,16 @@ var SAFETY_RULES = [
|
|
|
10734
12226
|
maxSerializedBytes: 128 * 1024
|
|
10735
12227
|
})
|
|
10736
12228
|
];
|
|
10737
|
-
function
|
|
10738
|
-
if (Array.isArray(value)) return value.map(
|
|
12229
|
+
function stable5(value) {
|
|
12230
|
+
if (Array.isArray(value)) return value.map(stable5);
|
|
10739
12231
|
if (value === null || typeof value !== "object") return value;
|
|
10740
12232
|
const record = value;
|
|
10741
12233
|
return Object.fromEntries(
|
|
10742
|
-
Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key,
|
|
12234
|
+
Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable5(record[key])])
|
|
10743
12235
|
);
|
|
10744
12236
|
}
|
|
10745
12237
|
function writeJson3(value) {
|
|
10746
|
-
return `${JSON.stringify(
|
|
12238
|
+
return `${JSON.stringify(stable5(value), null, 2)}
|
|
10747
12239
|
`;
|
|
10748
12240
|
}
|
|
10749
12241
|
function escapeHtml2(value) {
|
|
@@ -10760,7 +12252,7 @@ function increment(record, key) {
|
|
|
10760
12252
|
const label = key && key.trim() !== "" ? key : "unknown";
|
|
10761
12253
|
record[label] = (record[label] ?? 0) + 1;
|
|
10762
12254
|
}
|
|
10763
|
-
function
|
|
12255
|
+
function selectRun4(read, runId) {
|
|
10764
12256
|
if (runId !== void 0) {
|
|
10765
12257
|
return read.runs.find((run) => run.runId === runId);
|
|
10766
12258
|
}
|
|
@@ -10795,11 +12287,11 @@ function renderCheckSection(result) {
|
|
|
10795
12287
|
`Diagnostics: ${result.diagnostics.length}`
|
|
10796
12288
|
];
|
|
10797
12289
|
for (const finding of result.findings.slice(0, 10)) {
|
|
10798
|
-
const
|
|
10799
|
-
lines.push(`- ${finding.ruleId}: ${finding.message} (${
|
|
12290
|
+
const path15 = finding.evidence[0]?.path ?? "(run)";
|
|
12291
|
+
lines.push(`- ${finding.ruleId}: ${finding.message} (${path15})`);
|
|
10800
12292
|
}
|
|
10801
|
-
for (const
|
|
10802
|
-
lines.push(`- ${
|
|
12293
|
+
for (const diagnostic4 of result.diagnostics.slice(0, 10)) {
|
|
12294
|
+
lines.push(`- ${diagnostic4.code}: ${diagnostic4.message}`);
|
|
10803
12295
|
}
|
|
10804
12296
|
return lines.join("\n");
|
|
10805
12297
|
}
|
|
@@ -10874,8 +12366,8 @@ function renderHtml(trace, check, diff) {
|
|
|
10874
12366
|
`;
|
|
10875
12367
|
}
|
|
10876
12368
|
async function writeArtifact(outputDir, relativePath, content, files) {
|
|
10877
|
-
const outPath =
|
|
10878
|
-
await promises.mkdir(
|
|
12369
|
+
const outPath = path13__default.default.join(outputDir, relativePath);
|
|
12370
|
+
await promises.mkdir(path13__default.default.dirname(outPath), { recursive: true });
|
|
10879
12371
|
await promises.writeFile(outPath, content, "utf-8");
|
|
10880
12372
|
files.push(relativePath);
|
|
10881
12373
|
}
|
|
@@ -10891,7 +12383,7 @@ function manifestStatus(check, diff) {
|
|
|
10891
12383
|
return "ok";
|
|
10892
12384
|
}
|
|
10893
12385
|
async function artifactsCommand(target, options = {}, stdin = process.stdin) {
|
|
10894
|
-
const outputDir = options.outputDir !== void 0 && options.outputDir.trim() !== "" ?
|
|
12386
|
+
const outputDir = options.outputDir !== void 0 && options.outputDir.trim() !== "" ? path13__default.default.resolve(options.outputDir.trim()) : "";
|
|
10895
12387
|
if (outputDir === "") {
|
|
10896
12388
|
console.error("--output-dir is required.");
|
|
10897
12389
|
process.exitCode = 1;
|
|
@@ -10908,7 +12400,7 @@ async function artifactsCommand(target, options = {}, stdin = process.stdin) {
|
|
|
10908
12400
|
process.exitCode = 1;
|
|
10909
12401
|
return;
|
|
10910
12402
|
}
|
|
10911
|
-
const selectedRun =
|
|
12403
|
+
const selectedRun = selectRun4(read, options.run);
|
|
10912
12404
|
const check = runTraceChecks(
|
|
10913
12405
|
{ read },
|
|
10914
12406
|
{
|
|
@@ -10957,8 +12449,8 @@ async function artifactsCommand(target, options = {}, stdin = process.stdin) {
|
|
|
10957
12449
|
await writeArtifact(outputDir, "report.html", renderHtml(trace, check, diff), files);
|
|
10958
12450
|
const summaryTarget = options.githubSummary ?? process.env.GITHUB_STEP_SUMMARY;
|
|
10959
12451
|
if (summaryTarget !== void 0 && summaryTarget.trim() !== "") {
|
|
10960
|
-
await promises.mkdir(
|
|
10961
|
-
await promises.appendFile(
|
|
12452
|
+
await promises.mkdir(path13__default.default.dirname(path13__default.default.resolve(summaryTarget)), { recursive: true });
|
|
12453
|
+
await promises.appendFile(path13__default.default.resolve(summaryTarget), `
|
|
10962
12454
|
${renderMarkdown(trace, check, diff)}`, "utf-8");
|
|
10963
12455
|
}
|
|
10964
12456
|
const manifestFiles = [...files, "manifest.json"].sort((a, b) => a.localeCompare(b));
|
|
@@ -10977,10 +12469,10 @@ ${renderMarkdown(trace, check, diff)}`, "utf-8");
|
|
|
10977
12469
|
findings: diff?.findings.length ?? 0,
|
|
10978
12470
|
diagnostics: diff?.diagnostics.length ?? 0
|
|
10979
12471
|
},
|
|
10980
|
-
...summaryTarget !== void 0 && summaryTarget.trim() !== "" ? { githubSummary:
|
|
12472
|
+
...summaryTarget !== void 0 && summaryTarget.trim() !== "" ? { githubSummary: path13__default.default.resolve(summaryTarget) } : {},
|
|
10981
12473
|
note: NOTE
|
|
10982
12474
|
};
|
|
10983
|
-
await promises.writeFile(
|
|
12475
|
+
await promises.writeFile(path13__default.default.join(outputDir, "manifest.json"), writeJson3(manifest), "utf-8");
|
|
10984
12476
|
if (options.json === true) {
|
|
10985
12477
|
console.log(writeJson3(manifest).trimEnd());
|
|
10986
12478
|
} else {
|
|
@@ -10991,6 +12483,371 @@ ${renderMarkdown(trace, check, diff)}`, "utf-8");
|
|
|
10991
12483
|
}
|
|
10992
12484
|
}
|
|
10993
12485
|
}
|
|
12486
|
+
function validateReporterArtifactPath(options) {
|
|
12487
|
+
const outputDir = path13__default.default.resolve(options.outputDir);
|
|
12488
|
+
const diagnostics = [];
|
|
12489
|
+
const rawPath = options.relativePath;
|
|
12490
|
+
if (rawPath.length === 0) {
|
|
12491
|
+
diagnostics.push({
|
|
12492
|
+
code: "artifact_path_empty",
|
|
12493
|
+
severity: "error",
|
|
12494
|
+
message: "Reporter artifact path must not be empty."
|
|
12495
|
+
});
|
|
12496
|
+
return { ok: false, outputDir, diagnostics };
|
|
12497
|
+
}
|
|
12498
|
+
if (rawPath.includes("\0")) {
|
|
12499
|
+
diagnostics.push({
|
|
12500
|
+
code: "invalid_artifact_path",
|
|
12501
|
+
severity: "error",
|
|
12502
|
+
message: "Reporter artifact path must not contain null bytes.",
|
|
12503
|
+
target: rawPath
|
|
12504
|
+
});
|
|
12505
|
+
return { ok: false, outputDir, diagnostics };
|
|
12506
|
+
}
|
|
12507
|
+
if (path13__default.default.isAbsolute(rawPath) || path13__default.default.win32.isAbsolute(rawPath)) {
|
|
12508
|
+
diagnostics.push({
|
|
12509
|
+
code: "artifact_path_absolute",
|
|
12510
|
+
severity: "error",
|
|
12511
|
+
message: "Reporter artifact path must be relative.",
|
|
12512
|
+
target: rawPath
|
|
12513
|
+
});
|
|
12514
|
+
return { ok: false, outputDir, diagnostics };
|
|
12515
|
+
}
|
|
12516
|
+
const normalized = path13__default.default.posix.normalize(rawPath.replace(/\\/g, "/"));
|
|
12517
|
+
const segments = normalized.split("/");
|
|
12518
|
+
if (normalized === "." || normalized.startsWith("../") || segments.some((segment) => segment === "..")) {
|
|
12519
|
+
diagnostics.push({
|
|
12520
|
+
code: "artifact_path_escape",
|
|
12521
|
+
severity: "error",
|
|
12522
|
+
message: "Reporter artifact path must stay under the output directory.",
|
|
12523
|
+
target: rawPath
|
|
12524
|
+
});
|
|
12525
|
+
return { ok: false, outputDir, diagnostics };
|
|
12526
|
+
}
|
|
12527
|
+
const absolutePath = path13__default.default.resolve(outputDir, normalized);
|
|
12528
|
+
const relFromOutput = path13__default.default.relative(outputDir, absolutePath);
|
|
12529
|
+
if (relFromOutput.length === 0 || relFromOutput.startsWith("..") || path13__default.default.isAbsolute(relFromOutput)) {
|
|
12530
|
+
diagnostics.push({
|
|
12531
|
+
code: "artifact_path_escape",
|
|
12532
|
+
severity: "error",
|
|
12533
|
+
message: "Reporter artifact path resolved outside the output directory.",
|
|
12534
|
+
target: rawPath
|
|
12535
|
+
});
|
|
12536
|
+
return { ok: false, outputDir, diagnostics };
|
|
12537
|
+
}
|
|
12538
|
+
return {
|
|
12539
|
+
ok: true,
|
|
12540
|
+
outputDir,
|
|
12541
|
+
relativePath: normalized,
|
|
12542
|
+
absolutePath,
|
|
12543
|
+
diagnostics
|
|
12544
|
+
};
|
|
12545
|
+
}
|
|
12546
|
+
|
|
12547
|
+
// packages/cli/src/ci-summary.ts
|
|
12548
|
+
var NOTE2 = "Generated locally by AgentInspect from reporter artifact manifests. Trace contents are not embedded.";
|
|
12549
|
+
var MAX_TEXT = 180;
|
|
12550
|
+
var FRAMEWORKS = /* @__PURE__ */ new Set(["vitest", "jest", "manual"]);
|
|
12551
|
+
var STATUSES = /* @__PURE__ */ new Set(["passed", "failed", "skipped", "todo"]);
|
|
12552
|
+
var ARTIFACT_KINDS = /* @__PURE__ */ new Set(["trace", "report", "eval", "redaction", "summary"]);
|
|
12553
|
+
var ARTIFACT_FORMATS = /* @__PURE__ */ new Set(["json", "jsonl", "md", "html"]);
|
|
12554
|
+
var REDACTION_PROFILES = /* @__PURE__ */ new Set(["local", "share", "strict"]);
|
|
12555
|
+
function stable6(value) {
|
|
12556
|
+
if (Array.isArray(value)) return value.map(stable6);
|
|
12557
|
+
if (value === null || typeof value !== "object") return value;
|
|
12558
|
+
const record = value;
|
|
12559
|
+
return Object.fromEntries(
|
|
12560
|
+
Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable6(record[key])])
|
|
12561
|
+
);
|
|
12562
|
+
}
|
|
12563
|
+
function writeJson4(value) {
|
|
12564
|
+
return `${JSON.stringify(stable6(value), null, 2)}
|
|
12565
|
+
`;
|
|
12566
|
+
}
|
|
12567
|
+
function isObject(value) {
|
|
12568
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12569
|
+
}
|
|
12570
|
+
function readString(value, label) {
|
|
12571
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
12572
|
+
throw new Error(`${label} must be a non-empty string.`);
|
|
12573
|
+
}
|
|
12574
|
+
return safeText(value);
|
|
12575
|
+
}
|
|
12576
|
+
function readOptionalString(value) {
|
|
12577
|
+
return typeof value === "string" && value.trim() !== "" ? safeText(value) : void 0;
|
|
12578
|
+
}
|
|
12579
|
+
function readFramework(value) {
|
|
12580
|
+
const framework = readString(value, "manifest.framework");
|
|
12581
|
+
if (!FRAMEWORKS.has(framework)) {
|
|
12582
|
+
throw new Error(`Unsupported reporter framework: ${framework}.`);
|
|
12583
|
+
}
|
|
12584
|
+
return framework;
|
|
12585
|
+
}
|
|
12586
|
+
function readStatus(value) {
|
|
12587
|
+
const status = readString(value, "result.status");
|
|
12588
|
+
if (!STATUSES.has(status)) {
|
|
12589
|
+
throw new Error(`Unsupported reporter test status: ${status}.`);
|
|
12590
|
+
}
|
|
12591
|
+
return status;
|
|
12592
|
+
}
|
|
12593
|
+
function readArtifact(value, index) {
|
|
12594
|
+
if (!isObject(value)) throw new Error(`manifest.artifacts[${index}] must be an object.`);
|
|
12595
|
+
const kind = readString(value.kind, `manifest.artifacts[${index}].kind`);
|
|
12596
|
+
const format = readString(value.format, `manifest.artifacts[${index}].format`);
|
|
12597
|
+
const redactionProfile = readString(
|
|
12598
|
+
value.redactionProfile,
|
|
12599
|
+
`manifest.artifacts[${index}].redactionProfile`
|
|
12600
|
+
);
|
|
12601
|
+
if (!ARTIFACT_KINDS.has(kind)) throw new Error(`Unsupported artifact kind: ${kind}.`);
|
|
12602
|
+
if (!ARTIFACT_FORMATS.has(format)) throw new Error(`Unsupported artifact format: ${format}.`);
|
|
12603
|
+
if (!REDACTION_PROFILES.has(redactionProfile)) {
|
|
12604
|
+
throw new Error(`Unsupported artifact redaction profile: ${redactionProfile}.`);
|
|
12605
|
+
}
|
|
12606
|
+
const artifactPath = readString(value.path, `manifest.artifacts[${index}].path`);
|
|
12607
|
+
const pathCheck = validateReporterArtifactPath({
|
|
12608
|
+
outputDir: process.cwd(),
|
|
12609
|
+
relativePath: artifactPath
|
|
12610
|
+
});
|
|
12611
|
+
if (!pathCheck.ok || pathCheck.relativePath === void 0) {
|
|
12612
|
+
throw new Error(`Unsafe reporter artifact path: ${artifactPath}.`);
|
|
12613
|
+
}
|
|
12614
|
+
return {
|
|
12615
|
+
kind,
|
|
12616
|
+
path: pathCheck.relativePath,
|
|
12617
|
+
format,
|
|
12618
|
+
redactionProfile
|
|
12619
|
+
};
|
|
12620
|
+
}
|
|
12621
|
+
function readArtifacts(value, label) {
|
|
12622
|
+
if (value === void 0) return [];
|
|
12623
|
+
if (!Array.isArray(value)) throw new Error(`${label} must be an array.`);
|
|
12624
|
+
return value.map((item, index) => readArtifact(item, index));
|
|
12625
|
+
}
|
|
12626
|
+
function readDiagnosticsCount(value, label) {
|
|
12627
|
+
if (value === void 0) return 0;
|
|
12628
|
+
if (!Array.isArray(value)) throw new Error(`${label} must be an array.`);
|
|
12629
|
+
return value.length;
|
|
12630
|
+
}
|
|
12631
|
+
function readResult(value, index) {
|
|
12632
|
+
if (!isObject(value)) throw new Error(`manifest.results[${index}] must be an object.`);
|
|
12633
|
+
const file = readOptionalString(value.file);
|
|
12634
|
+
const tracePath = readOptionalString(value.tracePath);
|
|
12635
|
+
return {
|
|
12636
|
+
testId: readString(value.testId, `manifest.results[${index}].testId`),
|
|
12637
|
+
name: readString(value.name, `manifest.results[${index}].name`),
|
|
12638
|
+
...file === void 0 ? {} : { file },
|
|
12639
|
+
status: readStatus(value.status),
|
|
12640
|
+
...tracePath === void 0 ? {} : { tracePath },
|
|
12641
|
+
artifacts: readArtifacts(value.artifacts, `manifest.results[${index}].artifacts`),
|
|
12642
|
+
diagnostics: readDiagnosticsCount(
|
|
12643
|
+
value.diagnostics,
|
|
12644
|
+
`manifest.results[${index}].diagnostics`
|
|
12645
|
+
)
|
|
12646
|
+
};
|
|
12647
|
+
}
|
|
12648
|
+
function readManifestDocument(value) {
|
|
12649
|
+
if (!isObject(value)) throw new Error("Reporter manifest file must contain a JSON object.");
|
|
12650
|
+
const candidate = isObject(value.manifest) ? value.manifest : value;
|
|
12651
|
+
if (!isObject(candidate)) throw new Error("Reporter manifest must be a JSON object.");
|
|
12652
|
+
const schemaVersion = readString(candidate.schemaVersion, "manifest.schemaVersion");
|
|
12653
|
+
if (schemaVersion !== "0.1") {
|
|
12654
|
+
throw new Error(`Unsupported reporter manifest schemaVersion: ${schemaVersion}.`);
|
|
12655
|
+
}
|
|
12656
|
+
if (!Array.isArray(candidate.results)) {
|
|
12657
|
+
throw new Error("manifest.results must be an array.");
|
|
12658
|
+
}
|
|
12659
|
+
const artifacts = readArtifacts(candidate.artifacts, "manifest.artifacts");
|
|
12660
|
+
const results = candidate.results.map((item, index) => readResult(item, index));
|
|
12661
|
+
const diagnostics = readDiagnosticsCount(candidate.diagnostics, "manifest.diagnostics");
|
|
12662
|
+
const manifest = {
|
|
12663
|
+
framework: readFramework(candidate.framework),
|
|
12664
|
+
generatedAt: readString(candidate.generatedAt, "manifest.generatedAt"),
|
|
12665
|
+
results,
|
|
12666
|
+
artifacts
|
|
12667
|
+
};
|
|
12668
|
+
return {
|
|
12669
|
+
packageName: readOptionalString(value.package),
|
|
12670
|
+
manifest,
|
|
12671
|
+
diagnostics
|
|
12672
|
+
};
|
|
12673
|
+
}
|
|
12674
|
+
function cwdRelative(filePath) {
|
|
12675
|
+
const relative = path13__default.default.relative(process.cwd(), path13__default.default.resolve(filePath)).replace(/\\/g, "/");
|
|
12676
|
+
if (relative === "" || relative.startsWith("../") || path13__default.default.isAbsolute(relative)) {
|
|
12677
|
+
return path13__default.default.basename(filePath);
|
|
12678
|
+
}
|
|
12679
|
+
return relative;
|
|
12680
|
+
}
|
|
12681
|
+
async function readReporterManifest(filePath) {
|
|
12682
|
+
const absolute = path13__default.default.resolve(filePath);
|
|
12683
|
+
const raw = await promises.readFile(absolute, "utf-8");
|
|
12684
|
+
const document = readManifestDocument(JSON.parse(raw));
|
|
12685
|
+
const manifest = document.manifest;
|
|
12686
|
+
const results = manifest.results.map((result) => ({
|
|
12687
|
+
testId: safeText(result.testId),
|
|
12688
|
+
name: safeText(result.name),
|
|
12689
|
+
...result.file === void 0 ? {} : { file: safeText(path13__default.default.basename(result.file)) },
|
|
12690
|
+
status: result.status,
|
|
12691
|
+
...result.tracePath === void 0 ? {} : { tracePath: safeText(path13__default.default.basename(result.tracePath)) },
|
|
12692
|
+
artifacts: result.artifacts,
|
|
12693
|
+
diagnostics: result.diagnostics
|
|
12694
|
+
}));
|
|
12695
|
+
return {
|
|
12696
|
+
...document.packageName === void 0 ? {} : { packageName: document.packageName },
|
|
12697
|
+
manifestFile: cwdRelative(absolute),
|
|
12698
|
+
framework: manifest.framework,
|
|
12699
|
+
generatedAt: manifest.generatedAt,
|
|
12700
|
+
results,
|
|
12701
|
+
artifacts: manifest.artifacts,
|
|
12702
|
+
diagnostics: document.diagnostics
|
|
12703
|
+
};
|
|
12704
|
+
}
|
|
12705
|
+
function summarize3(manifests) {
|
|
12706
|
+
const summary = {
|
|
12707
|
+
manifests: manifests.length,
|
|
12708
|
+
tests: 0,
|
|
12709
|
+
failed: 0,
|
|
12710
|
+
passed: 0,
|
|
12711
|
+
skipped: 0,
|
|
12712
|
+
todo: 0,
|
|
12713
|
+
artifacts: 0,
|
|
12714
|
+
diagnostics: 0
|
|
12715
|
+
};
|
|
12716
|
+
for (const manifest of manifests) {
|
|
12717
|
+
summary.artifacts += manifest.artifacts.length;
|
|
12718
|
+
summary.diagnostics += manifest.diagnostics;
|
|
12719
|
+
for (const result of manifest.results) {
|
|
12720
|
+
summary.tests += 1;
|
|
12721
|
+
if (result.status === "failed") summary.failed += 1;
|
|
12722
|
+
else if (result.status === "passed") summary.passed += 1;
|
|
12723
|
+
else if (result.status === "skipped") summary.skipped += 1;
|
|
12724
|
+
else summary.todo += 1;
|
|
12725
|
+
summary.artifacts += result.artifacts.length;
|
|
12726
|
+
summary.diagnostics += result.diagnostics;
|
|
12727
|
+
}
|
|
12728
|
+
}
|
|
12729
|
+
return {
|
|
12730
|
+
status: summary.failed > 0 ? "failed" : summary.diagnostics > 0 ? "warning" : "ok",
|
|
12731
|
+
manifests,
|
|
12732
|
+
summary,
|
|
12733
|
+
note: NOTE2
|
|
12734
|
+
};
|
|
12735
|
+
}
|
|
12736
|
+
function markdownCell(value) {
|
|
12737
|
+
return safeText(String(value ?? "unknown")).replaceAll("|", "\\|").replace(/\r?\n/g, " ");
|
|
12738
|
+
}
|
|
12739
|
+
function renderMarkdown2(result) {
|
|
12740
|
+
const lines = [
|
|
12741
|
+
"# AgentInspect CI Summary",
|
|
12742
|
+
"",
|
|
12743
|
+
NOTE2,
|
|
12744
|
+
"",
|
|
12745
|
+
"| Field | Value |",
|
|
12746
|
+
"| --- | --- |",
|
|
12747
|
+
`| Status | ${result.status} |`,
|
|
12748
|
+
`| Manifests | ${result.summary.manifests} |`,
|
|
12749
|
+
`| Tests | ${result.summary.tests} |`,
|
|
12750
|
+
`| Failed | ${result.summary.failed} |`,
|
|
12751
|
+
`| Passed | ${result.summary.passed} |`,
|
|
12752
|
+
`| Skipped | ${result.summary.skipped} |`,
|
|
12753
|
+
`| Todo | ${result.summary.todo} |`,
|
|
12754
|
+
`| Artifacts | ${result.summary.artifacts} |`,
|
|
12755
|
+
`| Diagnostics | ${result.summary.diagnostics} |`,
|
|
12756
|
+
"",
|
|
12757
|
+
"## Tests",
|
|
12758
|
+
"",
|
|
12759
|
+
"| Framework | Status | Test | File | Trace | Artifacts |",
|
|
12760
|
+
"| --- | --- | --- | --- | --- | --- |"
|
|
12761
|
+
];
|
|
12762
|
+
const rows = result.manifests.flatMap(
|
|
12763
|
+
(manifest) => manifest.results.map((test) => ({
|
|
12764
|
+
framework: manifest.framework,
|
|
12765
|
+
test
|
|
12766
|
+
}))
|
|
12767
|
+
);
|
|
12768
|
+
if (rows.length === 0) {
|
|
12769
|
+
lines.push("| unknown | unknown | No tests found | unknown | unknown | 0 |");
|
|
12770
|
+
} else {
|
|
12771
|
+
for (const row of rows) {
|
|
12772
|
+
lines.push(
|
|
12773
|
+
`| ${markdownCell(row.framework)} | ${markdownCell(row.test.status)} | ${markdownCell(row.test.name)} | ${markdownCell(row.test.file)} | ${markdownCell(row.test.tracePath)} | ${row.test.artifacts.length} |`
|
|
12774
|
+
);
|
|
12775
|
+
}
|
|
12776
|
+
}
|
|
12777
|
+
lines.push("", "## Manifests", "", "| Framework | File | Generated | Artifacts |", "| --- | --- | --- | --- |");
|
|
12778
|
+
for (const manifest of result.manifests) {
|
|
12779
|
+
lines.push(
|
|
12780
|
+
`| ${markdownCell(manifest.framework)} | ${markdownCell(manifest.manifestFile)} | ${markdownCell(manifest.generatedAt)} | ${manifest.artifacts.length} |`
|
|
12781
|
+
);
|
|
12782
|
+
}
|
|
12783
|
+
lines.push("", "## Artifacts", "", "| Framework | Kind | Path | Format | Profile |", "| --- | --- | --- | --- | --- |");
|
|
12784
|
+
const artifacts = result.manifests.flatMap(
|
|
12785
|
+
(manifest) => manifest.artifacts.map((artifact) => ({ framework: manifest.framework, artifact }))
|
|
12786
|
+
);
|
|
12787
|
+
if (artifacts.length === 0) {
|
|
12788
|
+
lines.push("| unknown | unknown | No artifacts found | unknown | unknown |");
|
|
12789
|
+
} else {
|
|
12790
|
+
for (const row of artifacts) {
|
|
12791
|
+
lines.push(
|
|
12792
|
+
`| ${markdownCell(row.framework)} | ${markdownCell(row.artifact.kind)} | ${markdownCell(row.artifact.path)} | ${markdownCell(row.artifact.format)} | ${markdownCell(row.artifact.redactionProfile)} |`
|
|
12793
|
+
);
|
|
12794
|
+
}
|
|
12795
|
+
}
|
|
12796
|
+
lines.push("");
|
|
12797
|
+
return `${lines.join("\n")}
|
|
12798
|
+
`;
|
|
12799
|
+
}
|
|
12800
|
+
function safeText(value) {
|
|
12801
|
+
const compact = value.replace(/\s+/g, " ").trim();
|
|
12802
|
+
const redacted = compact.replace(/\bsk-[A-Za-z0-9_-]{8,}\b/g, "[REDACTED]").replace(
|
|
12803
|
+
/\b(?:api[_-]?key|authorization|token|secret|password)\s*[:=]\s*[^,\s]+/gi,
|
|
12804
|
+
"$1=[REDACTED]"
|
|
12805
|
+
);
|
|
12806
|
+
if (redacted.length <= MAX_TEXT) return redacted;
|
|
12807
|
+
return `${redacted.slice(0, MAX_TEXT - 12)}...[truncated]`;
|
|
12808
|
+
}
|
|
12809
|
+
async function ciSummaryCommand(manifestPaths, options = {}) {
|
|
12810
|
+
if (manifestPaths.length === 0) {
|
|
12811
|
+
console.error("At least one reporter manifest path is required.");
|
|
12812
|
+
process.exitCode = 1;
|
|
12813
|
+
return;
|
|
12814
|
+
}
|
|
12815
|
+
let result;
|
|
12816
|
+
try {
|
|
12817
|
+
const manifests = [];
|
|
12818
|
+
for (const manifestPath of manifestPaths) {
|
|
12819
|
+
manifests.push(await readReporterManifest(manifestPath));
|
|
12820
|
+
}
|
|
12821
|
+
manifests.sort((a, b) => a.manifestFile.localeCompare(b.manifestFile));
|
|
12822
|
+
result = summarize3(manifests);
|
|
12823
|
+
} catch (error) {
|
|
12824
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
12825
|
+
console.error(`[AgentInspect] ci-summary failed: ${message}`);
|
|
12826
|
+
process.exitCode = 1;
|
|
12827
|
+
return;
|
|
12828
|
+
}
|
|
12829
|
+
const markdown = renderMarkdown2(result);
|
|
12830
|
+
const outputPath = options.output !== void 0 && options.output.trim() !== "" ? path13__default.default.resolve(options.output.trim()) : void 0;
|
|
12831
|
+
if (outputPath !== void 0) {
|
|
12832
|
+
await promises.mkdir(path13__default.default.dirname(outputPath), { recursive: true });
|
|
12833
|
+
await promises.writeFile(outputPath, markdown, "utf-8");
|
|
12834
|
+
}
|
|
12835
|
+
const summaryTarget = options.githubSummary ?? process.env.GITHUB_STEP_SUMMARY;
|
|
12836
|
+
if (summaryTarget !== void 0 && summaryTarget.trim() !== "") {
|
|
12837
|
+
const summaryPath = path13__default.default.resolve(summaryTarget);
|
|
12838
|
+
await promises.mkdir(path13__default.default.dirname(summaryPath), { recursive: true });
|
|
12839
|
+
await promises.appendFile(summaryPath, `
|
|
12840
|
+
${markdown}`, "utf-8");
|
|
12841
|
+
}
|
|
12842
|
+
if (options.json === true) {
|
|
12843
|
+
console.log(writeJson4(result).trimEnd());
|
|
12844
|
+
} else if (outputPath !== void 0) {
|
|
12845
|
+
console.log(`Wrote AgentInspect CI summary to ${outputPath}`);
|
|
12846
|
+
console.log(`Status: ${result.status}`);
|
|
12847
|
+
} else {
|
|
12848
|
+
console.log(markdown.trimEnd());
|
|
12849
|
+
}
|
|
12850
|
+
}
|
|
10994
12851
|
|
|
10995
12852
|
// packages/cli/src/index.ts
|
|
10996
12853
|
function runCommand(action) {
|
|
@@ -11119,6 +12976,36 @@ function createCliProgram() {
|
|
|
11119
12976
|
]).option("--max-total-tokens <number>", "add llm.usage with a max total-token budget").action((target, opts) => {
|
|
11120
12977
|
runCommand(() => checkCommand(target, opts));
|
|
11121
12978
|
});
|
|
12979
|
+
program.command("eval").description("Run deterministic local evals against a trace").argument("<trace-path-or-run-id>", "trace file, directory, stdin -, or run id").option("--dir <path>", "trace directory for run-id lookup").addOption(
|
|
12980
|
+
new commander.Option("--format <format>", "trace input format").choices([
|
|
12981
|
+
"agent-inspect-jsonl",
|
|
12982
|
+
"openinference-json",
|
|
12983
|
+
"otlp-json"
|
|
12984
|
+
])
|
|
12985
|
+
).option("--run <run-id>", "select a run when the trace contains multiple runs").option("--config <path>", "path to eval config (.json, .js, .mjs, .cjs)").option("--json", "print deterministic JSON eval result").option("--markdown", "print deterministic Markdown eval summary").option("--require-success", "require the selected run to complete successfully").option("--required-tool <name>", "require a tool name (repeatable)", (value, previous = []) => [
|
|
12986
|
+
...previous,
|
|
12987
|
+
value
|
|
12988
|
+
]).option("--forbid-tool <name>", "forbid a tool name (repeatable)", (value, previous = []) => [
|
|
12989
|
+
...previous,
|
|
12990
|
+
value
|
|
12991
|
+
]).option("--forbidden-tool <name>", "alias for --forbid-tool (repeatable)", (value, previous = []) => [
|
|
12992
|
+
...previous,
|
|
12993
|
+
value
|
|
12994
|
+
]).option("--max-duration-ms <number>", "require run duration at or below this value").option("--max-depth <number>", "require tree depth at or below this value").option("--max-retries <number>", "require retry counts at or below this value").option("--max-total-tokens <number>", "require total LLM tokens at or below this value").option(
|
|
12995
|
+
"--require-retrieval-before-generation",
|
|
12996
|
+
"require a retrieval step before the first LLM generation"
|
|
12997
|
+
).option("--required-decision-metadata <key>", "require decision metadata (repeatable)", (value, previous = []) => [
|
|
12998
|
+
...previous,
|
|
12999
|
+
value
|
|
13000
|
+
]).option("--context-overlap", "require answer/context token overlap").option("--min-context-overlap <number>", "minimum answer/context overlap ratio").option("--min-shared-terms <number>", "minimum shared answer/context terms").option("--quote-overlap", "require quoted answer text to appear in context").option("--citation-presence", "require a citation or source reference").option("--required-source-id <id>", "require a source id in context or citations (repeatable)", (value, previous = []) => [
|
|
13001
|
+
...previous,
|
|
13002
|
+
value
|
|
13003
|
+
]).option("--min-answer-characters <number>", "minimum answer character count").option("--max-answer-characters <number>", "maximum answer character count").option("--min-answer-words <number>", "minimum answer word count").option("--max-answer-words <number>", "maximum answer word count").option("--banned-phrase <text>", "ban unsupported-answer phrasing (repeatable)", (value, previous = []) => [
|
|
13004
|
+
...previous,
|
|
13005
|
+
value
|
|
13006
|
+
]).action((target, opts) => {
|
|
13007
|
+
runCommand(() => evalCommand(target, opts));
|
|
13008
|
+
});
|
|
11122
13009
|
program.command("scan").description("Best-effort local safety scan for trace capture risks").argument("<trace-path-or-run-id>", "trace file, directory, stdin -, or run id").option("--dir <path>", "trace directory for run-id lookup").addOption(
|
|
11123
13010
|
new commander.Option("--format <format>", "trace input format").choices([
|
|
11124
13011
|
"agent-inspect-jsonl",
|
|
@@ -11146,6 +13033,9 @@ function createCliProgram() {
|
|
|
11146
13033
|
).option("--run <run-id>", "select a run when the trace contains multiple runs").option("--baseline <trace-path-or-run-id>", "optional baseline trace for diff artifacts").option("--baseline-run <run-id>", "select a run from the baseline trace").option("--github-summary <path>", "append a safe summary to this file, e.g. GITHUB_STEP_SUMMARY").option("--json", "print deterministic JSON manifest").action((target, opts) => {
|
|
11147
13034
|
runCommand(() => artifactsCommand(target, opts));
|
|
11148
13035
|
});
|
|
13036
|
+
program.command("ci-summary").description("Summarize local reporter artifact manifests for CI").argument("<manifest...>", "reporter artifact manifest JSON files").option("-o, --output <path>", "write Markdown summary to a local file").option("--github-summary <path>", "append Markdown summary to this local file, e.g. GITHUB_STEP_SUMMARY").option("--json", "print deterministic JSON summary").action((manifest, opts) => {
|
|
13037
|
+
runCommand(() => ciSummaryCommand(manifest, opts));
|
|
13038
|
+
});
|
|
11149
13039
|
program.command("diff").description("Compare two local AgentInspect JSONL traces (read-only)").argument("<left-run-id>", "first run id").argument("<right-run-id>", "second run id").option("--dir <path>", "trace directory").option("--json", "print diff result as JSON").option("--ignore-duration", "omit duration comparisons").option(
|
|
11150
13040
|
"--duration-threshold <duration>",
|
|
11151
13041
|
"ignore duration deltas at or below this (e.g. 500ms, 2s, 1m)"
|
|
@@ -11206,6 +13096,14 @@ function createCliProgram() {
|
|
|
11206
13096
|
).action((runId, opts) => {
|
|
11207
13097
|
runCommand(() => reportCommand(runId, opts));
|
|
11208
13098
|
});
|
|
13099
|
+
program.command("redact").description("Redact a local JSON or JSONL trace/file").argument("<trace-or-file>", "trace file, JSON file, stdin -, or run id").option("--dir <path>", "trace directory for run-id lookup").addOption(
|
|
13100
|
+
new commander.Option(
|
|
13101
|
+
"--profile <profile>",
|
|
13102
|
+
"redaction profile: local, share, strict (default: share)"
|
|
13103
|
+
).choices(["local", "share", "strict"])
|
|
13104
|
+
).option("-o, --output <path>", "write redacted content to a file").option("--json", "print deterministic JSON wrapper with findings").action((target, opts) => {
|
|
13105
|
+
runCommand(() => redactCommand(target, opts));
|
|
13106
|
+
});
|
|
11209
13107
|
program.command("explain").description("Explain a local trace with deterministic facts (no provider calls)").argument("<trace-path-or-run-id>", "trace file, directory, stdin -, or run id").option("--dir <path>", "trace directory for run-id lookup").addOption(
|
|
11210
13108
|
new commander.Option("--format <format>", "trace input format").choices([
|
|
11211
13109
|
"agent-inspect-jsonl",
|
|
@@ -11230,9 +13128,9 @@ function isPrimaryModule() {
|
|
|
11230
13128
|
if (!entry) return false;
|
|
11231
13129
|
const selfPath = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
11232
13130
|
try {
|
|
11233
|
-
return fs.realpathSync(
|
|
13131
|
+
return fs.realpathSync(path13__default.default.resolve(entry)) === fs.realpathSync(path13__default.default.resolve(selfPath));
|
|
11234
13132
|
} catch {
|
|
11235
|
-
return
|
|
13133
|
+
return path13__default.default.resolve(entry) === path13__default.default.resolve(selfPath);
|
|
11236
13134
|
}
|
|
11237
13135
|
}
|
|
11238
13136
|
if (isPrimaryModule()) {
|