agent-inspect 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { realpathSync, createReadStream } from 'fs';
3
- import path10 from 'path';
3
+ import path11 from 'path';
4
4
  import { fileURLToPath, pathToFileURL } from 'url';
5
5
  import { Command, Option } from 'commander';
6
6
  import { AsyncLocalStorage } from 'async_hooks';
@@ -12,7 +12,7 @@ import tty from 'tty';
12
12
  import { createInterface } from 'readline';
13
13
 
14
14
  // package.json
15
- var version = "2.0.0";
15
+ var version = "2.1.0";
16
16
 
17
17
  // packages/core/src/correlation-metadata.ts
18
18
  var TRACE_CORRELATION_KEYS = [
@@ -744,7 +744,7 @@ function formatDuration(ms) {
744
744
  // packages/core/src/utils.ts
745
745
  var DEFAULT_TRACE_DIR_NAME = ".agent-inspect";
746
746
  var RUNS_DIR_NAME = "runs";
747
- var FALLBACK_TRACE_DIR = path10.join(
747
+ var FALLBACK_TRACE_DIR = path11.join(
748
748
  os.tmpdir(),
749
749
  "agent-inspect",
750
750
  RUNS_DIR_NAME
@@ -779,7 +779,7 @@ function getDefaultTraceDir() {
779
779
  if (typeof home !== "string" || home.trim() === "") {
780
780
  return FALLBACK_TRACE_DIR;
781
781
  }
782
- return path10.join(home, DEFAULT_TRACE_DIR_NAME, RUNS_DIR_NAME);
782
+ return path11.join(home, DEFAULT_TRACE_DIR_NAME, RUNS_DIR_NAME);
783
783
  } catch {
784
784
  return FALLBACK_TRACE_DIR;
785
785
  }
@@ -787,11 +787,11 @@ function getDefaultTraceDir() {
787
787
  function getTraceFilePath(runId, traceDir) {
788
788
  const baseDir = traceDir ?? getDefaultTraceDir();
789
789
  let safeId = typeof runId === "string" && runId.trim() !== "" ? runId.trim() : "run_unknown";
790
- safeId = path10.basename(safeId);
790
+ safeId = path11.basename(safeId);
791
791
  if (safeId === "" || safeId === "." || safeId === "..") {
792
792
  safeId = "run_unknown";
793
793
  }
794
- return path10.join(baseDir, `${safeId}.jsonl`);
794
+ return path11.join(baseDir, `${safeId}.jsonl`);
795
795
  }
796
796
  function formatError(error) {
797
797
  if (error instanceof Error) {
@@ -1553,7 +1553,7 @@ var TraceDirectory = class {
1553
1553
  this.#dir = resolveTraceDir(options);
1554
1554
  }
1555
1555
  getPath(filename) {
1556
- return filename ? path10.join(this.#dir, filename) : this.#dir;
1556
+ return filename ? path11.join(this.#dir, filename) : this.#dir;
1557
1557
  }
1558
1558
  async list() {
1559
1559
  try {
@@ -1580,7 +1580,7 @@ function parseIsoToMs2(value) {
1580
1580
  }
1581
1581
  async function extractMetadata(filePath, _quickScan) {
1582
1582
  const stats = await stat(filePath);
1583
- let runIdFromFile = path10.basename(filePath);
1583
+ let runIdFromFile = path11.basename(filePath);
1584
1584
  if (runIdFromFile.endsWith(".jsonl")) {
1585
1585
  runIdFromFile = runIdFromFile.slice(0, -".jsonl".length);
1586
1586
  }
@@ -3551,7 +3551,7 @@ function findReaderByFormat(format, readers) {
3551
3551
  }
3552
3552
  async function jsonlFilesInDirectory(dirPath) {
3553
3553
  const entries = await readdir(dirPath, { withFileTypes: true });
3554
- return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map((entry) => path10.join(dirPath, entry.name)).sort((a, b) => a.localeCompare(b));
3554
+ return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map((entry) => path11.join(dirPath, entry.name)).sort((a, b) => a.localeCompare(b));
3555
3555
  }
3556
3556
  async function resolveInput(input3) {
3557
3557
  const cached = resolvedInputCache.get(input3);
@@ -5294,11 +5294,11 @@ function isRecord9(v) {
5294
5294
  function isNonEmptyStringArray(v) {
5295
5295
  return Array.isArray(v) && v.length > 0 && v.every((x) => typeof x === "string" && x.trim() !== "");
5296
5296
  }
5297
- function validateRedact(redact) {
5298
- if (!Array.isArray(redact)) {
5297
+ function validateRedact(redact2) {
5298
+ if (!Array.isArray(redact2)) {
5299
5299
  throw new Error("Invalid config: redact must be an array");
5300
5300
  }
5301
- for (const r of redact) {
5301
+ for (const r of redact2) {
5302
5302
  if (typeof r === "string") continue;
5303
5303
  if (!isRecord9(r)) {
5304
5304
  throw new Error("Invalid config: redact entries must be strings or objects");
@@ -7677,9 +7677,9 @@ Trace directory: ${traceDir}`);
7677
7677
  if (validation !== void 0 && !validation.ok) {
7678
7678
  process.exitCode = 1;
7679
7679
  }
7680
- const outPath = options.output !== void 0 && options.output.trim() !== "" ? path10.resolve(options.output.trim()) : void 0;
7680
+ const outPath = options.output !== void 0 && options.output.trim() !== "" ? path11.resolve(options.output.trim()) : void 0;
7681
7681
  if (outPath !== void 0) {
7682
- await mkdir(path10.dirname(outPath), { recursive: true });
7682
+ await mkdir(path11.dirname(outPath), { recursive: true });
7683
7683
  await writeFile(outPath, result.content, "utf-8");
7684
7684
  const vlabel = validation !== void 0 ? validation.ok ? "ok" : "failed" : "skipped";
7685
7685
  console.log(`Wrote ${result.fileExtension} export to ${outPath} (validation: ${vlabel})`);
@@ -7856,13 +7856,13 @@ function pairSteps(left, right) {
7856
7856
  return pairs;
7857
7857
  }
7858
7858
  function compareLeafSteps(L, R, segments, opts, out) {
7859
- const path12 = buildPath(segments);
7859
+ const path13 = buildPath(segments);
7860
7860
  if (L.name !== R.name) {
7861
7861
  out.push({
7862
7862
  kind: "structure",
7863
7863
  severity: "warning",
7864
7864
  message: "Step name differs",
7865
- path: path12,
7865
+ path: path13,
7866
7866
  left: L.name,
7867
7867
  right: R.name
7868
7868
  });
@@ -7872,7 +7872,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
7872
7872
  kind: "step-type",
7873
7873
  severity: "warning",
7874
7874
  message: "Step type differs",
7875
- path: path12,
7875
+ path: path13,
7876
7876
  left: L.type,
7877
7877
  right: R.type
7878
7878
  });
@@ -7882,7 +7882,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
7882
7882
  kind: "step-status",
7883
7883
  severity: "warning",
7884
7884
  message: "Step status differs",
7885
- path: path12,
7885
+ path: path13,
7886
7886
  left: L.status,
7887
7887
  right: R.status
7888
7888
  });
@@ -7894,7 +7894,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
7894
7894
  kind: "error",
7895
7895
  severity: "error",
7896
7896
  message: "Step error message differs",
7897
- path: path12,
7897
+ path: path13,
7898
7898
  left: le || void 0,
7899
7899
  right: re || void 0
7900
7900
  });
@@ -7912,7 +7912,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
7912
7912
  kind: "duration",
7913
7913
  severity: "info",
7914
7914
  message: "Step duration differs",
7915
- path: path12,
7915
+ path: path13,
7916
7916
  left: ld,
7917
7917
  right: rd
7918
7918
  });
@@ -7925,7 +7925,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
7925
7925
  kind: "metadata",
7926
7926
  severity: "info",
7927
7927
  message: "Step metadata differs",
7928
- path: path12,
7928
+ path: path13,
7929
7929
  left: L.metadata,
7930
7930
  right: R.metadata
7931
7931
  });
@@ -7937,7 +7937,7 @@ function compareLeafSteps(L, R, segments, opts, out) {
7937
7937
  kind: "output",
7938
7938
  severity: "info",
7939
7939
  message: "Output preview differs",
7940
- path: path12,
7940
+ path: path13,
7941
7941
  left: L.outputPreview,
7942
7942
  right: R.outputPreview
7943
7943
  });
@@ -8097,11 +8097,11 @@ function diffRuns(left, right, options) {
8097
8097
  }
8098
8098
 
8099
8099
  // packages/core/src/diff/renderer.ts
8100
- function formatPath(path12) {
8101
- if (path12 === void 0 || path12.path.length === 0) {
8100
+ function formatPath(path13) {
8101
+ if (path13 === void 0 || path13.path.length === 0) {
8102
8102
  return "(run)";
8103
8103
  }
8104
- return path12.path.map((s) => s.name).join(" > ");
8104
+ return path13.path.map((s) => s.name).join(" > ");
8105
8105
  }
8106
8106
  function formatValue(v, verbose) {
8107
8107
  if (v === void 0) return "(undefined)";
@@ -8532,9 +8532,9 @@ async function reportCommand(runId, options = {}) {
8532
8532
  redactionProfile,
8533
8533
  correlation: !options.noCorrelation
8534
8534
  });
8535
- const outPath = options.output !== void 0 && options.output.trim() !== "" ? path10.resolve(options.output.trim()) : void 0;
8535
+ const outPath = options.output !== void 0 && options.output.trim() !== "" ? path11.resolve(options.output.trim()) : void 0;
8536
8536
  if (outPath !== void 0) {
8537
- await mkdir(path10.dirname(outPath), { recursive: true });
8537
+ await mkdir(path11.dirname(outPath), { recursive: true });
8538
8538
  await writeFile(outPath, result.content, "utf-8");
8539
8539
  console.log(`Wrote ${result.fileExtension} report to ${outPath}`);
8540
8540
  }
@@ -8554,6 +8554,383 @@ async function reportCommand(runId, options = {}) {
8554
8554
  console.log(result.content);
8555
8555
  }
8556
8556
  }
8557
+ var DEFAULT_REDACT_KEYS2 = [
8558
+ "authorization",
8559
+ "cookie",
8560
+ "token",
8561
+ "apiKey",
8562
+ "password",
8563
+ "secret",
8564
+ "email"
8565
+ ];
8566
+ var SHARE_PROFILE_EXTRA_KEYS2 = [
8567
+ "userEmail",
8568
+ "customerEmail",
8569
+ "phone",
8570
+ "phoneNumber",
8571
+ "address",
8572
+ "ip",
8573
+ "ipAddress",
8574
+ "sessionId",
8575
+ "requestId",
8576
+ "correlationId",
8577
+ "decisionId",
8578
+ "groupId",
8579
+ "customerId",
8580
+ "userId",
8581
+ "accountId",
8582
+ "tenantId",
8583
+ "orgId",
8584
+ "organizationId",
8585
+ "traceId",
8586
+ "spanId",
8587
+ "parentSpanId"
8588
+ ];
8589
+ var STRICT_PROFILE_EXTRA_KEYS2 = [
8590
+ "prompt",
8591
+ "completion",
8592
+ "input",
8593
+ "output",
8594
+ "inputPreview",
8595
+ "outputPreview",
8596
+ "message",
8597
+ "messages",
8598
+ "transcript",
8599
+ "context",
8600
+ "document",
8601
+ "documents",
8602
+ "chunk",
8603
+ "chunks",
8604
+ "retrieval",
8605
+ "query"
8606
+ ];
8607
+ function isRecord13(value) {
8608
+ return typeof value === "object" && value !== null && !Array.isArray(value);
8609
+ }
8610
+ function toKey2(key) {
8611
+ return key.toLowerCase();
8612
+ }
8613
+ function stableHash2(value) {
8614
+ const hash = crypto.createHash("sha256").update(value, "utf8").digest("hex");
8615
+ return hash.slice(0, 8);
8616
+ }
8617
+ function stringifyScalar(value) {
8618
+ if (typeof value === "string") return value;
8619
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
8620
+ return String(value);
8621
+ }
8622
+ return void 0;
8623
+ }
8624
+ function patternDetector(options) {
8625
+ return {
8626
+ id: options.id,
8627
+ severity: options.severity ?? "warning",
8628
+ matchKind: "value",
8629
+ detect(input3) {
8630
+ if (typeof input3.value !== "string") return [];
8631
+ options.pattern.lastIndex = 0;
8632
+ return options.pattern.test(input3.value) ? [{ action: "replace", severity: options.severity ?? "warning", matchKind: "value" }] : [];
8633
+ }
8634
+ };
8635
+ }
8636
+ function digitsOnly(value) {
8637
+ return value.replace(/\D/g, "");
8638
+ }
8639
+ function passesLuhn(value) {
8640
+ const digits = digitsOnly(value);
8641
+ if (digits.length < 13 || digits.length > 19) return false;
8642
+ let sum = 0;
8643
+ let double = false;
8644
+ for (let i = digits.length - 1; i >= 0; i -= 1) {
8645
+ let digit = Number(digits[i]);
8646
+ if (double) {
8647
+ digit *= 2;
8648
+ if (digit > 9) digit -= 9;
8649
+ }
8650
+ sum += digit;
8651
+ double = !double;
8652
+ }
8653
+ return sum % 10 === 0;
8654
+ }
8655
+ var credentialDetectors = [
8656
+ patternDetector({
8657
+ id: "value.authorizationHeader",
8658
+ pattern: /^(?:basic|bearer|digest|apikey)\s+[a-z0-9._~+/=-]+$/i,
8659
+ severity: "error"
8660
+ }),
8661
+ patternDetector({
8662
+ id: "value.bearerToken",
8663
+ pattern: /\bbearer\s+[a-z0-9._~+/=-]{12,}\b/i,
8664
+ severity: "error"
8665
+ }),
8666
+ patternDetector({
8667
+ id: "value.cookie",
8668
+ pattern: /\b[a-z0-9_.-]+=[^;\s]+(?:;\s*[a-z0-9_.-]+=[^;\s]+)+/i,
8669
+ severity: "error"
8670
+ }),
8671
+ patternDetector({
8672
+ id: "value.jwt",
8673
+ pattern: /\beyJ[a-zA-Z0-9_-]{8,}\.[a-zA-Z0-9_-]{8,}\.[a-zA-Z0-9_-]{8,}\b/,
8674
+ severity: "error"
8675
+ }),
8676
+ patternDetector({
8677
+ id: "value.providerApiKey",
8678
+ pattern: /\b(?:sk-(?:proj-)?[a-zA-Z0-9_-]{16,}|sk-ant-[a-zA-Z0-9_-]{16,}|AIza[0-9A-Za-z_-]{20,})\b/,
8679
+ severity: "error"
8680
+ }),
8681
+ patternDetector({
8682
+ id: "value.githubToken",
8683
+ pattern: /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{20,}\b/,
8684
+ severity: "error"
8685
+ }),
8686
+ patternDetector({
8687
+ id: "value.awsAccessKey",
8688
+ pattern: /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/,
8689
+ severity: "error"
8690
+ }),
8691
+ patternDetector({
8692
+ id: "value.privateKey",
8693
+ pattern: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*-----END [A-Z ]*PRIVATE KEY-----/,
8694
+ severity: "error"
8695
+ }),
8696
+ {
8697
+ id: "value.creditCard",
8698
+ severity: "error",
8699
+ matchKind: "value",
8700
+ detect(input3) {
8701
+ if (typeof input3.value !== "string") return [];
8702
+ const candidatePattern = /(?:\d[ -]?){13,19}/g;
8703
+ for (const match of input3.value.matchAll(candidatePattern)) {
8704
+ const candidate = match[0] ?? "";
8705
+ if (passesLuhn(candidate)) {
8706
+ return [{ action: "replace", severity: "error", matchKind: "value" }];
8707
+ }
8708
+ }
8709
+ return [];
8710
+ }
8711
+ }
8712
+ ];
8713
+ var identifierDetectors = [
8714
+ patternDetector({
8715
+ id: "value.email",
8716
+ pattern: /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i
8717
+ }),
8718
+ patternDetector({
8719
+ id: "value.phone",
8720
+ pattern: /\b(?:\+\d{1,3}[\s.-]?)?(?:\(?\d{3}\)?[\s.-])\d{3}[\s.-]\d{4}\b/
8721
+ }),
8722
+ patternDetector({
8723
+ id: "value.ipv4",
8724
+ pattern: /\b(?:(?:25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|1?\d?\d)\b/
8725
+ }),
8726
+ patternDetector({
8727
+ id: "value.ipv6",
8728
+ pattern: /\b(?:[0-9a-f]{1,4}:){2,7}[0-9a-f]{1,4}\b/i
8729
+ })
8730
+ ];
8731
+ function builtInDetectorsForProfile(profile) {
8732
+ if (profile === "local") return credentialDetectors;
8733
+ return [...credentialDetectors, ...identifierDetectors];
8734
+ }
8735
+ function compileRules2(rules, extraKeys) {
8736
+ const out = /* @__PURE__ */ new Map();
8737
+ const set = (rule) => {
8738
+ const key = toKey2(rule.key);
8739
+ out.set(key, { ...rule, key });
8740
+ };
8741
+ for (const key of DEFAULT_REDACT_KEYS2) {
8742
+ set({ key, strategy: "full" });
8743
+ }
8744
+ for (const key of extraKeys ?? []) {
8745
+ if (typeof key === "string" && key.length > 0) {
8746
+ set({ key, strategy: "full" });
8747
+ }
8748
+ }
8749
+ for (const rule of rules ?? []) {
8750
+ if (typeof rule === "string") {
8751
+ set({ key: rule, strategy: "full" });
8752
+ continue;
8753
+ }
8754
+ if (rule.strategy === "full") set({ key: rule.key, strategy: "full" });
8755
+ if (rule.strategy === "hash") set({ key: rule.key, strategy: "hash" });
8756
+ if (rule.strategy === "prefix") {
8757
+ set({
8758
+ key: rule.key,
8759
+ strategy: "prefix",
8760
+ keep: typeof rule.keep === "number" ? rule.keep : 8
8761
+ });
8762
+ }
8763
+ }
8764
+ return [...out.values()];
8765
+ }
8766
+ function actionForRule(rule) {
8767
+ if (rule.strategy === "full") return "replace";
8768
+ return rule.strategy;
8769
+ }
8770
+ function applyRule(rule, value, replacement) {
8771
+ if (rule.strategy === "full") return replacement;
8772
+ const asString = stringifyScalar(value);
8773
+ if (rule.strategy === "prefix") {
8774
+ if (asString === void 0) return replacement;
8775
+ const keep = Math.max(0, Math.floor(rule.keep));
8776
+ return asString.length <= keep ? `${asString}\u2026` : `${asString.slice(0, keep)}\u2026`;
8777
+ }
8778
+ if (rule.strategy === "hash") {
8779
+ if (asString === void 0) return "[HASH:unknown]";
8780
+ return `[HASH:${stableHash2(asString)}]`;
8781
+ }
8782
+ return value;
8783
+ }
8784
+ function childPath(path13, key) {
8785
+ if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) {
8786
+ return path13 ? `${path13}.${key}` : key;
8787
+ }
8788
+ return `${path13 || "$"}[${JSON.stringify(key)}]`;
8789
+ }
8790
+ function indexPath(path13, index) {
8791
+ return `${path13 || "$"}[${index}]`;
8792
+ }
8793
+ function makeFinding(path13, detector, action, matchKind, severity = "warning", preview) {
8794
+ return preview === void 0 ? { path: path13, detector, action, severity, matchKind } : { path: path13, detector, action, severity, matchKind, preview };
8795
+ }
8796
+ function createRedactionProfile(profile = "local") {
8797
+ switch (profile) {
8798
+ case "local":
8799
+ return { profile: "local", extraKeys: [] };
8800
+ case "share":
8801
+ return {
8802
+ profile: "share",
8803
+ extraKeys: SHARE_PROFILE_EXTRA_KEYS2,
8804
+ maxMetadataValueLengthCap: 500,
8805
+ maxPreviewLengthCap: 200
8806
+ };
8807
+ case "strict":
8808
+ return {
8809
+ profile: "strict",
8810
+ extraKeys: [...SHARE_PROFILE_EXTRA_KEYS2, ...STRICT_PROFILE_EXTRA_KEYS2],
8811
+ maxMetadataValueLengthCap: 200,
8812
+ maxPreviewLengthCap: 80
8813
+ };
8814
+ }
8815
+ }
8816
+ var Redactor2 = class {
8817
+ #rules;
8818
+ #detectors;
8819
+ #profile;
8820
+ #replacement;
8821
+ #maxDepth;
8822
+ #collectFindings;
8823
+ constructor(options) {
8824
+ const resolved = createRedactionProfile(options?.profile ?? "local");
8825
+ this.#profile = resolved.profile;
8826
+ this.#rules = compileRules2(options?.rules, [
8827
+ ...resolved.extraKeys,
8828
+ ...options?.extraKeys ?? []
8829
+ ]);
8830
+ this.#detectors = [
8831
+ ...builtInDetectorsForProfile(this.#profile),
8832
+ ...options?.detectors ?? []
8833
+ ];
8834
+ this.#replacement = options?.replacement ?? "[REDACTED]";
8835
+ this.#maxDepth = options?.maxDepth ?? 32;
8836
+ this.#collectFindings = options?.collectFindings ?? true;
8837
+ }
8838
+ redactValue(key, value) {
8839
+ return this.#redactValue(value, key, key, 0, {
8840
+ findings: [],
8841
+ seen: /* @__PURE__ */ new WeakMap()
8842
+ });
8843
+ }
8844
+ redactRecord(record) {
8845
+ return this.redact(record).value;
8846
+ }
8847
+ redact(value) {
8848
+ const state = {
8849
+ findings: [],
8850
+ seen: /* @__PURE__ */ new WeakMap()
8851
+ };
8852
+ const redacted = this.#redactValue(value, void 0, "$", 0, state);
8853
+ return {
8854
+ value: redacted,
8855
+ findings: state.findings,
8856
+ redacted: state.findings.some((finding) => finding.action !== "keep"),
8857
+ profile: this.#profile
8858
+ };
8859
+ }
8860
+ #recordFinding(state, finding) {
8861
+ if (this.#collectFindings) state.findings.push(finding);
8862
+ }
8863
+ #redactValue(value, key, path13, depth, state) {
8864
+ if (depth > this.#maxDepth) {
8865
+ this.#recordFinding(
8866
+ state,
8867
+ makeFinding(path13, "structure.maxDepth", "truncate", "value", "warning")
8868
+ );
8869
+ return "[Truncated]";
8870
+ }
8871
+ if (key !== void 0) {
8872
+ const rule = this.#rules.find((candidate) => candidate.key === toKey2(key));
8873
+ if (rule) {
8874
+ this.#recordFinding(
8875
+ state,
8876
+ makeFinding(path13, `key.${rule.key}`, actionForRule(rule), "key", "warning")
8877
+ );
8878
+ return applyRule(rule, value, this.#replacement);
8879
+ }
8880
+ }
8881
+ for (const detector of this.#detectors) {
8882
+ const detections = detector.detect({ path: path13, key, value });
8883
+ for (const detection of detections) {
8884
+ const action = detection.action ?? "replace";
8885
+ this.#recordFinding(
8886
+ state,
8887
+ makeFinding(
8888
+ path13,
8889
+ detector.id,
8890
+ action,
8891
+ detection.matchKind ?? detector.matchKind ?? "custom",
8892
+ detection.severity ?? detector.severity ?? "warning",
8893
+ detection.preview
8894
+ )
8895
+ );
8896
+ if (action !== "keep") {
8897
+ return detection.replacement ?? this.#replacement;
8898
+ }
8899
+ }
8900
+ }
8901
+ if (Array.isArray(value)) {
8902
+ if (state.seen.has(value)) return state.seen.get(value);
8903
+ const out = [];
8904
+ state.seen.set(value, out);
8905
+ value.forEach((item, index) => {
8906
+ out[index] = this.#redactValue(item, void 0, indexPath(path13, index), depth + 1, state);
8907
+ });
8908
+ return out;
8909
+ }
8910
+ if (isRecord13(value)) {
8911
+ if (state.seen.has(value)) return state.seen.get(value);
8912
+ const out = {};
8913
+ state.seen.set(value, out);
8914
+ for (const [entryKey, entryValue] of Object.entries(value)) {
8915
+ out[entryKey] = this.#redactValue(
8916
+ entryValue,
8917
+ entryKey,
8918
+ childPath(path13 === "$" ? "" : path13, entryKey),
8919
+ depth + 1,
8920
+ state
8921
+ );
8922
+ }
8923
+ return out;
8924
+ }
8925
+ return value;
8926
+ }
8927
+ };
8928
+ function createRedactor(options) {
8929
+ return new Redactor2(options);
8930
+ }
8931
+ function redact(value, options) {
8932
+ return createRedactor(options).redact(value);
8933
+ }
8557
8934
  async function readStdin(stdin) {
8558
8935
  stdin.setEncoding("utf8");
8559
8936
  let content = "";
@@ -8582,8 +8959,119 @@ async function inputFromTarget(target, options, stdin) {
8582
8959
  return { type: "file", path: runPath };
8583
8960
  }
8584
8961
 
8585
- // packages/cli/src/explain.ts
8962
+ // packages/cli/src/redact.ts
8586
8963
  function parseRedactionProfile3(value) {
8964
+ if (value === void 0 || value === "local" || value === "share" || value === "strict") {
8965
+ return value ?? "share";
8966
+ }
8967
+ throw new Error(`Unsupported --profile "${value}". Use local, share, or strict.`);
8968
+ }
8969
+ function stable(value) {
8970
+ if (Array.isArray(value)) return value.map(stable);
8971
+ if (value === null || typeof value !== "object") return value;
8972
+ const record = value;
8973
+ return Object.fromEntries(
8974
+ Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable(record[key])])
8975
+ );
8976
+ }
8977
+ function isMissingFileError3(error) {
8978
+ return error !== null && typeof error === "object" && "code" in error && error.code === "ENOENT";
8979
+ }
8980
+ async function contentFromTarget(target, options, stdin) {
8981
+ if (target === "-") {
8982
+ return { content: await readStdin(stdin), source: "stdin" };
8983
+ }
8984
+ try {
8985
+ const stats2 = await stat(target);
8986
+ if (stats2.isDirectory()) {
8987
+ throw new Error("redact requires a trace file, JSON file, stdin, or run id.");
8988
+ }
8989
+ return { content: await readFile(target, "utf-8"), source: target };
8990
+ } catch (error) {
8991
+ if (!isMissingFileError3(error)) throw error;
8992
+ }
8993
+ const runPath = getTraceFilePath(target, resolveTraceDir({ dir: options.dir }));
8994
+ const stats = await stat(runPath);
8995
+ if (stats.isDirectory()) {
8996
+ throw new Error("redact requires a trace file, JSON file, stdin, or run id.");
8997
+ }
8998
+ return { content: await readFile(runPath, "utf-8"), source: runPath };
8999
+ }
9000
+ function redactJsonText(content, profile) {
9001
+ const parsed = JSON.parse(content);
9002
+ const result = redact(parsed, { profile });
9003
+ return {
9004
+ content: `${JSON.stringify(result.value, null, 2)}
9005
+ `,
9006
+ findings: result.findings
9007
+ };
9008
+ }
9009
+ function redactJsonlText(content, profile) {
9010
+ const lines = content.split(/\r?\n/);
9011
+ const out = [];
9012
+ const findings = [];
9013
+ for (let index = 0; index < lines.length; index += 1) {
9014
+ const line = lines[index] ?? "";
9015
+ if (line.trim() === "") continue;
9016
+ let parsed;
9017
+ try {
9018
+ parsed = JSON.parse(line);
9019
+ } catch {
9020
+ throw new Error(`Input is not valid JSON or JSONL at line ${index + 1}.`);
9021
+ }
9022
+ const result = redact(parsed, { profile });
9023
+ out.push(JSON.stringify(result.value));
9024
+ findings.push(
9025
+ ...result.findings.map((finding) => ({
9026
+ ...finding,
9027
+ path: `line:${index + 1}:${finding.path}`
9028
+ }))
9029
+ );
9030
+ }
9031
+ return {
9032
+ content: out.length === 0 ? "" : `${out.join("\n")}
9033
+ `,
9034
+ findings
9035
+ };
9036
+ }
9037
+ function redactDocument(content, profile) {
9038
+ try {
9039
+ return redactJsonText(content, profile);
9040
+ } catch {
9041
+ return redactJsonlText(content, profile);
9042
+ }
9043
+ }
9044
+ async function redactCommand(target, options = {}, stdin = process.stdin) {
9045
+ const profile = parseRedactionProfile3(options.profile);
9046
+ const source = await contentFromTarget(target, options, stdin);
9047
+ const redacted = redactDocument(source.content, profile);
9048
+ if (options.output !== void 0) {
9049
+ await writeFile(options.output, redacted.content, "utf-8");
9050
+ }
9051
+ if (options.json) {
9052
+ console.log(
9053
+ JSON.stringify(
9054
+ stable({
9055
+ ok: true,
9056
+ profile,
9057
+ source: source.source,
9058
+ output: options.output,
9059
+ findings: redacted.findings,
9060
+ content: options.output === void 0 ? redacted.content : void 0
9061
+ }),
9062
+ null,
9063
+ 2
9064
+ )
9065
+ );
9066
+ return;
9067
+ }
9068
+ if (options.output === void 0) {
9069
+ process.stdout.write(redacted.content);
9070
+ }
9071
+ }
9072
+
9073
+ // packages/cli/src/explain.ts
9074
+ function parseRedactionProfile4(value) {
8587
9075
  const profile = (value ?? "local").trim().toLowerCase();
8588
9076
  if (profile === "local" || profile === "share" || profile === "strict") {
8589
9077
  return profile;
@@ -8654,7 +9142,7 @@ async function explainCommand(target, options = {}, stdin = process.stdin) {
8654
9142
  }
8655
9143
  let redactionProfile;
8656
9144
  try {
8657
- redactionProfile = parseRedactionProfile3(options.redactionProfile);
9145
+ redactionProfile = parseRedactionProfile4(options.redactionProfile);
8658
9146
  } catch (error) {
8659
9147
  process.exitCode = 1;
8660
9148
  console.error(error instanceof Error ? error.message : String(error));
@@ -8852,7 +9340,7 @@ async function openCommand(input3, options = {}, stdin = process.stdin) {
8852
9340
  }
8853
9341
  }
8854
9342
  }
8855
- function isRecord13(value) {
9343
+ function isRecord14(value) {
8856
9344
  return typeof value === "object" && value !== null && !Array.isArray(value);
8857
9345
  }
8858
9346
  function parseTarget(value) {
@@ -8861,7 +9349,7 @@ function parseTarget(value) {
8861
9349
  throw new Error('Unsupported migration target. Use "--to 1.0".');
8862
9350
  }
8863
9351
  function formatOf(value) {
8864
- if (!isRecord13(value)) return "unknown";
9352
+ if (!isRecord14(value)) return "unknown";
8865
9353
  if (value.schemaVersion === "0.1") return "0.1";
8866
9354
  if (value.schemaVersion === "0.2") return "0.2";
8867
9355
  if (value.schemaVersion === "1.0") return "1.0";
@@ -8874,14 +9362,14 @@ function uniqueSorted(values) {
8874
9362
  return [...new Set(values)].sort();
8875
9363
  }
8876
9364
  function isWithinDirectory(child, parent) {
8877
- const relative = path10.relative(parent, child);
8878
- return relative === "" || !relative.startsWith("..") && !path10.isAbsolute(relative);
9365
+ const relative = path11.relative(parent, child);
9366
+ return relative === "" || !relative.startsWith("..") && !path11.isAbsolute(relative);
8879
9367
  }
8880
9368
  async function resolveOutputPath(inputPath, output2, force) {
8881
9369
  if (output2 === void 0 || output2.trim() === "") return void 0;
8882
- const inputAbs = path10.resolve(inputPath);
8883
- const outputAbs = path10.resolve(output2.trim());
8884
- const inputDir = path10.dirname(inputAbs);
9370
+ const inputAbs = path11.resolve(inputPath);
9371
+ const outputAbs = path11.resolve(output2.trim());
9372
+ const inputDir = path11.dirname(inputAbs);
8885
9373
  if (!isWithinDirectory(outputAbs, inputDir)) {
8886
9374
  throw new Error("Refusing to write migrated output outside the input directory.");
8887
9375
  }
@@ -9002,7 +9490,7 @@ async function migrateCommand(input3, options = {}) {
9002
9490
  process.exitCode = 1;
9003
9491
  return;
9004
9492
  }
9005
- const inputPath = path10.resolve(input3.trim());
9493
+ const inputPath = path11.resolve(input3.trim());
9006
9494
  const dryRun = options.dryRun === true;
9007
9495
  if (!dryRun && (options.output === void 0 || options.output.trim() === "")) {
9008
9496
  console.error("migrate requires --dry-run or --output <path>.");
@@ -9021,7 +9509,7 @@ async function migrateCommand(input3, options = {}) {
9021
9509
  );
9022
9510
  const result = await buildMigration(inputPath, outputPath);
9023
9511
  if (!dryRun && outputPath !== void 0) {
9024
- await mkdir(path10.dirname(outputPath), { recursive: true });
9512
+ await mkdir(path11.dirname(outputPath), { recursive: true });
9025
9513
  await writeFile(outputPath, result.content, "utf-8");
9026
9514
  }
9027
9515
  printSummary2(result, dryRun);
@@ -9299,7 +9787,7 @@ function stripPrefix(name, prefixes) {
9299
9787
  }
9300
9788
  return name;
9301
9789
  }
9302
- function eventEvidence(event, path12) {
9790
+ function eventEvidence(event, path13) {
9303
9791
  return {
9304
9792
  runId: event.runId,
9305
9793
  eventId: event.eventId,
@@ -9309,7 +9797,7 @@ function eventEvidence(event, path12) {
9309
9797
  kind: event.kind,
9310
9798
  name: event.name,
9311
9799
  status: event.status,
9312
- ...path12 ? { path: path12 } : {}
9800
+ ...path13 ? { path: path13 } : {}
9313
9801
  };
9314
9802
  }
9315
9803
  function runEvidence(run) {
@@ -9346,7 +9834,7 @@ function finishedEvents(context, kind) {
9346
9834
  (event) => (kind === void 0 || event.kind === kind) && event.status !== "running"
9347
9835
  );
9348
9836
  }
9349
- function isRecord14(value) {
9837
+ function isRecord15(value) {
9350
9838
  return typeof value === "object" && value !== null && !Array.isArray(value);
9351
9839
  }
9352
9840
  function eventMap(events) {
@@ -9372,9 +9860,9 @@ function eventEndMs(event) {
9372
9860
  function normalizedKey(value) {
9373
9861
  return value.toLowerCase().replace(/[^a-z0-9_]/g, "");
9374
9862
  }
9375
- function lastPathSegment(path12) {
9376
- const parts = path12.split(".");
9377
- return parts[parts.length - 1] ?? path12;
9863
+ function lastPathSegment(path13) {
9864
+ const parts = path13.split(".");
9865
+ return parts[parts.length - 1] ?? path13;
9378
9866
  }
9379
9867
  function valueType(value) {
9380
9868
  if (Array.isArray(value)) return "array";
@@ -9388,22 +9876,22 @@ function serializedByteLength(value) {
9388
9876
  return void 0;
9389
9877
  }
9390
9878
  }
9391
- function pushValueEntries(entries, event, value, path12, key, depth = 0) {
9392
- entries.push({ event, path: path12, key, value });
9879
+ function pushValueEntries(entries, event, value, path13, key, depth = 0) {
9880
+ entries.push({ event, path: path13, key, value });
9393
9881
  if (depth >= 8) return;
9394
9882
  if (Array.isArray(value)) {
9395
9883
  for (const [index, item] of value.entries()) {
9396
- pushValueEntries(entries, event, item, `${path12}.${index}`, String(index), depth + 1);
9884
+ pushValueEntries(entries, event, item, `${path13}.${index}`, String(index), depth + 1);
9397
9885
  }
9398
9886
  return;
9399
9887
  }
9400
- if (!isRecord14(value)) return;
9888
+ if (!isRecord15(value)) return;
9401
9889
  for (const nestedKey of Object.keys(value).sort((a, b) => a.localeCompare(b))) {
9402
9890
  pushValueEntries(
9403
9891
  entries,
9404
9892
  event,
9405
9893
  value[nestedKey],
9406
- `${path12}.${nestedKey}`,
9894
+ `${path13}.${nestedKey}`,
9407
9895
  nestedKey,
9408
9896
  depth + 1
9409
9897
  );
@@ -9484,9 +9972,9 @@ function eventDurationMs(event) {
9484
9972
  }
9485
9973
  function treeShape(nodes) {
9486
9974
  const lines = [];
9487
- const visit = (node, path12) => {
9488
- lines.push(`${path12}:${node.event.kind}:${node.event.name}:${node.event.status ?? "unknown"}`);
9489
- node.children.forEach((child, index) => visit(child, `${path12}.${index}`));
9975
+ const visit = (node, path13) => {
9976
+ lines.push(`${path13}:${node.event.kind}:${node.event.name}:${node.event.status ?? "unknown"}`);
9977
+ node.children.forEach((child, index) => visit(child, `${path13}.${index}`));
9490
9978
  };
9491
9979
  nodes.forEach((node, index) => visit(node, String(index)));
9492
9980
  return lines;
@@ -9535,9 +10023,9 @@ function retrievalShape(context) {
9535
10023
  function guardrailShape(context) {
9536
10024
  return guardrailEvents(context).map((event) => signalName(event, ["guardrailName", "guardrail", "guardrailId"], ["guardrail:"])).sort((a, b) => a.localeCompare(b));
9537
10025
  }
9538
- function firstEvidenceForKind(context, kind, path12) {
10026
+ function firstEvidenceForKind(context, kind, path13) {
9539
10027
  const event = context.events.find((candidate) => candidate.kind === kind);
9540
- return event ? [eventEvidence(event, path12)] : runEvidence(context.selectedRun);
10028
+ return event ? [eventEvidence(event, path13)] : runEvidence(context.selectedRun);
9541
10029
  }
9542
10030
  function baselineDiffFinding(message, evidence, expected, actual) {
9543
10031
  return failFinding("baseline.regression", message, evidence, expected, actual);
@@ -9785,13 +10273,13 @@ function createStructureCycleRule() {
9785
10273
  const seenCycles = /* @__PURE__ */ new Set();
9786
10274
  const findings = [];
9787
10275
  for (const event of [...context.events].sort((a, b) => a.eventId.localeCompare(b.eventId))) {
9788
- const path12 = [];
10276
+ const path13 = [];
9789
10277
  const seenAt = /* @__PURE__ */ new Map();
9790
10278
  let current = event;
9791
10279
  while (current) {
9792
10280
  const existing = seenAt.get(current.eventId);
9793
10281
  if (existing !== void 0) {
9794
- const cycle = path12.slice(existing);
10282
+ const cycle = path13.slice(existing);
9795
10283
  const key = cycle.map((item) => item.eventId).sort().join("\0");
9796
10284
  if (!seenCycles.has(key)) {
9797
10285
  seenCycles.add(key);
@@ -9807,8 +10295,8 @@ function createStructureCycleRule() {
9807
10295
  }
9808
10296
  break;
9809
10297
  }
9810
- seenAt.set(current.eventId, path12.length);
9811
- path12.push(current);
10298
+ seenAt.set(current.eventId, path13.length);
10299
+ path13.push(current);
9812
10300
  current = current.parentId ? byId.get(current.parentId) : void 0;
9813
10301
  }
9814
10302
  }
@@ -10082,7 +10570,7 @@ function createSafetyOversizedAttributeRule(options) {
10082
10570
  )
10083
10571
  );
10084
10572
  }
10085
- if (isRecord14(entry.value) && options.maxObjectKeys !== void 0 && Object.keys(entry.value).length > options.maxObjectKeys) {
10573
+ if (isRecord15(entry.value) && options.maxObjectKeys !== void 0 && Object.keys(entry.value).length > options.maxObjectKeys) {
10086
10574
  findings.push(
10087
10575
  failFinding(
10088
10576
  "safety.oversizedAttribute",
@@ -10333,7 +10821,7 @@ function asConfig(value) {
10333
10821
  }
10334
10822
  async function loadConfig(configPath) {
10335
10823
  if (configPath === void 0) return {};
10336
- const extension = path10.extname(configPath);
10824
+ const extension = path11.extname(configPath);
10337
10825
  if (TS_CONFIG_EXTENSIONS.has(extension)) {
10338
10826
  throw new Error(
10339
10827
  "TypeScript check configs require an explicit precompiled JavaScript config or future --config-loader support."
@@ -10342,7 +10830,7 @@ async function loadConfig(configPath) {
10342
10830
  if (!CONFIG_EXTENSIONS.has(extension)) {
10343
10831
  throw new Error("Unsupported check config extension. Use .json, .js, .mjs, or .cjs.");
10344
10832
  }
10345
- const absolute = path10.resolve(configPath);
10833
+ const absolute = path11.resolve(configPath);
10346
10834
  if (extension === ".json") {
10347
10835
  const raw = await readFile(absolute, "utf-8");
10348
10836
  return asConfig(JSON.parse(raw));
@@ -10359,12 +10847,12 @@ function normalizeConfig(config) {
10359
10847
  }
10360
10848
  function buildRules(config, options) {
10361
10849
  const diagnostics = [];
10362
- const checks = normalizeConfig(config);
10363
- const run = checks.run ?? {};
10364
- const tool = checks.tool ?? {};
10365
- const llm = checks.llm ?? {};
10366
- const structure = checks.structure ?? {};
10367
- const safety = checks.safety ?? {};
10850
+ const checks2 = normalizeConfig(config);
10851
+ const run = checks2.run ?? {};
10852
+ const tool = checks2.tool ?? {};
10853
+ const llm = checks2.llm ?? {};
10854
+ const structure = checks2.structure ?? {};
10855
+ const safety = checks2.safety ?? {};
10368
10856
  const maxDurationMs = parseNumber(options.maxDurationMs, "--max-duration-ms") ?? run.maxDurationMs;
10369
10857
  const maxTotalTokens = parseNumber(options.maxTotalTokens, "--max-total-tokens") ?? llm.maxTotalTokens;
10370
10858
  const rules = [
@@ -10412,7 +10900,7 @@ function buildRules(config, options) {
10412
10900
  rules.push(createSafetyOversizedAttributeRule(safety));
10413
10901
  }
10414
10902
  const select = [
10415
- ...asStringArray(checks.select) ?? [],
10903
+ ...asStringArray(checks2.select) ?? [],
10416
10904
  ...options.rule ?? []
10417
10905
  ];
10418
10906
  return {
@@ -10442,16 +10930,16 @@ function exitCodeFor(result) {
10442
10930
  }
10443
10931
  return 1;
10444
10932
  }
10445
- function stable(value) {
10446
- if (Array.isArray(value)) return value.map(stable);
10933
+ function stable2(value) {
10934
+ if (Array.isArray(value)) return value.map(stable2);
10447
10935
  if (value === null || typeof value !== "object") return value;
10448
10936
  const record = value;
10449
10937
  return Object.fromEntries(
10450
- Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable(record[key])])
10938
+ Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable2(record[key])])
10451
10939
  );
10452
10940
  }
10453
10941
  function printJson(result) {
10454
- console.log(JSON.stringify(stable(result), null, 2));
10942
+ console.log(JSON.stringify(stable2(result), null, 2));
10455
10943
  }
10456
10944
  function printHuman(result) {
10457
10945
  console.log(`Check status: ${result.status}`);
@@ -10460,12 +10948,12 @@ function printHuman(result) {
10460
10948
  console.log(
10461
10949
  `Summary: ${result.summary.failed} failed, ${result.summary.warnings} warning(s), ${result.summary.errors} error(s)`
10462
10950
  );
10463
- for (const diagnostic3 of result.diagnostics) {
10464
- console.log(`- ${diagnostic3.code}: ${diagnostic3.message}`);
10951
+ for (const diagnostic4 of result.diagnostics) {
10952
+ console.log(`- ${diagnostic4.code}: ${diagnostic4.message}`);
10465
10953
  }
10466
10954
  for (const finding of result.findings) {
10467
- const path12 = finding.evidence[0]?.path;
10468
- console.log(`- ${finding.ruleId}: ${finding.message}${path12 ? ` (${path12})` : ""}`);
10955
+ const path13 = finding.evidence[0]?.path;
10956
+ console.log(`- ${finding.ruleId}: ${finding.message}${path13 ? ` (${path13})` : ""}`);
10469
10957
  }
10470
10958
  }
10471
10959
  function readErrorResult(error) {
@@ -10519,42 +11007,996 @@ async function checkCommand(target, options = {}, stdin = process.stdin) {
10519
11007
  else printHuman(result);
10520
11008
  }
10521
11009
 
10522
- // packages/cli/src/safety.ts
10523
- var BEST_EFFORT_NOTE = "Best-effort local safety verification only; not a compliance, privacy, security, or regulatory certification.";
10524
- var DEFAULT_MAX_STRING_LENGTH = 16384;
10525
- var DEFAULT_MAX_ARRAY_LENGTH = 1e3;
10526
- var DEFAULT_MAX_OBJECT_KEYS = 200;
10527
- var DEFAULT_MAX_SERIALIZED_BYTES = 128 * 1024;
10528
- function parseLimit3(value, label) {
10529
- if (value === void 0) return void 0;
10530
- const parsed = Number(value);
10531
- if (!Number.isFinite(parsed) || parsed < 0) {
10532
- throw new Error(`${label} must be a non-negative number.`);
11010
+ // packages/eval/src/index.ts
11011
+ function isTraceReadResult(value) {
11012
+ return value !== null && typeof value === "object" && "runs" in value && "events" in value && "format" in value;
11013
+ }
11014
+ function traceInputFrom(value) {
11015
+ return { type: "file", path: value instanceof URL ? value.pathname : value };
11016
+ }
11017
+ async function resolveRead(input3, options) {
11018
+ try {
11019
+ if (typeof input3 === "string") {
11020
+ const read2 = await openTrace(traceInputFrom(input3), {
11021
+ ...options.format !== void 0 && options.format !== "auto" ? { format: options.format } : {}
11022
+ });
11023
+ return { read: read2, runId: options.runId, diagnostics: [] };
11024
+ }
11025
+ if (isTraceReadResult(input3)) {
11026
+ return { read: input3, runId: options.runId, diagnostics: [] };
11027
+ }
11028
+ if (isTraceReadResult(input3.trace)) {
11029
+ return { read: input3.trace, runId: options.runId ?? input3.runId, diagnostics: [] };
11030
+ }
11031
+ const format = options.format ?? input3.format;
11032
+ const read = await openTrace(traceInputFrom(input3.trace), {
11033
+ ...format !== void 0 && format !== "auto" ? { format } : {}
11034
+ });
11035
+ return { read, runId: options.runId ?? input3.runId, diagnostics: [] };
11036
+ } catch (error) {
11037
+ return { diagnostics: [diagnosticFromError(error)] };
10533
11038
  }
10534
- return parsed;
10535
11039
  }
10536
- function stable2(value) {
10537
- if (Array.isArray(value)) return value.map(stable2);
10538
- if (value === null || typeof value !== "object") return value;
10539
- const record = value;
10540
- return Object.fromEntries(
10541
- Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable2(record[key])])
10542
- );
11040
+ function diagnosticFromError(error) {
11041
+ if (error instanceof TraceReadError) {
11042
+ const code = error.code === "unsupported_format" ? "AI_EVAL_UNSUPPORTED_FORMAT" : error.code === "ambiguous_format" ? "AI_EVAL_AMBIGUOUS_FORMAT" : "AI_EVAL_TRACE_UNREADABLE";
11043
+ return { code, severity: "error", message: error.message };
11044
+ }
11045
+ return {
11046
+ code: "AI_EVAL_TRACE_UNREADABLE",
11047
+ severity: "error",
11048
+ message: error instanceof Error ? error.message : String(error)
11049
+ };
10543
11050
  }
10544
- function safetyDiagnostic(code, message, severity = "error") {
10545
- return { code, message, severity };
11051
+ function flatten2(nodes) {
11052
+ return nodes.flatMap((node) => [node, ...flatten2(node.children)]);
10546
11053
  }
10547
- function warningDiagnostics(warnings, unsupportedFields) {
10548
- return [
10549
- ...warnings.map(
10550
- (warning) => safetyDiagnostic(
10551
- warning.code,
10552
- warning.message,
10553
- warning.severity === "error" ? "error" : "warning"
10554
- )
10555
- ),
10556
- ...unsupportedFields.map(
10557
- (field) => safetyDiagnostic(
11054
+ function selectRun3(read, runId) {
11055
+ if (runId !== void 0) {
11056
+ const run = read.runs.find((candidate) => candidate.runId === runId);
11057
+ return run === void 0 ? {
11058
+ diagnostics: [
11059
+ {
11060
+ code: "AI_EVAL_RUN_SELECTION_REQUIRED",
11061
+ severity: "error",
11062
+ message: `Run not found: ${runId}.`
11063
+ }
11064
+ ]
11065
+ } : { run, diagnostics: [] };
11066
+ }
11067
+ if (read.runs.length === 1) {
11068
+ return { run: read.runs[0], diagnostics: [] };
11069
+ }
11070
+ if (read.runs.length === 0) {
11071
+ return {
11072
+ diagnostics: [
11073
+ {
11074
+ code: "AI_EVAL_RUN_SELECTION_REQUIRED",
11075
+ severity: "error",
11076
+ message: "No runs are available for eval."
11077
+ }
11078
+ ]
11079
+ };
11080
+ }
11081
+ return {
11082
+ diagnostics: [
11083
+ {
11084
+ code: "AI_EVAL_RUN_SELECTION_REQUIRED",
11085
+ severity: "error",
11086
+ message: "Multiple runs are available; select a run before eval."
11087
+ }
11088
+ ]
11089
+ };
11090
+ }
11091
+ function errorResult3(format, diagnostics, runId) {
11092
+ const errors = diagnostics.filter((item) => item.severity === "error").length;
11093
+ return {
11094
+ ok: false,
11095
+ status: "error",
11096
+ format,
11097
+ ...runId !== void 0 ? { runId } : {},
11098
+ summary: { passed: 0, failed: 0, warnings: 0, errors },
11099
+ findings: [],
11100
+ diagnostics: [...diagnostics]
11101
+ };
11102
+ }
11103
+ function normalizeFinding2(rule, finding) {
11104
+ return {
11105
+ ...finding,
11106
+ ruleId: finding.ruleId || rule.id,
11107
+ severity: finding.severity ?? rule.severity ?? "error",
11108
+ evidence: [...finding.evidence]
11109
+ };
11110
+ }
11111
+ function compareFindings2(a, b) {
11112
+ const aEvidence = a.evidence[0];
11113
+ const bEvidence = b.evidence[0];
11114
+ 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);
11115
+ }
11116
+ function summarize2(findings, ruleCount) {
11117
+ const failed = findings.filter((item) => item.status === "fail").length;
11118
+ const warnings = findings.filter((item) => item.status === "warning").length;
11119
+ const errors = findings.filter((item) => item.severity === "error").length;
11120
+ return {
11121
+ passed: Math.max(0, ruleCount - failed - warnings),
11122
+ failed,
11123
+ warnings,
11124
+ errors
11125
+ };
11126
+ }
11127
+ function defaultRules() {
11128
+ return [checks.requireSuccess()];
11129
+ }
11130
+ async function evalRun(input3, options = {}) {
11131
+ const resolved = await resolveRead(input3, options);
11132
+ if (resolved.read === void 0) {
11133
+ return errorResult3("unknown", resolved.diagnostics);
11134
+ }
11135
+ const selected = selectRun3(resolved.read, resolved.runId);
11136
+ if (selected.run === void 0) {
11137
+ return errorResult3(resolved.read.format, selected.diagnostics, resolved.runId);
11138
+ }
11139
+ const rules = [...options.checks ?? defaultRules()].sort(
11140
+ (a, b) => a.id.localeCompare(b.id)
11141
+ );
11142
+ const context = {
11143
+ format: resolved.read.format,
11144
+ run: selected.run,
11145
+ nodes: flatten2(selected.run.children),
11146
+ events: resolved.read.events.filter((event) => event.runId === selected.run?.runId)
11147
+ };
11148
+ const diagnostics = [];
11149
+ const findings = [];
11150
+ for (const rule of rules) {
11151
+ try {
11152
+ findings.push(...rule.evaluate(context).map((finding) => normalizeFinding2(rule, finding)));
11153
+ } catch (error) {
11154
+ diagnostics.push({
11155
+ code: "AI_EVAL_RULE_FAILED",
11156
+ severity: "error",
11157
+ ruleId: rule.id,
11158
+ message: `Eval rule ${rule.id} failed: ${error instanceof Error ? error.message : String(error)}`
11159
+ });
11160
+ }
11161
+ }
11162
+ if (diagnostics.length > 0) {
11163
+ return errorResult3(resolved.read.format, diagnostics, selected.run.runId);
11164
+ }
11165
+ const sortedFindings = findings.sort(compareFindings2);
11166
+ const summary = summarize2(sortedFindings, rules.length);
11167
+ const status = summary.failed > 0 ? "fail" : "pass";
11168
+ return {
11169
+ ok: status === "pass",
11170
+ status,
11171
+ format: resolved.read.format,
11172
+ runId: selected.run.runId,
11173
+ summary,
11174
+ findings: sortedFindings,
11175
+ diagnostics: []
11176
+ };
11177
+ }
11178
+ function evidenceForRun(run, path13) {
11179
+ return [{ runId: run.runId, ...path13 !== void 0 ? { path: path13 } : {} }];
11180
+ }
11181
+ function evidenceForEvent(event, path13) {
11182
+ return [
11183
+ {
11184
+ runId: event.runId,
11185
+ eventId: event.eventId,
11186
+ ...event.parentId !== void 0 ? { parentId: event.parentId } : {},
11187
+ kind: event.kind,
11188
+ name: event.name,
11189
+ ...path13 !== void 0 ? { path: path13 } : {}
11190
+ }
11191
+ ];
11192
+ }
11193
+ function fail(ruleId, message, evidence, expected, actual) {
11194
+ return {
11195
+ ruleId,
11196
+ status: "fail",
11197
+ severity: "error",
11198
+ message,
11199
+ ...expected !== void 0 ? { expected } : {},
11200
+ ...actual !== void 0 ? { actual } : {},
11201
+ evidence
11202
+ };
11203
+ }
11204
+ function nodeNames(nodes, kind) {
11205
+ return nodes.filter((node) => node.event.kind === kind).map((node) => node.event.name).sort((a, b) => a.localeCompare(b));
11206
+ }
11207
+ function hasAttribute(node, key) {
11208
+ return node.event.attributes !== void 0 && Object.prototype.hasOwnProperty.call(node.event.attributes, key);
11209
+ }
11210
+ function numericAttribute(node, keys) {
11211
+ const attrs = node.event.attributes;
11212
+ if (attrs === void 0) return void 0;
11213
+ for (const key of keys) {
11214
+ const value = attrs[key];
11215
+ if (typeof value === "number" && Number.isFinite(value)) return value;
11216
+ }
11217
+ return void 0;
11218
+ }
11219
+ function totalTokenCount(events) {
11220
+ return events.reduce((total, event) => {
11221
+ const usage = event.tokenUsage;
11222
+ if (usage?.total !== void 0) return total + usage.total;
11223
+ return total + (usage?.input ?? 0) + (usage?.output ?? 0);
11224
+ }, 0);
11225
+ }
11226
+ function createRule(id, category, evaluate) {
11227
+ return { id, category, severity: "error", evaluate };
11228
+ }
11229
+ var DEFAULT_ANSWER_KEYS = [
11230
+ "answer",
11231
+ "finalAnswer",
11232
+ "final",
11233
+ "response",
11234
+ "result",
11235
+ "output",
11236
+ "outputPreview",
11237
+ "completion",
11238
+ "text"
11239
+ ];
11240
+ var DEFAULT_CONTEXT_KEYS = [
11241
+ "context",
11242
+ "contexts",
11243
+ "document",
11244
+ "documents",
11245
+ "retrieved",
11246
+ "retrieval",
11247
+ "chunks",
11248
+ "chunk",
11249
+ "source",
11250
+ "sources",
11251
+ "sourceText"
11252
+ ];
11253
+ var DEFAULT_CITATION_KEYS = [
11254
+ "citation",
11255
+ "citations",
11256
+ "reference",
11257
+ "references",
11258
+ "sourceId",
11259
+ "sourceIds",
11260
+ "source_id",
11261
+ "source_ids"
11262
+ ];
11263
+ var DEFAULT_SOURCE_ID_KEYS = [
11264
+ ...DEFAULT_CITATION_KEYS,
11265
+ "id",
11266
+ "ids",
11267
+ "documentId",
11268
+ "documentIds",
11269
+ "docId",
11270
+ "docIds"
11271
+ ];
11272
+ var DEFAULT_BANNED_UNSUPPORTED_PHRASES = [
11273
+ "i don't have enough information",
11274
+ "i do not have enough information",
11275
+ "not enough context",
11276
+ "cannot determine from the context",
11277
+ "unable to determine from the provided context"
11278
+ ];
11279
+ var STOP_WORDS = /* @__PURE__ */ new Set([
11280
+ "a",
11281
+ "an",
11282
+ "and",
11283
+ "are",
11284
+ "but",
11285
+ "for",
11286
+ "from",
11287
+ "have",
11288
+ "into",
11289
+ "not",
11290
+ "that",
11291
+ "the",
11292
+ "their",
11293
+ "this",
11294
+ "was",
11295
+ "were",
11296
+ "with",
11297
+ "you",
11298
+ "your"
11299
+ ]);
11300
+ function normalizeKey(key) {
11301
+ return key.toLowerCase().replace(/[^a-z0-9]/g, "");
11302
+ }
11303
+ function keySet(keys) {
11304
+ return new Set(keys.map(normalizeKey));
11305
+ }
11306
+ function valuesAsStrings(value, depth = 0) {
11307
+ if (depth > 5) return [];
11308
+ if (typeof value === "string") {
11309
+ const trimmed = value.trim();
11310
+ return trimmed.length > 0 ? [trimmed] : [];
11311
+ }
11312
+ if (typeof value === "number" || typeof value === "boolean") return [String(value)];
11313
+ if (Array.isArray(value)) {
11314
+ return value.flatMap((item) => valuesAsStrings(item, depth + 1));
11315
+ }
11316
+ if (value !== null && typeof value === "object") {
11317
+ return Object.values(value).flatMap(
11318
+ (item) => valuesAsStrings(item, depth + 1)
11319
+ );
11320
+ }
11321
+ return [];
11322
+ }
11323
+ function collectTextFields(nodes, keys, preferredKinds = []) {
11324
+ const wanted = keySet(keys);
11325
+ const preferred = new Set(preferredKinds);
11326
+ const orderedNodes = [...nodes].sort((a, b) => {
11327
+ const aPreferred = preferred.has(a.event.kind) ? 0 : 1;
11328
+ const bPreferred = preferred.has(b.event.kind) ? 0 : 1;
11329
+ return aPreferred - bPreferred || a.event.eventId.localeCompare(b.event.eventId);
11330
+ });
11331
+ const fields = [];
11332
+ for (const node of orderedNodes) {
11333
+ const attrs = node.event.attributes;
11334
+ if (attrs === void 0) continue;
11335
+ for (const [key, value] of Object.entries(attrs)) {
11336
+ if (!wanted.has(normalizeKey(key))) continue;
11337
+ for (const text of valuesAsStrings(value)) {
11338
+ fields.push({ text, node, path: `attributes.${key}` });
11339
+ }
11340
+ }
11341
+ }
11342
+ return fields;
11343
+ }
11344
+ function tokenize(text) {
11345
+ 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));
11346
+ }
11347
+ function firstEvidence(fields, run, path13) {
11348
+ const first = fields[0];
11349
+ return first === void 0 ? evidenceForRun(run, path13) : evidenceForEvent(first.node.event, first.path);
11350
+ }
11351
+ function collectSourceIds(nodes, keys) {
11352
+ const wanted = keySet(keys);
11353
+ const ids = /* @__PURE__ */ new Set();
11354
+ for (const node of nodes) {
11355
+ const attrs = node.event.attributes;
11356
+ if (attrs === void 0) continue;
11357
+ for (const [key, value] of Object.entries(attrs)) {
11358
+ if (!wanted.has(normalizeKey(key))) continue;
11359
+ for (const candidate of valuesAsStrings(value)) {
11360
+ const trimmed = candidate.trim();
11361
+ if (trimmed.length > 0 && trimmed.length <= 128) ids.add(trimmed);
11362
+ }
11363
+ }
11364
+ }
11365
+ return [...ids].sort((a, b) => a.localeCompare(b));
11366
+ }
11367
+ function citationCount(answer, citationFields) {
11368
+ const inline = answer.match(/\[[^\]\n]{1,40}\]|\([A-Za-z][A-Za-z0-9_-]{0,39}\)/g)?.length ?? 0;
11369
+ return inline + citationFields.length;
11370
+ }
11371
+ function quotedSnippets(text, minLength) {
11372
+ const snippets = /* @__PURE__ */ new Set();
11373
+ const pattern = /"([^"\n]+)"|'([^'\n]+)'|“([^”\n]+)”/g;
11374
+ for (const match of text.matchAll(pattern)) {
11375
+ const snippet = (match[1] ?? match[2] ?? match[3] ?? "").trim();
11376
+ if (snippet.length >= minLength) snippets.add(snippet);
11377
+ }
11378
+ return [...snippets].sort((a, b) => a.localeCompare(b));
11379
+ }
11380
+ function wordCount(text) {
11381
+ return tokenize(text).length;
11382
+ }
11383
+ var checks = {
11384
+ requireSuccess() {
11385
+ return createRule(
11386
+ "eval.requireSuccess",
11387
+ "run",
11388
+ (context) => context.run.status === "ok" ? [] : [
11389
+ fail(
11390
+ "eval.requireSuccess",
11391
+ "Run did not complete successfully.",
11392
+ evidenceForRun(context.run, "status"),
11393
+ "ok",
11394
+ context.run.status ?? "unknown"
11395
+ )
11396
+ ]
11397
+ );
11398
+ },
11399
+ requiredTools(required) {
11400
+ const expected = [...required].sort((a, b) => a.localeCompare(b));
11401
+ return createRule("eval.requiredTools", "tool", (context) => {
11402
+ const tools = new Set(nodeNames(context.nodes, "TOOL"));
11403
+ return expected.filter((name) => !tools.has(name)).map(
11404
+ (name) => fail(
11405
+ "eval.requiredTools",
11406
+ `Required tool ${name} did not appear.`,
11407
+ evidenceForRun(context.run, "children"),
11408
+ name,
11409
+ [...tools].sort((a, b) => a.localeCompare(b))
11410
+ )
11411
+ );
11412
+ });
11413
+ },
11414
+ forbiddenTools(forbidden) {
11415
+ const blocked = [...forbidden].sort((a, b) => a.localeCompare(b));
11416
+ return createRule(
11417
+ "eval.forbiddenTools",
11418
+ "tool",
11419
+ (context) => context.nodes.filter((node) => node.event.kind === "TOOL" && blocked.includes(node.event.name)).map(
11420
+ (node) => fail(
11421
+ "eval.forbiddenTools",
11422
+ `Forbidden tool ${node.event.name} appeared.`,
11423
+ evidenceForEvent(node.event, "name"),
11424
+ "tool absent",
11425
+ node.event.name
11426
+ )
11427
+ )
11428
+ );
11429
+ },
11430
+ maxDurationMs(maxDurationMs) {
11431
+ return createRule(
11432
+ "eval.maxDurationMs",
11433
+ "run",
11434
+ (context) => context.run.durationMs !== void 0 && context.run.durationMs > maxDurationMs ? [
11435
+ fail(
11436
+ "eval.maxDurationMs",
11437
+ `Run duration exceeded ${maxDurationMs}ms.`,
11438
+ evidenceForRun(context.run, "durationMs"),
11439
+ { maxDurationMs },
11440
+ context.run.durationMs
11441
+ )
11442
+ ] : []
11443
+ );
11444
+ },
11445
+ maxDepth(maxDepth) {
11446
+ return createRule("eval.maxDepth", "structure", (context) => {
11447
+ const deepest = context.nodes.reduce(
11448
+ (current, node) => current === void 0 || node.depth > current.depth ? node : current,
11449
+ void 0
11450
+ );
11451
+ return deepest !== void 0 && deepest.depth > maxDepth ? [
11452
+ fail(
11453
+ "eval.maxDepth",
11454
+ `Run tree depth exceeded ${maxDepth}.`,
11455
+ evidenceForEvent(deepest.event, "depth"),
11456
+ { maxDepth },
11457
+ deepest.depth
11458
+ )
11459
+ ] : [];
11460
+ });
11461
+ },
11462
+ maxRetries(maxRetries) {
11463
+ return createRule(
11464
+ "eval.maxRetries",
11465
+ "structure",
11466
+ (context) => context.nodes.flatMap((node) => {
11467
+ const retries = numericAttribute(node, ["retryCount", "retries", "attempt"]);
11468
+ return retries !== void 0 && retries > maxRetries ? [
11469
+ fail(
11470
+ "eval.maxRetries",
11471
+ `Retry count exceeded ${maxRetries}.`,
11472
+ evidenceForEvent(node.event, "attributes.retryCount"),
11473
+ { maxRetries },
11474
+ retries
11475
+ )
11476
+ ] : [];
11477
+ })
11478
+ );
11479
+ },
11480
+ maxTotalTokens(maxTotalTokens) {
11481
+ return createRule("eval.maxTotalTokens", "llm", (context) => {
11482
+ const total = totalTokenCount(context.events);
11483
+ return total > maxTotalTokens ? [
11484
+ fail(
11485
+ "eval.maxTotalTokens",
11486
+ `Total token usage exceeded ${maxTotalTokens}.`,
11487
+ evidenceForRun(context.run, "tokenUsage.total"),
11488
+ { maxTotalTokens },
11489
+ total
11490
+ )
11491
+ ] : [];
11492
+ });
11493
+ },
11494
+ noFailedSteps() {
11495
+ return createRule(
11496
+ "eval.noFailedSteps",
11497
+ "run",
11498
+ (context) => context.nodes.filter((node) => node.event.status === "error" || node.event.kind === "ERROR").map(
11499
+ (node) => fail(
11500
+ "eval.noFailedSteps",
11501
+ "Run contains a failed step or error node.",
11502
+ evidenceForEvent(node.event, "status"),
11503
+ "no failed nodes",
11504
+ node.event.status ?? node.event.kind
11505
+ )
11506
+ )
11507
+ );
11508
+ },
11509
+ requiredRetrievalBeforeGeneration() {
11510
+ return createRule("eval.requiredRetrievalBeforeGeneration", "retrieval", (context) => {
11511
+ const firstLlmIndex = context.nodes.findIndex((node) => node.event.kind === "LLM");
11512
+ if (firstLlmIndex === -1) return [];
11513
+ const retrievalIndex = context.nodes.findIndex(
11514
+ (node, index) => index < firstLlmIndex && node.event.kind === "RETRIEVER"
11515
+ );
11516
+ return retrievalIndex === -1 ? [
11517
+ fail(
11518
+ "eval.requiredRetrievalBeforeGeneration",
11519
+ "No retrieval step appeared before the first LLM generation.",
11520
+ evidenceForEvent(context.nodes[firstLlmIndex].event, "kind"),
11521
+ "RETRIEVER before LLM",
11522
+ "LLM before RETRIEVER"
11523
+ )
11524
+ ] : [];
11525
+ });
11526
+ },
11527
+ requiredDecisionMetadata(keys) {
11528
+ const required = [...keys].sort((a, b) => a.localeCompare(b));
11529
+ return createRule("eval.requiredDecisionMetadata", "structure", (context) => {
11530
+ const decisions = context.nodes.filter((node) => node.event.kind === "DECISION");
11531
+ if (decisions.length === 0) {
11532
+ return [
11533
+ fail(
11534
+ "eval.requiredDecisionMetadata",
11535
+ "No decision node is available for required metadata.",
11536
+ evidenceForRun(context.run, "children"),
11537
+ { decisionMetadata: required },
11538
+ "no decision nodes"
11539
+ )
11540
+ ];
11541
+ }
11542
+ return decisions.flatMap(
11543
+ (node) => required.filter((key) => !hasAttribute(node, key)).map(
11544
+ (key) => fail(
11545
+ "eval.requiredDecisionMetadata",
11546
+ `Decision metadata ${key} is missing.`,
11547
+ evidenceForEvent(node.event, `attributes.${key}`),
11548
+ key,
11549
+ "missing"
11550
+ )
11551
+ )
11552
+ );
11553
+ });
11554
+ },
11555
+ contextOverlap(options = {}) {
11556
+ const minOverlap = options.minOverlap ?? 0.1;
11557
+ const minSharedTerms = options.minSharedTerms ?? 1;
11558
+ const answerKeys = options.answerKeys ?? DEFAULT_ANSWER_KEYS;
11559
+ const contextKeys = options.contextKeys ?? DEFAULT_CONTEXT_KEYS;
11560
+ return createRule("eval.contextOverlap", "retrieval", (context) => {
11561
+ const answers = collectTextFields(context.nodes, answerKeys, ["RESULT", "LLM", "AGENT"]);
11562
+ const contexts = collectTextFields(context.nodes, contextKeys, ["RETRIEVER", "TOOL"]);
11563
+ if (answers.length === 0 || contexts.length === 0) {
11564
+ return [
11565
+ fail(
11566
+ "eval.contextOverlap",
11567
+ "Answer and context text are required for overlap evaluation.",
11568
+ firstEvidence(answers.length > 0 ? answers : contexts, context.run, "children"),
11569
+ { answer: "present", context: "present" },
11570
+ { answerFields: answers.length, contextFields: contexts.length }
11571
+ )
11572
+ ];
11573
+ }
11574
+ const answerTerms = new Set(tokenize(answers.map((field) => field.text).join(" ")));
11575
+ const contextTerms = new Set(tokenize(contexts.map((field) => field.text).join(" ")));
11576
+ const sharedTerms = [...answerTerms].filter((term) => contextTerms.has(term)).length;
11577
+ const overlap = answerTerms.size === 0 ? 0 : sharedTerms / answerTerms.size;
11578
+ return sharedTerms < minSharedTerms || overlap < minOverlap ? [
11579
+ fail(
11580
+ "eval.contextOverlap",
11581
+ "Answer text did not sufficiently overlap retrieved context.",
11582
+ firstEvidence(answers, context.run, "attributes.answer"),
11583
+ { minOverlap, minSharedTerms },
11584
+ {
11585
+ answerTerms: answerTerms.size,
11586
+ contextTerms: contextTerms.size,
11587
+ sharedTerms,
11588
+ overlap: Number(overlap.toFixed(4))
11589
+ }
11590
+ )
11591
+ ] : [];
11592
+ });
11593
+ },
11594
+ quoteOverlap(options = {}) {
11595
+ const answerKeys = options.answerKeys ?? DEFAULT_ANSWER_KEYS;
11596
+ const contextKeys = options.contextKeys ?? DEFAULT_CONTEXT_KEYS;
11597
+ const minQuoteLength = options.minQuoteLength ?? 6;
11598
+ const requireQuote = options.requireQuote ?? true;
11599
+ return createRule("eval.quoteOverlap", "retrieval", (context) => {
11600
+ const answers = collectTextFields(context.nodes, answerKeys, ["RESULT", "LLM", "AGENT"]);
11601
+ const contexts = collectTextFields(context.nodes, contextKeys, ["RETRIEVER", "TOOL"]);
11602
+ const answerText = answers.map((field) => field.text).join(" ");
11603
+ const contextText = contexts.map((field) => field.text).join(" ").toLowerCase();
11604
+ const quotes = quotedSnippets(answerText, minQuoteLength);
11605
+ if (quotes.length === 0) {
11606
+ return requireQuote ? [
11607
+ fail(
11608
+ "eval.quoteOverlap",
11609
+ "Answer did not contain a quote for overlap evaluation.",
11610
+ firstEvidence(answers, context.run, "attributes.answer"),
11611
+ { quotedText: "present" },
11612
+ { quoteCount: 0 }
11613
+ )
11614
+ ] : [];
11615
+ }
11616
+ const missing = quotes.filter((quote) => !contextText.includes(quote.toLowerCase()));
11617
+ return missing.length > 0 ? [
11618
+ fail(
11619
+ "eval.quoteOverlap",
11620
+ "Quoted answer text did not appear in retrieved context.",
11621
+ firstEvidence(answers, context.run, "attributes.answer"),
11622
+ { allQuotesInContext: true },
11623
+ { quoteCount: quotes.length, missingQuotes: missing.length }
11624
+ )
11625
+ ] : [];
11626
+ });
11627
+ },
11628
+ citationPresence(options = {}) {
11629
+ const answerKeys = options.answerKeys ?? DEFAULT_ANSWER_KEYS;
11630
+ const citationKeys = options.citationKeys ?? DEFAULT_CITATION_KEYS;
11631
+ return createRule("eval.citationPresence", "retrieval", (context) => {
11632
+ const answers = collectTextFields(context.nodes, answerKeys, ["RESULT", "LLM", "AGENT"]);
11633
+ const citations = collectTextFields(context.nodes, citationKeys);
11634
+ const count = citationCount(answers.map((field) => field.text).join(" "), citations);
11635
+ return count === 0 ? [
11636
+ fail(
11637
+ "eval.citationPresence",
11638
+ "Answer did not include citations or source references.",
11639
+ firstEvidence(answers, context.run, "attributes.answer"),
11640
+ { citationCount: ">= 1" },
11641
+ { citationCount: 0 }
11642
+ )
11643
+ ] : [];
11644
+ });
11645
+ },
11646
+ requiredSourceIds(requiredIds, options = {}) {
11647
+ const expected = [...requiredIds].sort((a, b) => a.localeCompare(b));
11648
+ const sourceIdKeys = options.sourceIdKeys ?? DEFAULT_SOURCE_ID_KEYS;
11649
+ return createRule("eval.requiredSourceIds", "retrieval", (context) => {
11650
+ const available = collectSourceIds(context.nodes, sourceIdKeys);
11651
+ const availableSet = new Set(available);
11652
+ const missing = expected.filter((id) => !availableSet.has(id));
11653
+ return missing.length > 0 ? [
11654
+ fail(
11655
+ "eval.requiredSourceIds",
11656
+ "Required source IDs were not present in trace context or citations.",
11657
+ evidenceForRun(context.run, "children"),
11658
+ { sourceIds: expected },
11659
+ { missingSourceIds: missing, availableSourceIds: available.slice(0, 20) }
11660
+ )
11661
+ ] : [];
11662
+ });
11663
+ },
11664
+ answerLengthBounds(options) {
11665
+ const answerKeys = options.answerKeys ?? DEFAULT_ANSWER_KEYS;
11666
+ return createRule("eval.answerLengthBounds", "llm", (context) => {
11667
+ const answers = collectTextFields(context.nodes, answerKeys, ["RESULT", "LLM", "AGENT"]);
11668
+ const answer = answers.map((field) => field.text).join(" ").trim();
11669
+ const characters = answer.length;
11670
+ const words = wordCount(answer);
11671
+ const tooShort = options.minCharacters !== void 0 && characters < options.minCharacters || options.minWords !== void 0 && words < options.minWords;
11672
+ const tooLong = options.maxCharacters !== void 0 && characters > options.maxCharacters || options.maxWords !== void 0 && words > options.maxWords;
11673
+ return answer.length === 0 || tooShort || tooLong ? [
11674
+ fail(
11675
+ "eval.answerLengthBounds",
11676
+ "Answer length fell outside required bounds.",
11677
+ firstEvidence(answers, context.run, "attributes.answer"),
11678
+ {
11679
+ minCharacters: options.minCharacters,
11680
+ maxCharacters: options.maxCharacters,
11681
+ minWords: options.minWords,
11682
+ maxWords: options.maxWords
11683
+ },
11684
+ { characters, words }
11685
+ )
11686
+ ] : [];
11687
+ });
11688
+ },
11689
+ bannedUnsupportedPhrases(phrases = DEFAULT_BANNED_UNSUPPORTED_PHRASES, options = {}) {
11690
+ const answerKeys = options.answerKeys ?? DEFAULT_ANSWER_KEYS;
11691
+ const banned = [...phrases].map((phrase) => phrase.toLowerCase()).sort();
11692
+ return createRule("eval.bannedUnsupportedPhrases", "safety", (context) => {
11693
+ const answers = collectTextFields(context.nodes, answerKeys, ["RESULT", "LLM", "AGENT"]);
11694
+ const answer = answers.map((field) => field.text).join(" ").toLowerCase();
11695
+ const matches = banned.filter((phrase) => answer.includes(phrase));
11696
+ return matches.length > 0 ? [
11697
+ fail(
11698
+ "eval.bannedUnsupportedPhrases",
11699
+ "Answer contained banned unsupported-answer phrasing.",
11700
+ firstEvidence(answers, context.run, "attributes.answer"),
11701
+ { bannedPhraseCount: banned.length },
11702
+ { matchedPhraseCount: matches.length }
11703
+ )
11704
+ ] : [];
11705
+ });
11706
+ }
11707
+ };
11708
+ function renderEvalMarkdown(result) {
11709
+ const lines = [
11710
+ `# AgentInspect Eval`,
11711
+ "",
11712
+ `Status: ${result.status}`,
11713
+ `Format: ${result.format}`,
11714
+ ...result.runId !== void 0 ? [`Run: ${result.runId}`] : [],
11715
+ `Summary: ${result.summary.passed} passed, ${result.summary.failed} failed, ${result.summary.warnings} warnings, ${result.summary.errors} errors`
11716
+ ];
11717
+ if (result.diagnostics.length > 0) {
11718
+ lines.push("", "## Diagnostics");
11719
+ for (const diagnostic4 of result.diagnostics) {
11720
+ lines.push(`- ${diagnostic4.code}: ${diagnostic4.message}`);
11721
+ }
11722
+ }
11723
+ if (result.findings.length > 0) {
11724
+ lines.push("", "## Findings");
11725
+ for (const finding of result.findings) {
11726
+ const path13 = finding.evidence[0]?.path;
11727
+ lines.push(`- ${finding.ruleId}: ${finding.message}${path13 ? ` (${path13})` : ""}`);
11728
+ }
11729
+ }
11730
+ return `${lines.join("\n")}
11731
+ `;
11732
+ }
11733
+
11734
+ // packages/cli/src/eval.ts
11735
+ var CONFIG_EXTENSIONS2 = /* @__PURE__ */ new Set([".json", ".js", ".mjs", ".cjs"]);
11736
+ var TS_CONFIG_EXTENSIONS2 = /* @__PURE__ */ new Set([".ts", ".mts", ".cts"]);
11737
+ function diagnostic3(code, message, severity = "error") {
11738
+ return { code, message, severity };
11739
+ }
11740
+ function errorResult4(code, message, format = "unknown") {
11741
+ return {
11742
+ ok: false,
11743
+ status: "error",
11744
+ format,
11745
+ summary: { passed: 0, failed: 0, warnings: 0, errors: 1 },
11746
+ findings: [],
11747
+ diagnostics: [diagnostic3(code, message)]
11748
+ };
11749
+ }
11750
+ function parseNumber2(value, label) {
11751
+ if (value === void 0) return void 0;
11752
+ const parsed = Number(value);
11753
+ if (!Number.isFinite(parsed) || parsed < 0) {
11754
+ throw new Error(`${label} must be a non-negative number.`);
11755
+ }
11756
+ return parsed;
11757
+ }
11758
+ function asStringArray2(value, label) {
11759
+ if (value === void 0) return void 0;
11760
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
11761
+ throw new Error(`${label} must be an array of strings.`);
11762
+ }
11763
+ return value;
11764
+ }
11765
+ function asConfig2(value) {
11766
+ if (value === void 0 || value === null) return {};
11767
+ if (typeof value !== "object" || Array.isArray(value)) {
11768
+ throw new Error("Config must export an object.");
11769
+ }
11770
+ return value;
11771
+ }
11772
+ async function loadConfig2(configPath) {
11773
+ if (configPath === void 0) return {};
11774
+ const extension = path11.extname(configPath);
11775
+ if (TS_CONFIG_EXTENSIONS2.has(extension)) {
11776
+ throw new Error(
11777
+ "TypeScript eval configs require an explicit precompiled JavaScript config or future --config-loader support."
11778
+ );
11779
+ }
11780
+ if (!CONFIG_EXTENSIONS2.has(extension)) {
11781
+ throw new Error("Unsupported eval config extension. Use .json, .js, .mjs, or .cjs.");
11782
+ }
11783
+ const absolute = path11.resolve(configPath);
11784
+ if (extension === ".json") {
11785
+ const raw = await readFile(absolute, "utf-8");
11786
+ return asConfig2(JSON.parse(raw));
11787
+ }
11788
+ const mod = await import(pathToFileURL(absolute).href);
11789
+ return asConfig2("default" in mod ? mod.default : mod);
11790
+ }
11791
+ function normalizeConfig2(config) {
11792
+ if (config.eval === void 0) return {};
11793
+ if (typeof config.eval !== "object" || Array.isArray(config.eval)) {
11794
+ throw new Error("eval config must be an object.");
11795
+ }
11796
+ return config.eval;
11797
+ }
11798
+ function maybeAdd(rules, value, makeRule) {
11799
+ if (value !== void 0) rules.push(makeRule(value));
11800
+ }
11801
+ function optionObject(value) {
11802
+ if (value === void 0 || value === false) return void 0;
11803
+ if (value === true) return {};
11804
+ if (typeof value !== "object" || Array.isArray(value)) {
11805
+ throw new Error("Eval heuristic config must be a boolean or object.");
11806
+ }
11807
+ return value;
11808
+ }
11809
+ function sourceIdsFromConfig(value) {
11810
+ if (value === void 0) return void 0;
11811
+ if (Array.isArray(value)) return { ids: asStringArray2(value, "eval.requiredSourceIds") ?? [] };
11812
+ if (typeof value !== "object" || Array.isArray(value)) {
11813
+ throw new Error("eval.requiredSourceIds must be an array or object.");
11814
+ }
11815
+ const ids = asStringArray2(value.ids, "eval.requiredSourceIds.ids") ?? [];
11816
+ return { ids, options: { sourceIdKeys: value.sourceIdKeys } };
11817
+ }
11818
+ function buildRules2(config, options) {
11819
+ const evalConfig = normalizeConfig2(config);
11820
+ const rules = [];
11821
+ if (evalConfig.requireSuccess || options.requireSuccess) rules.push(checks.requireSuccess());
11822
+ const requiredTools = [
11823
+ ...asStringArray2(evalConfig.requiredTools, "eval.requiredTools") ?? [],
11824
+ ...options.requiredTool ?? []
11825
+ ];
11826
+ if (requiredTools.length > 0) rules.push(checks.requiredTools(requiredTools));
11827
+ const forbiddenTools = [
11828
+ ...asStringArray2(evalConfig.forbiddenTools, "eval.forbiddenTools") ?? [],
11829
+ ...options.forbidTool ?? [],
11830
+ ...options.forbiddenTool ?? []
11831
+ ];
11832
+ if (forbiddenTools.length > 0) rules.push(checks.forbiddenTools(forbiddenTools));
11833
+ const maxDurationMs = parseNumber2(options.maxDurationMs, "--max-duration-ms") ?? evalConfig.maxDurationMs;
11834
+ maybeAdd(rules, maxDurationMs, checks.maxDurationMs);
11835
+ const maxDepth = parseNumber2(options.maxDepth, "--max-depth") ?? evalConfig.maxDepth;
11836
+ maybeAdd(rules, maxDepth, checks.maxDepth);
11837
+ const maxRetries = parseNumber2(options.maxRetries, "--max-retries") ?? evalConfig.maxRetries;
11838
+ maybeAdd(rules, maxRetries, checks.maxRetries);
11839
+ const maxTotalTokens = parseNumber2(options.maxTotalTokens, "--max-total-tokens") ?? evalConfig.maxTotalTokens;
11840
+ maybeAdd(rules, maxTotalTokens, checks.maxTotalTokens);
11841
+ if (evalConfig.requiredRetrievalBeforeGeneration || options.requireRetrievalBeforeGeneration) {
11842
+ rules.push(checks.requiredRetrievalBeforeGeneration());
11843
+ }
11844
+ const requiredDecisionMetadata = [
11845
+ ...asStringArray2(
11846
+ evalConfig.requiredDecisionMetadata,
11847
+ "eval.requiredDecisionMetadata"
11848
+ ) ?? [],
11849
+ ...options.requiredDecisionMetadata ?? []
11850
+ ];
11851
+ if (requiredDecisionMetadata.length > 0) {
11852
+ rules.push(checks.requiredDecisionMetadata(requiredDecisionMetadata));
11853
+ }
11854
+ const contextOverlap = optionObject(evalConfig.contextOverlap);
11855
+ const minOverlap = parseNumber2(options.minContextOverlap, "--min-context-overlap");
11856
+ const minSharedTerms = parseNumber2(options.minSharedTerms, "--min-shared-terms");
11857
+ if (contextOverlap !== void 0 || options.contextOverlap || minOverlap !== void 0 || minSharedTerms !== void 0) {
11858
+ rules.push(checks.contextOverlap({ ...contextOverlap, minOverlap, minSharedTerms }));
11859
+ }
11860
+ const quoteOverlap = optionObject(evalConfig.quoteOverlap);
11861
+ if (quoteOverlap !== void 0 || options.quoteOverlap) {
11862
+ rules.push(checks.quoteOverlap(quoteOverlap));
11863
+ }
11864
+ const citationPresence = optionObject(evalConfig.citationPresence);
11865
+ if (citationPresence !== void 0 || options.citationPresence) {
11866
+ rules.push(checks.citationPresence(citationPresence));
11867
+ }
11868
+ const sourceIds = sourceIdsFromConfig(evalConfig.requiredSourceIds);
11869
+ const requiredSourceIds = [...sourceIds?.ids ?? [], ...options.requiredSourceId ?? []];
11870
+ if (requiredSourceIds.length > 0) {
11871
+ rules.push(checks.requiredSourceIds(requiredSourceIds, sourceIds?.options));
11872
+ }
11873
+ const cliAnswerLength = {
11874
+ minCharacters: parseNumber2(options.minAnswerCharacters, "--min-answer-characters"),
11875
+ maxCharacters: parseNumber2(options.maxAnswerCharacters, "--max-answer-characters"),
11876
+ minWords: parseNumber2(options.minAnswerWords, "--min-answer-words"),
11877
+ maxWords: parseNumber2(options.maxAnswerWords, "--max-answer-words")
11878
+ };
11879
+ const hasCliAnswerLength = Object.values(cliAnswerLength).some((value) => value !== void 0);
11880
+ if (evalConfig.answerLengthBounds !== void 0 || hasCliAnswerLength) {
11881
+ rules.push(checks.answerLengthBounds({ ...evalConfig.answerLengthBounds, ...cliAnswerLength }));
11882
+ }
11883
+ const bannedPhrases = [
11884
+ ...asStringArray2(
11885
+ evalConfig.bannedUnsupportedPhrases,
11886
+ "eval.bannedUnsupportedPhrases"
11887
+ ) ?? [],
11888
+ ...options.bannedPhrase ?? []
11889
+ ];
11890
+ if (bannedPhrases.length > 0) rules.push(checks.bannedUnsupportedPhrases(bannedPhrases));
11891
+ return rules.length > 0 ? rules : void 0;
11892
+ }
11893
+ function exitCodeFor2(result) {
11894
+ if (result.status === "pass") return 0;
11895
+ if (result.status === "fail") return 1;
11896
+ return 2;
11897
+ }
11898
+ function stable3(value) {
11899
+ if (Array.isArray(value)) return value.map(stable3);
11900
+ if (value === null || typeof value !== "object") return value;
11901
+ const record = value;
11902
+ return Object.fromEntries(
11903
+ Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable3(record[key])])
11904
+ );
11905
+ }
11906
+ function printJson2(result) {
11907
+ console.log(JSON.stringify(stable3(result), null, 2));
11908
+ }
11909
+ function printHuman2(result) {
11910
+ console.log(`Eval status: ${result.status}`);
11911
+ console.log(`Format: ${result.format}`);
11912
+ if (result.runId !== void 0) console.log(`Run: ${result.runId}`);
11913
+ console.log(
11914
+ `Summary: ${result.summary.failed} failed, ${result.summary.warnings} warning(s), ${result.summary.errors} error(s)`
11915
+ );
11916
+ for (const diagnostic4 of result.diagnostics) {
11917
+ console.log(`- ${diagnostic4.code}: ${diagnostic4.message}`);
11918
+ }
11919
+ for (const finding of result.findings) {
11920
+ const path13 = finding.evidence[0]?.path;
11921
+ console.log(`- ${finding.ruleId}: ${finding.message}${path13 ? ` (${path13})` : ""}`);
11922
+ }
11923
+ }
11924
+ function readErrorResult2(error) {
11925
+ if (error instanceof TraceReadError) {
11926
+ const code = error.code === "unsupported_format" ? "AI_EVAL_UNSUPPORTED_FORMAT" : error.code === "ambiguous_format" ? "AI_EVAL_AMBIGUOUS_FORMAT" : "AI_EVAL_TRACE_UNREADABLE";
11927
+ return errorResult4(code, error.message);
11928
+ }
11929
+ return errorResult4(
11930
+ "AI_EVAL_TRACE_UNREADABLE",
11931
+ error instanceof Error ? error.message : String(error)
11932
+ );
11933
+ }
11934
+ async function evalCommand(target, options = {}, stdin = process.stdin) {
11935
+ let result;
11936
+ let phase = "config";
11937
+ try {
11938
+ const config = await loadConfig2(options.config);
11939
+ const rules = buildRules2(config, options);
11940
+ phase = "read";
11941
+ const input3 = await inputFromTarget(target, options, stdin);
11942
+ const read = await openTrace(input3, {
11943
+ ...options.format !== void 0 ? { format: options.format } : {}
11944
+ });
11945
+ result = await evalRun(read, {
11946
+ ...rules !== void 0 ? { checks: rules } : {},
11947
+ ...options.run !== void 0 ? { runId: options.run } : {}
11948
+ });
11949
+ } catch (error) {
11950
+ if (phase === "config") {
11951
+ const message = error instanceof Error ? error.message : String(error);
11952
+ 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";
11953
+ result = errorResult4(code, message);
11954
+ } else {
11955
+ result = readErrorResult2(error);
11956
+ }
11957
+ }
11958
+ process.exitCode = exitCodeFor2(result);
11959
+ if (options.json) printJson2(result);
11960
+ else if (options.markdown) console.log(renderEvalMarkdown(result).trimEnd());
11961
+ else printHuman2(result);
11962
+ }
11963
+
11964
+ // packages/cli/src/safety.ts
11965
+ var BEST_EFFORT_NOTE = "Best-effort local safety verification only; not a compliance, privacy, security, or regulatory certification.";
11966
+ var DEFAULT_MAX_STRING_LENGTH = 16384;
11967
+ var DEFAULT_MAX_ARRAY_LENGTH = 1e3;
11968
+ var DEFAULT_MAX_OBJECT_KEYS = 200;
11969
+ var DEFAULT_MAX_SERIALIZED_BYTES = 128 * 1024;
11970
+ function parseLimit3(value, label) {
11971
+ if (value === void 0) return void 0;
11972
+ const parsed = Number(value);
11973
+ if (!Number.isFinite(parsed) || parsed < 0) {
11974
+ throw new Error(`${label} must be a non-negative number.`);
11975
+ }
11976
+ return parsed;
11977
+ }
11978
+ function stable4(value) {
11979
+ if (Array.isArray(value)) return value.map(stable4);
11980
+ if (value === null || typeof value !== "object") return value;
11981
+ const record = value;
11982
+ return Object.fromEntries(
11983
+ Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable4(record[key])])
11984
+ );
11985
+ }
11986
+ function safetyDiagnostic(code, message, severity = "error") {
11987
+ return { code, message, severity };
11988
+ }
11989
+ function warningDiagnostics(warnings, unsupportedFields) {
11990
+ return [
11991
+ ...warnings.map(
11992
+ (warning) => safetyDiagnostic(
11993
+ warning.code,
11994
+ warning.message,
11995
+ warning.severity === "error" ? "error" : "warning"
11996
+ )
11997
+ ),
11998
+ ...unsupportedFields.map(
11999
+ (field) => safetyDiagnostic(
10558
12000
  "unsupported_field",
10559
12001
  `Reader reported unsupported field: ${field}`,
10560
12002
  "warning"
@@ -10596,7 +12038,7 @@ function resultFromParts(parts) {
10596
12038
  note: BEST_EFFORT_NOTE
10597
12039
  };
10598
12040
  }
10599
- function readErrorResult2(command, error) {
12041
+ function readErrorResult3(command, error) {
10600
12042
  if (error instanceof TraceReadError) {
10601
12043
  const code = error.code === "unsupported_format" ? "AI_SAFETY_UNSUPPORTED_FORMAT" : error.code === "ambiguous_format" ? "AI_SAFETY_AMBIGUOUS_FORMAT" : "AI_SAFETY_TRACE_UNREADABLE";
10602
12044
  return resultFromParts({
@@ -10646,27 +12088,76 @@ function buildSafetyRules(options) {
10646
12088
  })
10647
12089
  ];
10648
12090
  }
10649
- function exitCodeFor2(result) {
12091
+ function flattenNodes2(nodes) {
12092
+ return nodes.flatMap((node) => [
12093
+ node,
12094
+ ...flattenNodes2(
12095
+ node.children
12096
+ )
12097
+ ]);
12098
+ }
12099
+ function detectorSeverity(finding) {
12100
+ return finding.severity;
12101
+ }
12102
+ function redactionDetectorFindings(read, runId) {
12103
+ const runs = runId === void 0 ? read.runs : read.runs.filter((run) => run.runId === runId);
12104
+ const out = [];
12105
+ for (const run of runs) {
12106
+ for (const node of flattenNodes2(run.children)) {
12107
+ const attrs = node.event.attributes;
12108
+ if (attrs === void 0) continue;
12109
+ const result = redact(attrs, { profile: "share" });
12110
+ for (const finding of result.findings) {
12111
+ if (finding.action === "keep") continue;
12112
+ out.push({
12113
+ ruleId: "safety.redactDetector",
12114
+ severity: detectorSeverity(finding),
12115
+ status: finding.severity === "error" ? "fail" : "warning",
12116
+ message: `Redaction detector ${finding.detector} matched ${finding.matchKind} at ${finding.path}.`,
12117
+ expected: "redacted trace content",
12118
+ actual: finding.detector,
12119
+ evidence: [
12120
+ {
12121
+ runId: node.event.runId,
12122
+ eventId: node.event.eventId,
12123
+ ...node.event.parentId !== void 0 ? { parentId: node.event.parentId } : {},
12124
+ kind: node.event.kind,
12125
+ name: node.event.name,
12126
+ ...node.event.status !== void 0 ? { status: node.event.status } : {},
12127
+ path: `attributes.${finding.path.replace(/^\$\.?/, "")}`
12128
+ }
12129
+ ]
12130
+ });
12131
+ }
12132
+ }
12133
+ }
12134
+ return out.sort((a, b) => {
12135
+ const aEvidence = a.evidence[0];
12136
+ const bEvidence = b.evidence[0];
12137
+ return (aEvidence?.runId ?? "").localeCompare(bEvidence?.runId ?? "") || (aEvidence?.eventId ?? "").localeCompare(bEvidence?.eventId ?? "") || (aEvidence?.path ?? "").localeCompare(bEvidence?.path ?? "") || a.message.localeCompare(b.message);
12138
+ });
12139
+ }
12140
+ function exitCodeFor3(result) {
10650
12141
  if (result.status === "SAFE" || result.status === "SAFE WITH WARNINGS") return 0;
10651
12142
  if (result.status === "UNSAFE") return 1;
10652
12143
  return 2;
10653
12144
  }
10654
- function printJson2(result) {
10655
- console.log(JSON.stringify(stable2(result), null, 2));
12145
+ function printJson3(result) {
12146
+ console.log(JSON.stringify(stable4(result), null, 2));
10656
12147
  }
10657
- function printHuman2(result) {
12148
+ function printHuman3(result) {
10658
12149
  console.log(`Safety status: ${result.status}`);
10659
12150
  console.log(`Format: ${result.format}`);
10660
12151
  if (result.runId !== void 0) console.log(`Run: ${result.runId}`);
10661
12152
  console.log(
10662
12153
  `Summary: ${result.summary.findings} finding(s), ${result.summary.warnings} warning(s), ${result.summary.errors} error(s)`
10663
12154
  );
10664
- for (const diagnostic3 of result.diagnostics) {
10665
- console.log(`- ${diagnostic3.code}: ${diagnostic3.message}`);
12155
+ for (const diagnostic4 of result.diagnostics) {
12156
+ console.log(`- ${diagnostic4.code}: ${diagnostic4.message}`);
10666
12157
  }
10667
12158
  for (const finding of result.findings) {
10668
- const path12 = finding.evidence[0]?.path;
10669
- console.log(`- ${finding.ruleId}: ${finding.message}${path12 ? ` (${path12})` : ""}`);
12159
+ const path13 = finding.evidence[0]?.path;
12160
+ console.log(`- ${finding.ruleId}: ${finding.message}${path13 ? ` (${path13})` : ""}`);
10670
12161
  }
10671
12162
  console.log(`Note: ${result.note}`);
10672
12163
  }
@@ -10685,11 +12176,12 @@ async function safetyCommand(command, target, options, stdin) {
10685
12176
  ...options.run !== void 0 ? { runId: options.run } : {}
10686
12177
  }
10687
12178
  );
12179
+ const detectorFindings = checkResult.diagnostics.length === 0 ? redactionDetectorFindings(read, checkResult.runId) : [];
10688
12180
  result = resultFromParts({
10689
12181
  command,
10690
12182
  format: checkResult.format,
10691
12183
  runId: checkResult.runId,
10692
- findings: checkResult.findings,
12184
+ findings: [...checkResult.findings, ...detectorFindings],
10693
12185
  diagnostics: [
10694
12186
  ...checkResult.diagnostics.map(diagnosticFromCheck),
10695
12187
  ...warningDiagnostics(read.warnings, read.unsupportedFields)
@@ -10699,11 +12191,11 @@ async function safetyCommand(command, target, options, stdin) {
10699
12191
  });
10700
12192
  } catch (error) {
10701
12193
  const message = error instanceof Error ? error.message : String(error);
10702
- result = message.startsWith("--") ? invalidArgumentResult(command, error) : readErrorResult2(command, error);
12194
+ result = message.startsWith("--") ? invalidArgumentResult(command, error) : readErrorResult3(command, error);
10703
12195
  }
10704
- process.exitCode = exitCodeFor2(result);
10705
- if (options.json) printJson2(result);
10706
- else printHuman2(result);
12196
+ process.exitCode = exitCodeFor3(result);
12197
+ if (options.json) printJson3(result);
12198
+ else printHuman3(result);
10707
12199
  }
10708
12200
  function scanCommand(target, options = {}, stdin = process.stdin) {
10709
12201
  return safetyCommand("scan", target, options, stdin);
@@ -10723,16 +12215,16 @@ var SAFETY_RULES = [
10723
12215
  maxSerializedBytes: 128 * 1024
10724
12216
  })
10725
12217
  ];
10726
- function stable3(value) {
10727
- if (Array.isArray(value)) return value.map(stable3);
12218
+ function stable5(value) {
12219
+ if (Array.isArray(value)) return value.map(stable5);
10728
12220
  if (value === null || typeof value !== "object") return value;
10729
12221
  const record = value;
10730
12222
  return Object.fromEntries(
10731
- Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable3(record[key])])
12223
+ Object.keys(record).sort((a, b) => a.localeCompare(b)).map((key) => [key, stable5(record[key])])
10732
12224
  );
10733
12225
  }
10734
12226
  function writeJson3(value) {
10735
- return `${JSON.stringify(stable3(value), null, 2)}
12227
+ return `${JSON.stringify(stable5(value), null, 2)}
10736
12228
  `;
10737
12229
  }
10738
12230
  function escapeHtml2(value) {
@@ -10749,7 +12241,7 @@ function increment(record, key) {
10749
12241
  const label = key && key.trim() !== "" ? key : "unknown";
10750
12242
  record[label] = (record[label] ?? 0) + 1;
10751
12243
  }
10752
- function selectRun3(read, runId) {
12244
+ function selectRun4(read, runId) {
10753
12245
  if (runId !== void 0) {
10754
12246
  return read.runs.find((run) => run.runId === runId);
10755
12247
  }
@@ -10784,11 +12276,11 @@ function renderCheckSection(result) {
10784
12276
  `Diagnostics: ${result.diagnostics.length}`
10785
12277
  ];
10786
12278
  for (const finding of result.findings.slice(0, 10)) {
10787
- const path12 = finding.evidence[0]?.path ?? "(run)";
10788
- lines.push(`- ${finding.ruleId}: ${finding.message} (${path12})`);
12279
+ const path13 = finding.evidence[0]?.path ?? "(run)";
12280
+ lines.push(`- ${finding.ruleId}: ${finding.message} (${path13})`);
10789
12281
  }
10790
- for (const diagnostic3 of result.diagnostics.slice(0, 10)) {
10791
- lines.push(`- ${diagnostic3.code}: ${diagnostic3.message}`);
12282
+ for (const diagnostic4 of result.diagnostics.slice(0, 10)) {
12283
+ lines.push(`- ${diagnostic4.code}: ${diagnostic4.message}`);
10792
12284
  }
10793
12285
  return lines.join("\n");
10794
12286
  }
@@ -10863,8 +12355,8 @@ function renderHtml(trace, check, diff) {
10863
12355
  `;
10864
12356
  }
10865
12357
  async function writeArtifact(outputDir, relativePath, content, files) {
10866
- const outPath = path10.join(outputDir, relativePath);
10867
- await mkdir(path10.dirname(outPath), { recursive: true });
12358
+ const outPath = path11.join(outputDir, relativePath);
12359
+ await mkdir(path11.dirname(outPath), { recursive: true });
10868
12360
  await writeFile(outPath, content, "utf-8");
10869
12361
  files.push(relativePath);
10870
12362
  }
@@ -10880,7 +12372,7 @@ function manifestStatus(check, diff) {
10880
12372
  return "ok";
10881
12373
  }
10882
12374
  async function artifactsCommand(target, options = {}, stdin = process.stdin) {
10883
- const outputDir = options.outputDir !== void 0 && options.outputDir.trim() !== "" ? path10.resolve(options.outputDir.trim()) : "";
12375
+ const outputDir = options.outputDir !== void 0 && options.outputDir.trim() !== "" ? path11.resolve(options.outputDir.trim()) : "";
10884
12376
  if (outputDir === "") {
10885
12377
  console.error("--output-dir is required.");
10886
12378
  process.exitCode = 1;
@@ -10897,7 +12389,7 @@ async function artifactsCommand(target, options = {}, stdin = process.stdin) {
10897
12389
  process.exitCode = 1;
10898
12390
  return;
10899
12391
  }
10900
- const selectedRun = selectRun3(read, options.run);
12392
+ const selectedRun = selectRun4(read, options.run);
10901
12393
  const check = runTraceChecks(
10902
12394
  { read },
10903
12395
  {
@@ -10946,8 +12438,8 @@ async function artifactsCommand(target, options = {}, stdin = process.stdin) {
10946
12438
  await writeArtifact(outputDir, "report.html", renderHtml(trace, check, diff), files);
10947
12439
  const summaryTarget = options.githubSummary ?? process.env.GITHUB_STEP_SUMMARY;
10948
12440
  if (summaryTarget !== void 0 && summaryTarget.trim() !== "") {
10949
- await mkdir(path10.dirname(path10.resolve(summaryTarget)), { recursive: true });
10950
- await appendFile(path10.resolve(summaryTarget), `
12441
+ await mkdir(path11.dirname(path11.resolve(summaryTarget)), { recursive: true });
12442
+ await appendFile(path11.resolve(summaryTarget), `
10951
12443
  ${renderMarkdown(trace, check, diff)}`, "utf-8");
10952
12444
  }
10953
12445
  const manifestFiles = [...files, "manifest.json"].sort((a, b) => a.localeCompare(b));
@@ -10966,10 +12458,10 @@ ${renderMarkdown(trace, check, diff)}`, "utf-8");
10966
12458
  findings: diff?.findings.length ?? 0,
10967
12459
  diagnostics: diff?.diagnostics.length ?? 0
10968
12460
  },
10969
- ...summaryTarget !== void 0 && summaryTarget.trim() !== "" ? { githubSummary: path10.resolve(summaryTarget) } : {},
12461
+ ...summaryTarget !== void 0 && summaryTarget.trim() !== "" ? { githubSummary: path11.resolve(summaryTarget) } : {},
10970
12462
  note: NOTE
10971
12463
  };
10972
- await writeFile(path10.join(outputDir, "manifest.json"), writeJson3(manifest), "utf-8");
12464
+ await writeFile(path11.join(outputDir, "manifest.json"), writeJson3(manifest), "utf-8");
10973
12465
  if (options.json === true) {
10974
12466
  console.log(writeJson3(manifest).trimEnd());
10975
12467
  } else {
@@ -11108,6 +12600,36 @@ function createCliProgram() {
11108
12600
  ]).option("--max-total-tokens <number>", "add llm.usage with a max total-token budget").action((target, opts) => {
11109
12601
  runCommand(() => checkCommand(target, opts));
11110
12602
  });
12603
+ 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(
12604
+ new Option("--format <format>", "trace input format").choices([
12605
+ "agent-inspect-jsonl",
12606
+ "openinference-json",
12607
+ "otlp-json"
12608
+ ])
12609
+ ).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 = []) => [
12610
+ ...previous,
12611
+ value
12612
+ ]).option("--forbid-tool <name>", "forbid a tool name (repeatable)", (value, previous = []) => [
12613
+ ...previous,
12614
+ value
12615
+ ]).option("--forbidden-tool <name>", "alias for --forbid-tool (repeatable)", (value, previous = []) => [
12616
+ ...previous,
12617
+ value
12618
+ ]).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(
12619
+ "--require-retrieval-before-generation",
12620
+ "require a retrieval step before the first LLM generation"
12621
+ ).option("--required-decision-metadata <key>", "require decision metadata (repeatable)", (value, previous = []) => [
12622
+ ...previous,
12623
+ value
12624
+ ]).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 = []) => [
12625
+ ...previous,
12626
+ value
12627
+ ]).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 = []) => [
12628
+ ...previous,
12629
+ value
12630
+ ]).action((target, opts) => {
12631
+ runCommand(() => evalCommand(target, opts));
12632
+ });
11111
12633
  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(
11112
12634
  new Option("--format <format>", "trace input format").choices([
11113
12635
  "agent-inspect-jsonl",
@@ -11195,6 +12717,14 @@ function createCliProgram() {
11195
12717
  ).action((runId, opts) => {
11196
12718
  runCommand(() => reportCommand(runId, opts));
11197
12719
  });
12720
+ 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(
12721
+ new Option(
12722
+ "--profile <profile>",
12723
+ "redaction profile: local, share, strict (default: share)"
12724
+ ).choices(["local", "share", "strict"])
12725
+ ).option("-o, --output <path>", "write redacted content to a file").option("--json", "print deterministic JSON wrapper with findings").action((target, opts) => {
12726
+ runCommand(() => redactCommand(target, opts));
12727
+ });
11198
12728
  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(
11199
12729
  new Option("--format <format>", "trace input format").choices([
11200
12730
  "agent-inspect-jsonl",
@@ -11219,9 +12749,9 @@ function isPrimaryModule() {
11219
12749
  if (!entry) return false;
11220
12750
  const selfPath = fileURLToPath(import.meta.url);
11221
12751
  try {
11222
- return realpathSync(path10.resolve(entry)) === realpathSync(path10.resolve(selfPath));
12752
+ return realpathSync(path11.resolve(entry)) === realpathSync(path11.resolve(selfPath));
11223
12753
  } catch {
11224
- return path10.resolve(entry) === path10.resolve(selfPath);
12754
+ return path11.resolve(entry) === path11.resolve(selfPath);
11225
12755
  }
11226
12756
  }
11227
12757
  if (isPrimaryModule()) {