agent-inspect 2.4.0 → 2.5.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.
@@ -23,7 +23,7 @@ var process2__default = /*#__PURE__*/_interopDefault(process2);
23
23
  var tty__default = /*#__PURE__*/_interopDefault(tty);
24
24
 
25
25
  // package.json
26
- var version = "2.4.0";
26
+ var version = "2.5.0";
27
27
 
28
28
  // packages/core/src/correlation-metadata.ts
29
29
  var TRACE_CORRELATION_KEYS = [
@@ -1440,18 +1440,18 @@ var proto = Object.defineProperties(() => {
1440
1440
  }
1441
1441
  }
1442
1442
  });
1443
- var createStyler = (open2, close, parent) => {
1443
+ var createStyler = (open3, close, parent) => {
1444
1444
  let openAll;
1445
1445
  let closeAll;
1446
1446
  if (parent === void 0) {
1447
- openAll = open2;
1447
+ openAll = open3;
1448
1448
  closeAll = close;
1449
1449
  } else {
1450
- openAll = parent.openAll + open2;
1450
+ openAll = parent.openAll + open3;
1451
1451
  closeAll = close + parent.closeAll;
1452
1452
  }
1453
1453
  return {
1454
- open: open2,
1454
+ open: open3,
1455
1455
  close,
1456
1456
  openAll,
1457
1457
  closeAll,
@@ -1516,15 +1516,15 @@ function getStatusIcon(status) {
1516
1516
  if (status === "error") return source_default.red("\u2716");
1517
1517
  return source_default.yellow("\u23F3");
1518
1518
  }
1519
- function renderStepLine(name, durationMs, status, depth) {
1519
+ function renderStepLine(name, durationMs2, status, depth) {
1520
1520
  try {
1521
1521
  const nm = formatTerminalName(name);
1522
1522
  const ind = getIndent(depth ?? 0);
1523
- if (status === "running" && durationMs === void 0) {
1523
+ if (status === "running" && durationMs2 === void 0) {
1524
1524
  return `${ind}${source_default.yellow("\u23F3")} ${nm}`;
1525
1525
  }
1526
- const hasDur = durationMs !== void 0 && Number.isFinite(durationMs);
1527
- const dur = hasDur ? formatDuration2(durationMs) : void 0;
1526
+ const hasDur = durationMs2 !== void 0 && Number.isFinite(durationMs2);
1527
+ const dur = hasDur ? formatDuration2(durationMs2) : void 0;
1528
1528
  if (status === "running") {
1529
1529
  return dur !== void 0 ? `${ind}${source_default.yellow("\u23F3")} ${nm} (${dur})` : `${ind}${source_default.yellow("\u23F3")} ${nm}`;
1530
1530
  }
@@ -1677,14 +1677,14 @@ async function extractMetadata(filePath, _quickScan) {
1677
1677
  } else {
1678
1678
  status = "unknown";
1679
1679
  }
1680
- const durationMs = explicitDurationMs ?? (startedAt !== void 0 && endedAt !== void 0 && Number.isFinite(startedAt) && Number.isFinite(endedAt) && endedAt >= startedAt ? endedAt - startedAt : void 0);
1680
+ const durationMs2 = explicitDurationMs ?? (startedAt !== void 0 && endedAt !== void 0 && Number.isFinite(startedAt) && Number.isFinite(endedAt) && endedAt >= startedAt ? endedAt - startedAt : void 0);
1681
1681
  return {
1682
1682
  runId: resolvedRunId,
1683
1683
  name,
1684
1684
  status,
1685
1685
  startedAt,
1686
1686
  endedAt,
1687
- durationMs,
1687
+ durationMs: durationMs2,
1688
1688
  eventCount: parsedTrace.sourceEventCount,
1689
1689
  filePath,
1690
1690
  fileSize: stats.size,
@@ -1705,7 +1705,7 @@ function buildRunSummary(events) {
1705
1705
  const runId = started?.runId ?? events.find((e) => typeof e.runId === "string")?.runId ?? "unknown-run";
1706
1706
  const name = typeof started?.name === "string" && started.name.trim() !== "" ? started.name : void 0;
1707
1707
  const status = lastCompleted ? lastCompleted.status : started ? "running" : "unknown";
1708
- const durationMs = lastCompleted && isFiniteNumber(lastCompleted.durationMs) ? lastCompleted.durationMs : void 0;
1708
+ const durationMs2 = lastCompleted && isFiniteNumber(lastCompleted.durationMs) ? lastCompleted.durationMs : void 0;
1709
1709
  started && isFiniteNumber(started.startTime) ? started.startTime : void 0;
1710
1710
  const steps = /* @__PURE__ */ new Map();
1711
1711
  for (const e of events) {
@@ -1795,7 +1795,7 @@ function buildRunSummary(events) {
1795
1795
  runId,
1796
1796
  name,
1797
1797
  status,
1798
- durationMs,
1798
+ durationMs: durationMs2,
1799
1799
  totalSteps,
1800
1800
  llmSteps,
1801
1801
  toolSteps,
@@ -3226,10 +3226,10 @@ function statusIcon(status) {
3226
3226
  if (status === "running") return "\u23F3";
3227
3227
  return "?";
3228
3228
  }
3229
- function durationCell(status, durationMs) {
3229
+ function durationCell(status, durationMs2) {
3230
3230
  if (status === "running" || status === "unknown") return "-";
3231
- if (durationMs !== void 0 && Number.isFinite(durationMs)) {
3232
- return formatDuration2(durationMs);
3231
+ if (durationMs2 !== void 0 && Number.isFinite(durationMs2)) {
3232
+ return formatDuration2(durationMs2);
3233
3233
  }
3234
3234
  return "-";
3235
3235
  }
@@ -3886,7 +3886,7 @@ var TreeBuilder = class {
3886
3886
  const startedAt = sorted.length > 0 ? sorted[0].timestamp : void 0;
3887
3887
  const endedAt = sorted.length > 0 ? sorted[sorted.length - 1].timestamp : void 0;
3888
3888
  const status = computeRunStatus(sorted);
3889
- const durationMs = startedAt !== void 0 && endedAt !== void 0 && Number.isFinite(startedAt) && Number.isFinite(endedAt) && endedAt >= startedAt && status !== "running" ? endedAt - startedAt : void 0;
3889
+ const durationMs2 = startedAt !== void 0 && endedAt !== void 0 && Number.isFinite(startedAt) && Number.isFinite(endedAt) && endedAt >= startedAt && status !== "running" ? endedAt - startedAt : void 0;
3890
3890
  const name = sorted.find((e) => e.kind === "RUN")?.name;
3891
3891
  out.push({
3892
3892
  runId,
@@ -3894,7 +3894,7 @@ var TreeBuilder = class {
3894
3894
  status,
3895
3895
  startedAt,
3896
3896
  endedAt: status === "running" ? void 0 : endedAt,
3897
- durationMs,
3897
+ durationMs: durationMs2,
3898
3898
  children: roots,
3899
3899
  metadata: {
3900
3900
  totalEvents: sorted.length,
@@ -4589,9 +4589,9 @@ function mapOpenInferenceSpan(span, index, version2) {
4589
4589
  if (endedAt !== void 0) {
4590
4590
  event.endedAt = endedAt;
4591
4591
  }
4592
- const durationMs = durationBetweenIso(startedAt, endedAt);
4593
- if (durationMs !== void 0) {
4594
- event.durationMs = durationMs;
4592
+ const durationMs2 = durationBetweenIso(startedAt, endedAt);
4593
+ if (durationMs2 !== void 0) {
4594
+ event.durationMs = durationMs2;
4595
4595
  }
4596
4596
  if (tokenUsage !== void 0) {
4597
4597
  event.tokenUsage = tokenUsage;
@@ -5176,9 +5176,9 @@ function mapOtlpSpan(context) {
5176
5176
  if (endedAt !== void 0) {
5177
5177
  event.endedAt = endedAt;
5178
5178
  }
5179
- const durationMs = durationBetweenIso(startedAt, endedAt);
5180
- if (durationMs !== void 0) {
5181
- event.durationMs = durationMs;
5179
+ const durationMs2 = durationBetweenIso(startedAt, endedAt);
5180
+ if (durationMs2 !== void 0) {
5181
+ event.durationMs = durationMs2;
5182
5182
  }
5183
5183
  if (tokenUsage !== void 0) {
5184
5184
  event.tokenUsage = tokenUsage;
@@ -6183,13 +6183,13 @@ var EventNormalizer = class {
6183
6183
  const timestampMissing = parsedTs === void 0;
6184
6184
  const parentIdKey = cfg.parentIdKey;
6185
6185
  const parentId = parentIdKey ? safeString(raw[parentIdKey]) : void 0;
6186
- let durationMs;
6186
+ let durationMs2;
6187
6187
  const durationKey = cfg.durationKey;
6188
6188
  if (durationKey) {
6189
6189
  const v = raw[durationKey];
6190
- if (isFiniteNumber2(v)) durationMs = v;
6190
+ if (isFiniteNumber2(v)) durationMs2 = v;
6191
6191
  } else if (isFiniteNumber2(raw.durationMs)) {
6192
- durationMs = raw.durationMs;
6192
+ durationMs2 = raw.durationMs;
6193
6193
  }
6194
6194
  let status;
6195
6195
  const statusKey = cfg.statusKey;
@@ -6236,7 +6236,7 @@ var EventNormalizer = class {
6236
6236
  kind,
6237
6237
  timestamp,
6238
6238
  ...status ? { status } : {},
6239
- ...durationMs !== void 0 ? { durationMs } : {},
6239
+ ...durationMs2 !== void 0 ? { durationMs: durationMs2 } : {},
6240
6240
  ...Object.keys(attributes).length > 0 ? { attributes } : {},
6241
6241
  confidence,
6242
6242
  source: {
@@ -7824,7 +7824,7 @@ function manualTraceEventsToRunTree(events) {
7824
7824
  }
7825
7825
  const startedAt = started.startTime;
7826
7826
  const endedAt = lastCompleted !== void 0 && runStatus !== "running" ? lastCompleted.endTime : void 0;
7827
- const durationMs = lastCompleted !== void 0 && Number.isFinite(lastCompleted.durationMs) ? lastCompleted.durationMs : void 0;
7827
+ const durationMs2 = lastCompleted !== void 0 && Number.isFinite(lastCompleted.durationMs) ? lastCompleted.durationMs : void 0;
7828
7828
  const steps = /* @__PURE__ */ new Map();
7829
7829
  for (const e of events) {
7830
7830
  if (e.event !== "step_started") continue;
@@ -7913,7 +7913,7 @@ function manualTraceEventsToRunTree(events) {
7913
7913
  status: runStatus,
7914
7914
  startedAt,
7915
7915
  endedAt,
7916
- durationMs,
7916
+ durationMs: durationMs2,
7917
7917
  children: roots,
7918
7918
  metadata: {
7919
7919
  totalEvents: inspectNodes.size,
@@ -8226,7 +8226,7 @@ function manualTraceEventsToComparableRun(events) {
8226
8226
  let runStatus;
8227
8227
  if (lastCompleted === void 0) runStatus = "running";
8228
8228
  else runStatus = lastCompleted.status;
8229
- const durationMs = lastCompleted !== void 0 && Number.isFinite(lastCompleted.durationMs) ? lastCompleted.durationMs : void 0;
8229
+ const durationMs2 = lastCompleted !== void 0 && Number.isFinite(lastCompleted.durationMs) ? lastCompleted.durationMs : void 0;
8230
8230
  const steps = /* @__PURE__ */ new Map();
8231
8231
  let order = 0;
8232
8232
  for (const e of events) {
@@ -8297,7 +8297,7 @@ function manualTraceEventsToComparableRun(events) {
8297
8297
  runId,
8298
8298
  name: rs.name,
8299
8299
  status: runStatus,
8300
- durationMs,
8300
+ durationMs: durationMs2,
8301
8301
  steps: roots
8302
8302
  };
8303
8303
  }
@@ -11191,7 +11191,7 @@ function createSafetyRawContentRule(options = {}) {
11191
11191
  }
11192
11192
  function createSafetySecretPatternRule(options = {}) {
11193
11193
  const patterns = options.patterns ?? DEFAULT_SECRET_PATTERNS;
11194
- const maxStringLength = options.maxStringLength ?? 4096;
11194
+ const maxStringLength2 = options.maxStringLength ?? 4096;
11195
11195
  return {
11196
11196
  id: "safety.secretPattern",
11197
11197
  category: "safety",
@@ -11201,7 +11201,7 @@ function createSafetySecretPatternRule(options = {}) {
11201
11201
  for (const event of context.events) {
11202
11202
  for (const entry of eventValueEntries(event, { includeSummaries: true, includeError: true })) {
11203
11203
  if (typeof entry.value !== "string") continue;
11204
- const sample = entry.value.slice(0, maxStringLength);
11204
+ const sample = entry.value.slice(0, maxStringLength2);
11205
11205
  for (const pattern of patterns) {
11206
11206
  pattern.pattern.lastIndex = 0;
11207
11207
  if (!pattern.pattern.test(sample)) continue;
@@ -11457,6 +11457,621 @@ function runTraceChecks(input3, options = {}) {
11457
11457
  diagnostics
11458
11458
  };
11459
11459
  }
11460
+ var ALL_RULES = [
11461
+ "circuit.same-tool-repetition",
11462
+ "circuit.same-args-repetition",
11463
+ "circuit.max-loop-iterations",
11464
+ "circuit.max-retries",
11465
+ "circuit.tool-timeout",
11466
+ "circuit.runaway-llm-loop",
11467
+ "circuit.excessive-branch-width"
11468
+ ];
11469
+ function closed(ruleId, message) {
11470
+ return { ruleId, status: "closed", severity: "info", message, evidence: [] };
11471
+ }
11472
+ function open2(ruleId, message, evidence, severity = "error") {
11473
+ return {
11474
+ ruleId,
11475
+ status: severity === "warning" ? "warn" : "open",
11476
+ severity,
11477
+ message,
11478
+ evidence
11479
+ };
11480
+ }
11481
+ function isToolEvent(event) {
11482
+ const name = event.name.toLowerCase();
11483
+ return event.kind === "tool" || name.startsWith("tool:") || name.startsWith("function:") || name.includes(".tool.") || name.startsWith("mcp:");
11484
+ }
11485
+ function isLlmEvent(event) {
11486
+ const name = event.name.toLowerCase();
11487
+ return event.kind === "llm" || name.startsWith("llm:") || name.includes(".llm.") || name.includes("generation");
11488
+ }
11489
+ function toolLabel(event) {
11490
+ const attrs = event.attributes ?? {};
11491
+ const fromAttr = attrs.toolName ?? attrs.tool ?? attrs.function;
11492
+ if (typeof fromAttr === "string" && fromAttr.length > 0) return fromAttr;
11493
+ return event.name.replace(/^(tool:|function:|mcp:)/i, "");
11494
+ }
11495
+ function argsHash(toolName2, args) {
11496
+ return crypto.createHash("sha256").update(`${toolName2}:${JSON.stringify(args ?? null)}`).digest("hex").slice(0, 16);
11497
+ }
11498
+ function toolArgs(event) {
11499
+ const attrs = event.attributes ?? {};
11500
+ return attrs.arguments ?? attrs.args ?? attrs.input ?? attrs.parameters;
11501
+ }
11502
+ function durationMs(event) {
11503
+ if (typeof event.durationMs === "number") return event.durationMs;
11504
+ const attrs = event.attributes ?? {};
11505
+ const fromAttr = attrs.durationMs ?? attrs.duration;
11506
+ return typeof fromAttr === "number" ? fromAttr : void 0;
11507
+ }
11508
+ function attemptNumber(event) {
11509
+ const attrs = event.attributes ?? {};
11510
+ const value = attrs.attempt ?? attrs.retryAttempt ?? attrs.retryCount;
11511
+ return typeof value === "number" ? value : void 0;
11512
+ }
11513
+ function evaluateSameToolRepetition(events, maxRepeats) {
11514
+ const ruleId = "circuit.same-tool-repetition";
11515
+ const counts = /* @__PURE__ */ new Map();
11516
+ for (const event of events.filter(isToolEvent)) {
11517
+ const label = toolLabel(event);
11518
+ counts.set(label, (counts.get(label) ?? 0) + 1);
11519
+ }
11520
+ const evidence = [];
11521
+ for (const [toolName2, count] of counts) {
11522
+ if (count > maxRepeats) {
11523
+ evidence.push({ ruleId, toolName: toolName2, count, threshold: maxRepeats });
11524
+ }
11525
+ }
11526
+ if (evidence.length === 0) {
11527
+ return closed(ruleId, "Tool repetition within threshold.");
11528
+ }
11529
+ return open2(ruleId, "Same tool repeated beyond threshold.", evidence);
11530
+ }
11531
+ function evaluateSameArgsRepetition(events, maxRepeats) {
11532
+ const ruleId = "circuit.same-args-repetition";
11533
+ const counts = /* @__PURE__ */ new Map();
11534
+ for (const event of events.filter(isToolEvent)) {
11535
+ const label = toolLabel(event);
11536
+ const hash = argsHash(label, toolArgs(event));
11537
+ const key = `${label}:${hash}`;
11538
+ const current = counts.get(key) ?? { toolName: label, count: 0 };
11539
+ current.count += 1;
11540
+ counts.set(key, current);
11541
+ }
11542
+ const evidence = [];
11543
+ for (const entry of counts.values()) {
11544
+ if (entry.count > maxRepeats) {
11545
+ evidence.push({ ruleId, toolName: entry.toolName, count: entry.count, threshold: maxRepeats });
11546
+ }
11547
+ }
11548
+ if (evidence.length === 0) {
11549
+ return closed(ruleId, "Tool argument repetition within threshold.");
11550
+ }
11551
+ return open2(ruleId, "Same tool arguments repeated beyond threshold.", evidence);
11552
+ }
11553
+ function evaluateMaxLoopIterations(events, maxIterations) {
11554
+ const ruleId = "circuit.max-loop-iterations";
11555
+ const iterationEvents = events.filter((event) => {
11556
+ const attrs = event.attributes ?? {};
11557
+ return typeof attrs.iteration === "number" || event.name.toLowerCase().includes("loop");
11558
+ });
11559
+ const maxSeen = iterationEvents.reduce((max, event) => {
11560
+ const attrs = event.attributes ?? {};
11561
+ const iteration = typeof attrs.iteration === "number" ? attrs.iteration : max;
11562
+ return Math.max(max, iteration);
11563
+ }, iterationEvents.length);
11564
+ if (maxSeen <= maxIterations) {
11565
+ return closed(ruleId, "Loop iterations within threshold.");
11566
+ }
11567
+ return open2(ruleId, "Loop iterations exceeded threshold.", [
11568
+ { ruleId, count: maxSeen, threshold: maxIterations }
11569
+ ]);
11570
+ }
11571
+ function evaluateMaxRetries(events, maxRetries) {
11572
+ const ruleId = "circuit.max-retries";
11573
+ const attempts = events.map(attemptNumber).filter((value) => value !== void 0);
11574
+ const maxAttempt = attempts.length > 0 ? Math.max(...attempts) : 0;
11575
+ if (maxAttempt <= maxRetries) {
11576
+ return closed(ruleId, "Retry count within threshold.");
11577
+ }
11578
+ return open2(ruleId, "Retry count exceeded threshold.", [
11579
+ { ruleId, count: maxAttempt, threshold: maxRetries }
11580
+ ]);
11581
+ }
11582
+ function evaluateToolTimeout(events, maxDurationMs) {
11583
+ const ruleId = "circuit.tool-timeout";
11584
+ const evidence = [];
11585
+ for (const event of events.filter(isToolEvent)) {
11586
+ const duration = durationMs(event);
11587
+ if (duration !== void 0 && duration > maxDurationMs) {
11588
+ evidence.push({
11589
+ ruleId,
11590
+ toolName: toolLabel(event),
11591
+ count: duration,
11592
+ threshold: maxDurationMs,
11593
+ eventId: event.eventId
11594
+ });
11595
+ }
11596
+ }
11597
+ if (evidence.length === 0) {
11598
+ return closed(ruleId, "Tool durations within timeout.");
11599
+ }
11600
+ return open2(ruleId, "Tool call exceeded configured timeout.", evidence, "warning");
11601
+ }
11602
+ function evaluateRunawayLlmLoop(events, maxLlmCalls) {
11603
+ const ruleId = "circuit.runaway-llm-loop";
11604
+ const llmCount = events.filter(isLlmEvent).length;
11605
+ const hasTerminal = events.some((event) => {
11606
+ const status = (event.status ?? event.attributes?.status ?? "").toString().toLowerCase();
11607
+ return status === "ok" || status === "success" || status === "completed";
11608
+ });
11609
+ if (llmCount <= maxLlmCalls || hasTerminal) {
11610
+ return closed(ruleId, "LLM call count within threshold or run completed.");
11611
+ }
11612
+ return open2(ruleId, "Runaway LLM loop detected.", [
11613
+ { ruleId, count: llmCount, threshold: maxLlmCalls }
11614
+ ]);
11615
+ }
11616
+ function evaluateExcessiveBranchWidth(events, maxWidth) {
11617
+ const ruleId = "circuit.excessive-branch-width";
11618
+ const children = /* @__PURE__ */ new Map();
11619
+ for (const event of events) {
11620
+ const parentId = event.parentId;
11621
+ if (!parentId) continue;
11622
+ children.set(parentId, (children.get(parentId) ?? 0) + 1);
11623
+ }
11624
+ const evidence = [];
11625
+ for (const [parentId, count] of children) {
11626
+ if (count > maxWidth) {
11627
+ evidence.push({ ruleId, path: parentId, count, threshold: maxWidth });
11628
+ }
11629
+ }
11630
+ if (evidence.length === 0) {
11631
+ return closed(ruleId, "Branch width within threshold.");
11632
+ }
11633
+ return open2(ruleId, "Excessive parallel branch width detected.", evidence, "warning");
11634
+ }
11635
+ function runRule(ruleId, events, options) {
11636
+ switch (ruleId) {
11637
+ case "circuit.same-tool-repetition":
11638
+ if (options.sameToolRepetition === void 0) return void 0;
11639
+ return evaluateSameToolRepetition(events, options.sameToolRepetition.maxRepeats);
11640
+ case "circuit.same-args-repetition":
11641
+ if (options.sameArgsRepetition === void 0) return void 0;
11642
+ return evaluateSameArgsRepetition(events, options.sameArgsRepetition.maxRepeats);
11643
+ case "circuit.max-loop-iterations":
11644
+ if (options.maxLoopIterations === void 0) return void 0;
11645
+ return evaluateMaxLoopIterations(events, options.maxLoopIterations.maxIterations);
11646
+ case "circuit.max-retries":
11647
+ if (options.maxRetries === void 0) return void 0;
11648
+ return evaluateMaxRetries(events, options.maxRetries.maxRetries);
11649
+ case "circuit.tool-timeout":
11650
+ if (options.toolTimeout === void 0) return void 0;
11651
+ return evaluateToolTimeout(events, options.toolTimeout.maxDurationMs);
11652
+ case "circuit.runaway-llm-loop":
11653
+ if (options.runawayLlmLoop === void 0) return void 0;
11654
+ return evaluateRunawayLlmLoop(events, options.runawayLlmLoop.maxLlmCalls);
11655
+ case "circuit.excessive-branch-width":
11656
+ if (options.excessiveBranchWidth === void 0) return void 0;
11657
+ return evaluateExcessiveBranchWidth(events, options.excessiveBranchWidth.maxWidth);
11658
+ default:
11659
+ return void 0;
11660
+ }
11661
+ }
11662
+ function runCircuits(events, options = {}) {
11663
+ const selected = options.rules ?? ALL_RULES;
11664
+ const results = [];
11665
+ for (const ruleId of selected) {
11666
+ const result = runRule(ruleId, events, options);
11667
+ if (result) results.push(result);
11668
+ }
11669
+ const ok = !results.some((result) => result.status === "open" && result.severity === "error");
11670
+ return { ok, results };
11671
+ }
11672
+
11673
+ // packages/guardrails/src/rules.ts
11674
+ var DEFAULT_INJECTION_PATTERNS = [
11675
+ "ignore previous instructions",
11676
+ "ignore all prior",
11677
+ "disregard your instructions",
11678
+ "system prompt",
11679
+ "you are now",
11680
+ "jailbreak"
11681
+ ];
11682
+ function pass(ruleId, message) {
11683
+ return { ruleId, status: "pass", severity: "info", message, evidence: [] };
11684
+ }
11685
+ function fail(ruleId, message, evidence, severity = "error") {
11686
+ return { ruleId, status: severity === "warning" ? "warn" : "fail", severity, message, evidence };
11687
+ }
11688
+ function boundedPreview(value, max = 80) {
11689
+ if (value.length <= max) return value;
11690
+ return `${value.slice(0, max - 3)}...`;
11691
+ }
11692
+ function evaluateBannedPhrase(text, options) {
11693
+ const ruleId = "guardrail.banned-phrase";
11694
+ const haystack = options.caseInsensitive !== false ? text.toLowerCase() : text;
11695
+ const evidence = [];
11696
+ for (const phrase of options.phrases) {
11697
+ const needle = options.caseInsensitive !== false ? phrase.toLowerCase() : phrase;
11698
+ if (needle.length > 0 && haystack.includes(needle)) {
11699
+ evidence.push({ ruleId, match: phrase, preview: boundedPreview(text) });
11700
+ }
11701
+ }
11702
+ if (evidence.length === 0) {
11703
+ return pass(ruleId, "No banned phrases matched.");
11704
+ }
11705
+ return fail(ruleId, `Matched ${evidence.length} banned phrase(s).`, evidence);
11706
+ }
11707
+ var SEVERITY_RANK2 = { info: 0, warning: 1, error: 2 };
11708
+ function evaluatePiiLeak(value, options = {}) {
11709
+ const ruleId = "guardrail.pii-leak";
11710
+ const minSeverity = options.minSeverity ?? "warning";
11711
+ const result = redact(value, { profile: options.profile ?? "share", collectFindings: true });
11712
+ const findings = result.findings.filter(
11713
+ (finding) => SEVERITY_RANK2[finding.severity] >= SEVERITY_RANK2[minSeverity]
11714
+ );
11715
+ if (findings.length === 0) {
11716
+ return pass(ruleId, "No PII-style redaction findings.");
11717
+ }
11718
+ const evidence = findings.map((finding) => ({
11719
+ ruleId,
11720
+ path: finding.path,
11721
+ detector: finding.detector,
11722
+ preview: finding.preview
11723
+ }));
11724
+ return fail(ruleId, `Detected ${findings.length} PII-style finding(s).`, evidence);
11725
+ }
11726
+ function measureDepth(value) {
11727
+ if (value === null || typeof value !== "object") return 0;
11728
+ if (Array.isArray(value)) {
11729
+ return 1 + Math.max(0, ...value.map((item) => measureDepth(item)));
11730
+ }
11731
+ const depths = Object.values(value).map((item) => measureDepth(item));
11732
+ return 1 + (depths.length === 0 ? 0 : Math.max(...depths));
11733
+ }
11734
+ function maxStringLength(value) {
11735
+ if (typeof value === "string") return value.length;
11736
+ if (value === null || typeof value !== "object") return 0;
11737
+ if (Array.isArray(value)) {
11738
+ return Math.max(0, ...value.map((item) => maxStringLength(item)));
11739
+ }
11740
+ return Math.max(0, ...Object.values(value).map((item) => maxStringLength(item)));
11741
+ }
11742
+ function evaluateUnsafeToolArgs(toolName2, toolArgs2, options = {}) {
11743
+ const ruleId = "guardrail.unsafe-tool-args";
11744
+ const blocked = new Set((options.blockedTools ?? []).map((name) => name.toLowerCase()));
11745
+ const evidence = [];
11746
+ if (blocked.has(toolName2.toLowerCase())) {
11747
+ evidence.push({ ruleId, preview: toolName2, match: toolName2 });
11748
+ }
11749
+ const maxDepth = options.maxDepth ?? 12;
11750
+ const depth = measureDepth(toolArgs2);
11751
+ if (depth > maxDepth) {
11752
+ evidence.push({ ruleId, path: "args", preview: `depth=${depth}` });
11753
+ }
11754
+ const maxLen = options.maxStringLength ?? 16384;
11755
+ const longest = maxStringLength(toolArgs2);
11756
+ if (longest > maxLen) {
11757
+ evidence.push({ ruleId, path: "args", preview: `maxStringLength=${longest}` });
11758
+ }
11759
+ if (evidence.length === 0) {
11760
+ return pass(ruleId, "Tool arguments within configured bounds.");
11761
+ }
11762
+ return fail(ruleId, "Unsafe or oversized tool arguments detected.", evidence);
11763
+ }
11764
+ function evaluatePromptInjection(text, options = {}) {
11765
+ const ruleId = "guardrail.prompt-injection";
11766
+ const patterns = options.patterns ?? DEFAULT_INJECTION_PATTERNS;
11767
+ const haystack = text.toLowerCase();
11768
+ const evidence = [];
11769
+ for (const pattern of patterns) {
11770
+ const needle = pattern.toLowerCase();
11771
+ if (needle.length > 0 && haystack.includes(needle)) {
11772
+ evidence.push({ ruleId, match: pattern, preview: boundedPreview(text) });
11773
+ }
11774
+ }
11775
+ if (evidence.length === 0) {
11776
+ return pass(ruleId, "No prompt-injection patterns matched.");
11777
+ }
11778
+ return fail(ruleId, `Matched ${evidence.length} injection pattern(s).`, evidence, "warning");
11779
+ }
11780
+ function validateSchemaField(value, field, path15, evidence) {
11781
+ const ruleId = "guardrail.structured-output";
11782
+ if (field.type) {
11783
+ const actual = value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
11784
+ if (actual !== field.type) {
11785
+ evidence.push({ ruleId, path: path15, preview: `expected ${field.type}, got ${actual}` });
11786
+ return;
11787
+ }
11788
+ }
11789
+ if (field.enum && !field.enum.some((item) => Object.is(item, value))) {
11790
+ evidence.push({ ruleId, path: path15, preview: "value not in enum" });
11791
+ }
11792
+ if (field.type === "object" && field.required && value && typeof value === "object" && !Array.isArray(value)) {
11793
+ const record = value;
11794
+ for (const key of field.required) {
11795
+ if (!(key in record)) {
11796
+ evidence.push({ ruleId, path: `${path15}.${key}`, preview: "missing required key" });
11797
+ }
11798
+ }
11799
+ }
11800
+ }
11801
+ function evaluateStructuredOutput(value, options) {
11802
+ const ruleId = "guardrail.structured-output";
11803
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
11804
+ return fail(ruleId, "Structured output must be an object.", [
11805
+ { ruleId, preview: typeof value }
11806
+ ]);
11807
+ }
11808
+ const record = value;
11809
+ const evidence = [];
11810
+ for (const [key, field] of Object.entries(options.schema)) {
11811
+ validateSchemaField(record[key], field, key, evidence);
11812
+ }
11813
+ if (evidence.length === 0) {
11814
+ return pass(ruleId, "Structured output matches schema subset.");
11815
+ }
11816
+ return fail(ruleId, "Structured output schema violation.", evidence);
11817
+ }
11818
+ function evaluateOversizeOutput(value, options = {}) {
11819
+ const ruleId = "guardrail.oversize-output";
11820
+ const text = typeof value === "string" ? value : JSON.stringify(value);
11821
+ const maxLength = options.maxLength ?? options.maxSerializedLength ?? 32768;
11822
+ if (text.length <= maxLength) {
11823
+ return pass(ruleId, "Output within size limits.");
11824
+ }
11825
+ return fail(ruleId, `Output exceeds max length (${text.length} > ${maxLength}).`, [
11826
+ { ruleId, preview: `length=${text.length}` }
11827
+ ]);
11828
+ }
11829
+ function evaluateRequiredJsonShape(value, options) {
11830
+ const ruleId = "guardrail.required-json-shape";
11831
+ let parsed = value;
11832
+ if (typeof value === "string") {
11833
+ try {
11834
+ parsed = JSON.parse(value);
11835
+ } catch {
11836
+ return fail(ruleId, "Value is not valid JSON.", [{ ruleId, preview: boundedPreview(value) }]);
11837
+ }
11838
+ }
11839
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
11840
+ return fail(ruleId, "JSON value must be an object.", [{ ruleId, preview: typeof parsed }]);
11841
+ }
11842
+ const record = parsed;
11843
+ const evidence = [];
11844
+ for (const key of options.requiredKeys) {
11845
+ if (!(key in record)) {
11846
+ evidence.push({ ruleId, path: key, preview: "missing required key" });
11847
+ }
11848
+ }
11849
+ if (evidence.length === 0) {
11850
+ return pass(ruleId, "Required JSON keys present.");
11851
+ }
11852
+ return fail(ruleId, "Missing required JSON keys.", evidence);
11853
+ }
11854
+
11855
+ // packages/guardrails/src/run.ts
11856
+ var ALL_RULES2 = [
11857
+ "guardrail.banned-phrase",
11858
+ "guardrail.pii-leak",
11859
+ "guardrail.unsafe-tool-args",
11860
+ "guardrail.prompt-injection",
11861
+ "guardrail.structured-output",
11862
+ "guardrail.oversize-output",
11863
+ "guardrail.required-json-shape"
11864
+ ];
11865
+ function isErrorFailure(result) {
11866
+ return result.status === "fail" && result.severity === "error";
11867
+ }
11868
+ function runRule2(ruleId, input3, options) {
11869
+ switch (ruleId) {
11870
+ case "guardrail.banned-phrase": {
11871
+ if (!options.bannedPhrase || input3.text === void 0) return void 0;
11872
+ return evaluateBannedPhrase(input3.text, options.bannedPhrase);
11873
+ }
11874
+ case "guardrail.pii-leak": {
11875
+ if (input3.value === void 0 && input3.text === void 0) return void 0;
11876
+ return evaluatePiiLeak(input3.value ?? input3.text, options.piiLeak);
11877
+ }
11878
+ case "guardrail.unsafe-tool-args": {
11879
+ if (!input3.toolName) return void 0;
11880
+ return evaluateUnsafeToolArgs(input3.toolName, input3.toolArgs ?? {}, options.unsafeToolArgs);
11881
+ }
11882
+ case "guardrail.prompt-injection": {
11883
+ if (input3.text === void 0) return void 0;
11884
+ return evaluatePromptInjection(input3.text, options.promptInjection);
11885
+ }
11886
+ case "guardrail.structured-output": {
11887
+ if (!options.structuredOutput || input3.value === void 0) return void 0;
11888
+ return evaluateStructuredOutput(input3.value, options.structuredOutput);
11889
+ }
11890
+ case "guardrail.oversize-output": {
11891
+ if (input3.value === void 0 && input3.text === void 0) return void 0;
11892
+ return evaluateOversizeOutput(input3.value ?? input3.text, options.oversizeOutput);
11893
+ }
11894
+ case "guardrail.required-json-shape": {
11895
+ if (!options.requiredJsonShape || input3.value === void 0 && input3.text === void 0) return void 0;
11896
+ return evaluateRequiredJsonShape(input3.value ?? input3.text, options.requiredJsonShape);
11897
+ }
11898
+ default:
11899
+ return void 0;
11900
+ }
11901
+ }
11902
+ function runGuardrails(input3, options = {}) {
11903
+ const selected = options.rules ?? ALL_RULES2;
11904
+ const results = [];
11905
+ for (const ruleId of selected) {
11906
+ const result = runRule2(ruleId, input3, options);
11907
+ if (result) results.push(result);
11908
+ }
11909
+ const ok = !results.some(isErrorFailure);
11910
+ return { ok, results };
11911
+ }
11912
+
11913
+ // packages/cli/src/safety-extensions.ts
11914
+ var GUARDRAIL_ALIASES = {
11915
+ "banned-phrase": "guardrail.banned-phrase",
11916
+ "pii-leak": "guardrail.pii-leak",
11917
+ "unsafe-tool-args": "guardrail.unsafe-tool-args",
11918
+ "prompt-injection": "guardrail.prompt-injection",
11919
+ "structured-output": "guardrail.structured-output",
11920
+ "oversize-output": "guardrail.oversize-output",
11921
+ "required-json-shape": "guardrail.required-json-shape"
11922
+ };
11923
+ var CIRCUIT_ALIASES = {
11924
+ "same-tool-repetition": "circuit.same-tool-repetition",
11925
+ "same-args-repetition": "circuit.same-args-repetition",
11926
+ "max-loop-iterations": "circuit.max-loop-iterations",
11927
+ "max-retries": "circuit.max-retries",
11928
+ "tool-timeout": "circuit.tool-timeout",
11929
+ "runaway-llm-loop": "circuit.runaway-llm-loop",
11930
+ "excessive-branch-width": "circuit.excessive-branch-width"
11931
+ };
11932
+ function parseGuardrailRules(values) {
11933
+ if (!values?.length) return void 0;
11934
+ return values.map((value) => {
11935
+ const rule = GUARDRAIL_ALIASES[value] ?? value;
11936
+ return rule;
11937
+ });
11938
+ }
11939
+ function parseCircuitRules(values) {
11940
+ if (!values?.length) return void 0;
11941
+ return values.map((value) => {
11942
+ const rule = CIRCUIT_ALIASES[value] ?? value;
11943
+ return rule;
11944
+ });
11945
+ }
11946
+ function toSeverity(severity) {
11947
+ return severity;
11948
+ }
11949
+ function guardrailFinding(result) {
11950
+ return {
11951
+ ruleId: result.ruleId,
11952
+ severity: toSeverity(result.severity),
11953
+ status: result.status === "pass" ? "pass" : result.status === "warn" ? "warning" : "fail",
11954
+ message: result.message,
11955
+ evidence: result.evidence.map(
11956
+ (item) => ({
11957
+ path: item.preview ? `${item.path ?? "value"} (${item.preview})` : item.path
11958
+ })
11959
+ )
11960
+ };
11961
+ }
11962
+ function circuitFinding(result) {
11963
+ return {
11964
+ ruleId: result.ruleId,
11965
+ severity: toSeverity(result.severity),
11966
+ status: result.status === "closed" ? "pass" : result.status === "warn" ? "warning" : "fail",
11967
+ message: result.message,
11968
+ evidence: result.evidence.map(
11969
+ (item) => ({
11970
+ runId: item.runId,
11971
+ eventId: item.eventId,
11972
+ path: item.path,
11973
+ name: item.toolName
11974
+ })
11975
+ ),
11976
+ actual: result.evidence[0]?.count,
11977
+ expected: result.evidence[0]?.threshold
11978
+ };
11979
+ }
11980
+ function eventToCircuit(event) {
11981
+ return {
11982
+ eventId: event.eventId,
11983
+ runId: event.runId,
11984
+ name: event.name,
11985
+ kind: event.kind,
11986
+ parentId: event.parentId,
11987
+ startedAt: event.startedAt,
11988
+ endedAt: event.endedAt,
11989
+ durationMs: event.durationMs,
11990
+ attributes: event.attributes,
11991
+ status: event.status
11992
+ };
11993
+ }
11994
+ function collectGuardrailInputs(read) {
11995
+ const inputs = [];
11996
+ for (const event of read.events) {
11997
+ const attrs = event.attributes ?? {};
11998
+ for (const key of ["output", "answer", "text", "content", "message"]) {
11999
+ const value = attrs[key];
12000
+ if (typeof value === "string") inputs.push({ text: value });
12001
+ else if (value !== void 0) inputs.push({ value });
12002
+ }
12003
+ if (event.kind === "TOOL" || event.name.startsWith("tool:")) {
12004
+ inputs.push({
12005
+ toolName: String(attrs.toolName ?? attrs.tool ?? event.name),
12006
+ toolArgs: attrs.arguments ?? attrs.args ?? attrs.input
12007
+ });
12008
+ }
12009
+ }
12010
+ return inputs;
12011
+ }
12012
+ var DEFAULT_GUARDRAIL_OPTIONS = {
12013
+ bannedPhrase: { phrases: ["delete all data", "ignore all instructions"] },
12014
+ promptInjection: {},
12015
+ piiLeak: { profile: "share" }
12016
+ };
12017
+ var DEFAULT_CIRCUIT_OPTIONS = {
12018
+ sameToolRepetition: { maxRepeats: 3 },
12019
+ sameArgsRepetition: { maxRepeats: 2 },
12020
+ maxLoopIterations: { maxIterations: 20 },
12021
+ maxRetries: { maxRetries: 3 },
12022
+ toolTimeout: { maxDurationMs: 6e4 },
12023
+ runawayLlmLoop: { maxLlmCalls: 12 },
12024
+ excessiveBranchWidth: { maxWidth: 8 }
12025
+ };
12026
+ function mergeSafetyExtensions(result, read, options) {
12027
+ const findings = [...result.findings];
12028
+ let failed = result.summary.failed;
12029
+ let warnings = result.summary.warnings;
12030
+ let passed = result.summary.passed;
12031
+ const guardrailRules = parseGuardrailRules(options.guardrails);
12032
+ if (guardrailRules) {
12033
+ for (const input3 of collectGuardrailInputs(read)) {
12034
+ const run = runGuardrails(input3, {
12035
+ ...DEFAULT_GUARDRAIL_OPTIONS,
12036
+ rules: guardrailRules
12037
+ });
12038
+ for (const item of run.results) {
12039
+ const finding = guardrailFinding(item);
12040
+ findings.push(finding);
12041
+ if (finding.status === "fail") failed += 1;
12042
+ else if (finding.status === "warning") warnings += 1;
12043
+ else passed += 1;
12044
+ }
12045
+ }
12046
+ }
12047
+ const circuitRules = parseCircuitRules(options.circuits);
12048
+ if (circuitRules) {
12049
+ const circuitRun = runCircuits(
12050
+ read.events.map(eventToCircuit),
12051
+ { ...DEFAULT_CIRCUIT_OPTIONS, rules: circuitRules }
12052
+ );
12053
+ for (const item of circuitRun.results) {
12054
+ const finding = circuitFinding(item);
12055
+ findings.push(finding);
12056
+ if (finding.status === "fail") failed += 1;
12057
+ else if (finding.status === "warning") warnings += 1;
12058
+ else passed += 1;
12059
+ }
12060
+ }
12061
+ const ok = failed === 0 && result.summary.errors === 0;
12062
+ return {
12063
+ ...result,
12064
+ ok,
12065
+ status: failed > 0 ? "fail" : result.status,
12066
+ summary: {
12067
+ passed,
12068
+ failed,
12069
+ warnings,
12070
+ errors: result.summary.errors
12071
+ },
12072
+ findings
12073
+ };
12074
+ }
11460
12075
 
11461
12076
  // packages/cli/src/check.ts
11462
12077
  var DEFAULT_SELECT = ["run.status"];
@@ -11696,12 +12311,19 @@ async function checkCommand(target, options = {}, stdin = process.stdin) {
11696
12311
  }
11697
12312
  );
11698
12313
  perRun.push(
11699
- runTraceChecks(
11700
- { read },
12314
+ mergeSafetyExtensions(
12315
+ runTraceChecks(
12316
+ { read },
12317
+ {
12318
+ rules: built.rules,
12319
+ select: built.select,
12320
+ runId: meta.runId
12321
+ }
12322
+ ),
12323
+ read,
11701
12324
  {
11702
- rules: built.rules,
11703
- select: built.select,
11704
- runId: meta.runId
12325
+ ...options.guardrails ? { guardrails: options.guardrails } : {},
12326
+ ...options.circuit ? { circuits: options.circuit } : {}
11705
12327
  }
11706
12328
  )
11707
12329
  );
@@ -11720,12 +12342,19 @@ async function checkCommand(target, options = {}, stdin = process.stdin) {
11720
12342
  const read = await openTrace(input3, {
11721
12343
  ...options.format !== void 0 ? { format: options.format } : {}
11722
12344
  });
11723
- result = runTraceChecks(
11724
- { read },
12345
+ result = mergeSafetyExtensions(
12346
+ runTraceChecks(
12347
+ { read },
12348
+ {
12349
+ rules: built.rules,
12350
+ select: built.select,
12351
+ ...options.run !== void 0 ? { runId: options.run } : {}
12352
+ }
12353
+ ),
12354
+ read,
11725
12355
  {
11726
- rules: built.rules,
11727
- select: built.select,
11728
- ...options.run !== void 0 ? { runId: options.run } : {}
12356
+ ...options.guardrails ? { guardrails: options.guardrails } : {},
12357
+ ...options.circuit ? { circuits: options.circuit } : {}
11729
12358
  }
11730
12359
  );
11731
12360
  }
@@ -11929,7 +12558,7 @@ function evidenceForEvent(event, path15) {
11929
12558
  }
11930
12559
  ];
11931
12560
  }
11932
- function fail(ruleId, message, evidence, expected, actual) {
12561
+ function fail2(ruleId, message, evidence, expected, actual) {
11933
12562
  return {
11934
12563
  ruleId,
11935
12564
  status: "fail",
@@ -12125,7 +12754,7 @@ var checks = {
12125
12754
  "eval.requireSuccess",
12126
12755
  "run",
12127
12756
  (context) => context.run.status === "ok" ? [] : [
12128
- fail(
12757
+ fail2(
12129
12758
  "eval.requireSuccess",
12130
12759
  "Run did not complete successfully.",
12131
12760
  evidenceForRun(context.run, "status"),
@@ -12140,7 +12769,7 @@ var checks = {
12140
12769
  return createRule("eval.requiredTools", "tool", (context) => {
12141
12770
  const tools = new Set(nodeNames(context.nodes, "TOOL"));
12142
12771
  return expected.filter((name) => !tools.has(name)).map(
12143
- (name) => fail(
12772
+ (name) => fail2(
12144
12773
  "eval.requiredTools",
12145
12774
  `Required tool ${name} did not appear.`,
12146
12775
  evidenceForRun(context.run, "children"),
@@ -12156,7 +12785,7 @@ var checks = {
12156
12785
  "eval.forbiddenTools",
12157
12786
  "tool",
12158
12787
  (context) => context.nodes.filter((node) => node.event.kind === "TOOL" && blocked.includes(node.event.name)).map(
12159
- (node) => fail(
12788
+ (node) => fail2(
12160
12789
  "eval.forbiddenTools",
12161
12790
  `Forbidden tool ${node.event.name} appeared.`,
12162
12791
  evidenceForEvent(node.event, "name"),
@@ -12171,7 +12800,7 @@ var checks = {
12171
12800
  "eval.maxDurationMs",
12172
12801
  "run",
12173
12802
  (context) => context.run.durationMs !== void 0 && context.run.durationMs > maxDurationMs ? [
12174
- fail(
12803
+ fail2(
12175
12804
  "eval.maxDurationMs",
12176
12805
  `Run duration exceeded ${maxDurationMs}ms.`,
12177
12806
  evidenceForRun(context.run, "durationMs"),
@@ -12188,7 +12817,7 @@ var checks = {
12188
12817
  void 0
12189
12818
  );
12190
12819
  return deepest !== void 0 && deepest.depth > maxDepth ? [
12191
- fail(
12820
+ fail2(
12192
12821
  "eval.maxDepth",
12193
12822
  `Run tree depth exceeded ${maxDepth}.`,
12194
12823
  evidenceForEvent(deepest.event, "depth"),
@@ -12205,7 +12834,7 @@ var checks = {
12205
12834
  (context) => context.nodes.flatMap((node) => {
12206
12835
  const retries = numericAttribute(node, ["retryCount", "retries", "attempt"]);
12207
12836
  return retries !== void 0 && retries > maxRetries ? [
12208
- fail(
12837
+ fail2(
12209
12838
  "eval.maxRetries",
12210
12839
  `Retry count exceeded ${maxRetries}.`,
12211
12840
  evidenceForEvent(node.event, "attributes.retryCount"),
@@ -12220,7 +12849,7 @@ var checks = {
12220
12849
  return createRule("eval.maxTotalTokens", "llm", (context) => {
12221
12850
  const total = totalTokenCount(context.events);
12222
12851
  return total > maxTotalTokens ? [
12223
- fail(
12852
+ fail2(
12224
12853
  "eval.maxTotalTokens",
12225
12854
  `Total token usage exceeded ${maxTotalTokens}.`,
12226
12855
  evidenceForRun(context.run, "tokenUsage.total"),
@@ -12235,7 +12864,7 @@ var checks = {
12235
12864
  "eval.noFailedSteps",
12236
12865
  "run",
12237
12866
  (context) => context.nodes.filter((node) => node.event.status === "error" || node.event.kind === "ERROR").map(
12238
- (node) => fail(
12867
+ (node) => fail2(
12239
12868
  "eval.noFailedSteps",
12240
12869
  "Run contains a failed step or error node.",
12241
12870
  evidenceForEvent(node.event, "status"),
@@ -12253,7 +12882,7 @@ var checks = {
12253
12882
  (node, index) => index < firstLlmIndex && node.event.kind === "RETRIEVER"
12254
12883
  );
12255
12884
  return retrievalIndex === -1 ? [
12256
- fail(
12885
+ fail2(
12257
12886
  "eval.requiredRetrievalBeforeGeneration",
12258
12887
  "No retrieval step appeared before the first LLM generation.",
12259
12888
  evidenceForEvent(context.nodes[firstLlmIndex].event, "kind"),
@@ -12269,7 +12898,7 @@ var checks = {
12269
12898
  const decisions = context.nodes.filter((node) => node.event.kind === "DECISION");
12270
12899
  if (decisions.length === 0) {
12271
12900
  return [
12272
- fail(
12901
+ fail2(
12273
12902
  "eval.requiredDecisionMetadata",
12274
12903
  "No decision node is available for required metadata.",
12275
12904
  evidenceForRun(context.run, "children"),
@@ -12280,7 +12909,7 @@ var checks = {
12280
12909
  }
12281
12910
  return decisions.flatMap(
12282
12911
  (node) => required.filter((key) => !hasAttribute(node, key)).map(
12283
- (key) => fail(
12912
+ (key) => fail2(
12284
12913
  "eval.requiredDecisionMetadata",
12285
12914
  `Decision metadata ${key} is missing.`,
12286
12915
  evidenceForEvent(node.event, `attributes.${key}`),
@@ -12301,7 +12930,7 @@ var checks = {
12301
12930
  const contexts = collectTextFields(context.nodes, contextKeys, ["RETRIEVER", "TOOL"]);
12302
12931
  if (answers.length === 0 || contexts.length === 0) {
12303
12932
  return [
12304
- fail(
12933
+ fail2(
12305
12934
  "eval.contextOverlap",
12306
12935
  "Answer and context text are required for overlap evaluation.",
12307
12936
  firstEvidence(answers.length > 0 ? answers : contexts, context.run, "children"),
@@ -12315,7 +12944,7 @@ var checks = {
12315
12944
  const sharedTerms = [...answerTerms].filter((term) => contextTerms.has(term)).length;
12316
12945
  const overlap = answerTerms.size === 0 ? 0 : sharedTerms / answerTerms.size;
12317
12946
  return sharedTerms < minSharedTerms || overlap < minOverlap ? [
12318
- fail(
12947
+ fail2(
12319
12948
  "eval.contextOverlap",
12320
12949
  "Answer text did not sufficiently overlap retrieved context.",
12321
12950
  firstEvidence(answers, context.run, "attributes.answer"),
@@ -12343,7 +12972,7 @@ var checks = {
12343
12972
  const quotes = quotedSnippets(answerText, minQuoteLength);
12344
12973
  if (quotes.length === 0) {
12345
12974
  return requireQuote ? [
12346
- fail(
12975
+ fail2(
12347
12976
  "eval.quoteOverlap",
12348
12977
  "Answer did not contain a quote for overlap evaluation.",
12349
12978
  firstEvidence(answers, context.run, "attributes.answer"),
@@ -12354,7 +12983,7 @@ var checks = {
12354
12983
  }
12355
12984
  const missing = quotes.filter((quote) => !contextText.includes(quote.toLowerCase()));
12356
12985
  return missing.length > 0 ? [
12357
- fail(
12986
+ fail2(
12358
12987
  "eval.quoteOverlap",
12359
12988
  "Quoted answer text did not appear in retrieved context.",
12360
12989
  firstEvidence(answers, context.run, "attributes.answer"),
@@ -12372,7 +13001,7 @@ var checks = {
12372
13001
  const citations = collectTextFields(context.nodes, citationKeys);
12373
13002
  const count = citationCount(answers.map((field) => field.text).join(" "), citations);
12374
13003
  return count === 0 ? [
12375
- fail(
13004
+ fail2(
12376
13005
  "eval.citationPresence",
12377
13006
  "Answer did not include citations or source references.",
12378
13007
  firstEvidence(answers, context.run, "attributes.answer"),
@@ -12390,7 +13019,7 @@ var checks = {
12390
13019
  const availableSet = new Set(available);
12391
13020
  const missing = expected.filter((id) => !availableSet.has(id));
12392
13021
  return missing.length > 0 ? [
12393
- fail(
13022
+ fail2(
12394
13023
  "eval.requiredSourceIds",
12395
13024
  "Required source IDs were not present in trace context or citations.",
12396
13025
  evidenceForRun(context.run, "children"),
@@ -12410,7 +13039,7 @@ var checks = {
12410
13039
  const tooShort = options.minCharacters !== void 0 && characters < options.minCharacters || options.minWords !== void 0 && words < options.minWords;
12411
13040
  const tooLong = options.maxCharacters !== void 0 && characters > options.maxCharacters || options.maxWords !== void 0 && words > options.maxWords;
12412
13041
  return answer.length === 0 || tooShort || tooLong ? [
12413
- fail(
13042
+ fail2(
12414
13043
  "eval.answerLengthBounds",
12415
13044
  "Answer length fell outside required bounds.",
12416
13045
  firstEvidence(answers, context.run, "attributes.answer"),
@@ -12433,7 +13062,7 @@ var checks = {
12433
13062
  const answer = answers.map((field) => field.text).join(" ").toLowerCase();
12434
13063
  const matches = banned.filter((phrase) => answer.includes(phrase));
12435
13064
  return matches.length > 0 ? [
12436
- fail(
13065
+ fail2(
12437
13066
  "eval.bannedUnsupportedPhrases",
12438
13067
  "Answer contained banned unsupported-answer phrasing.",
12439
13068
  firstEvidence(answers, context.run, "attributes.answer"),
@@ -12811,7 +13440,7 @@ function invalidArgumentResult(command, error) {
12811
13440
  });
12812
13441
  }
12813
13442
  function buildSafetyRules(options) {
12814
- const maxStringLength = parseLimit3(options.maxStringLength, "--max-string-length") ?? DEFAULT_MAX_STRING_LENGTH;
13443
+ const maxStringLength2 = parseLimit3(options.maxStringLength, "--max-string-length") ?? DEFAULT_MAX_STRING_LENGTH;
12815
13444
  const maxArrayLength = parseLimit3(options.maxArrayLength, "--max-array-length") ?? DEFAULT_MAX_ARRAY_LENGTH;
12816
13445
  const maxObjectKeys = parseLimit3(options.maxObjectKeys, "--max-object-keys") ?? DEFAULT_MAX_OBJECT_KEYS;
12817
13446
  const maxSerializedBytes = parseLimit3(options.maxSerializedBytes, "--max-serialized-bytes") ?? DEFAULT_MAX_SERIALIZED_BYTES;
@@ -12820,7 +13449,7 @@ function buildSafetyRules(options) {
12820
13449
  createSafetyRedactionRule(),
12821
13450
  createSafetySecretPatternRule(),
12822
13451
  createSafetyOversizedAttributeRule({
12823
- maxStringLength,
13452
+ maxStringLength: maxStringLength2,
12824
13453
  maxArrayLength,
12825
13454
  maxObjectKeys,
12826
13455
  maxSerializedBytes
@@ -13704,6 +14333,14 @@ function createCliProgram() {
13704
14333
  ]).option("--max-total-tokens <number>", "add llm.usage with a max total-token budget").option("--session <id>", "check all runs in a workflow session (requires --dir)").option("--group <id>", "check all runs sharing a groupId (requires --dir)").option(
13705
14334
  "--correlate-group",
13706
14335
  "when using --session, also match synthetic group: session keys"
14336
+ ).option(
14337
+ "--guardrails <rule>",
14338
+ "run optional guardrail rules (repeatable): banned-phrase, pii-leak, prompt-injection, ...",
14339
+ (value, previous = []) => [...previous, value]
14340
+ ).option(
14341
+ "--circuit <rule>",
14342
+ "run optional circuit rules (repeatable): same-tool-repetition, max-retries, ...",
14343
+ (value, previous = []) => [...previous, value]
13707
14344
  ).action((target, opts) => {
13708
14345
  runCommand(() => checkCommand(target, opts));
13709
14346
  });