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.
- package/CHANGELOG.md +8 -0
- package/README.md +2 -2
- package/docs/CLI.md +4 -1
- package/docs/LIMITATIONS.md +6 -0
- package/package.json +2 -2
- package/packages/cli/dist/index.cjs +703 -66
- package/packages/cli/dist/index.cjs.map +1 -1
- package/packages/cli/dist/index.mjs +704 -67
- package/packages/cli/dist/index.mjs.map +1 -1
|
@@ -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.
|
|
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 = (
|
|
1443
|
+
var createStyler = (open3, close, parent) => {
|
|
1444
1444
|
let openAll;
|
|
1445
1445
|
let closeAll;
|
|
1446
1446
|
if (parent === void 0) {
|
|
1447
|
-
openAll =
|
|
1447
|
+
openAll = open3;
|
|
1448
1448
|
closeAll = close;
|
|
1449
1449
|
} else {
|
|
1450
|
-
openAll = parent.openAll +
|
|
1450
|
+
openAll = parent.openAll + open3;
|
|
1451
1451
|
closeAll = close + parent.closeAll;
|
|
1452
1452
|
}
|
|
1453
1453
|
return {
|
|
1454
|
-
open:
|
|
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,
|
|
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" &&
|
|
1523
|
+
if (status === "running" && durationMs2 === void 0) {
|
|
1524
1524
|
return `${ind}${source_default.yellow("\u23F3")} ${nm}`;
|
|
1525
1525
|
}
|
|
1526
|
-
const hasDur =
|
|
1527
|
-
const dur = hasDur ? formatDuration2(
|
|
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
|
|
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
|
|
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,
|
|
3229
|
+
function durationCell(status, durationMs2) {
|
|
3230
3230
|
if (status === "running" || status === "unknown") return "-";
|
|
3231
|
-
if (
|
|
3232
|
-
return formatDuration2(
|
|
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
|
|
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
|
|
4593
|
-
if (
|
|
4594
|
-
event.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
|
|
5180
|
-
if (
|
|
5181
|
-
event.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
|
|
6186
|
+
let durationMs2;
|
|
6187
6187
|
const durationKey = cfg.durationKey;
|
|
6188
6188
|
if (durationKey) {
|
|
6189
6189
|
const v = raw[durationKey];
|
|
6190
|
-
if (isFiniteNumber2(v))
|
|
6190
|
+
if (isFiniteNumber2(v)) durationMs2 = v;
|
|
6191
6191
|
} else if (isFiniteNumber2(raw.durationMs)) {
|
|
6192
|
-
|
|
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
|
-
...
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
|
|
11700
|
-
|
|
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
|
-
|
|
11703
|
-
|
|
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 =
|
|
11724
|
-
|
|
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
|
-
|
|
11727
|
-
|
|
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
|
|
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
|
-
|
|
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) =>
|
|
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) =>
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) =>
|
|
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
|
-
|
|
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
|
-
|
|
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) =>
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
});
|