agent-inspect 6.17.3 → 6.17.5
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 +12 -0
- package/README.md +1 -1
- package/docs/ADAPTER-CONFORMANCE.md +24 -0
- package/docs/ADAPTERS.md +3 -3
- package/docs/API.md +13 -7
- package/docs/CI-ARTIFACTS.md +2 -1
- package/docs/CLI.md +2 -0
- package/docs/LIMITATIONS.md +1 -0
- package/docs/OPENAI-AGENTS-LOCAL.md +14 -0
- package/docs/SAFE-TRACE-SHARING.md +1 -0
- package/docs/SELF-HOSTING.md +2 -0
- package/docs/STANDARDS.md +15 -0
- package/docs/SUPPORT-LEVELS.md +23 -0
- package/docs/TRACE-CONTRACTS.md +90 -0
- package/package.json +7 -3
- package/packages/cli/dist/{chunk-5GGYDIZD.mjs → chunk-GOHYSE7W.mjs} +93 -19
- package/packages/cli/dist/chunk-GOHYSE7W.mjs.map +1 -0
- package/packages/cli/dist/index.cjs +505 -88
- package/packages/cli/dist/index.cjs.map +1 -1
- package/packages/cli/dist/index.mjs +417 -74
- package/packages/cli/dist/index.mjs.map +1 -1
- package/packages/cli/dist/{src-IDMCWKRH.mjs → src-OYGG7QAZ.mjs} +3 -3
- package/packages/cli/dist/{src-IDMCWKRH.mjs.map → src-OYGG7QAZ.mjs.map} +1 -1
- package/packages/core/dist/advanced.cjs +86 -17
- package/packages/core/dist/advanced.cjs.map +1 -1
- package/packages/core/dist/advanced.d.cts +1 -1
- package/packages/core/dist/advanced.d.ts +1 -1
- package/packages/core/dist/advanced.mjs +15 -5
- package/packages/core/dist/advanced.mjs.map +1 -1
- package/packages/core/dist/checks.cjs +126 -28
- package/packages/core/dist/checks.cjs.map +1 -1
- package/packages/core/dist/checks.d.cts +28 -2
- package/packages/core/dist/checks.d.ts +28 -2
- package/packages/core/dist/checks.mjs +1 -1
- package/packages/core/dist/{chunk-UFP54T7F.mjs → chunk-DIZPIPY2.mjs} +128 -30
- package/packages/core/dist/chunk-DIZPIPY2.mjs.map +1 -0
- package/packages/core/dist/{index-Xk9X-yjY.d.cts → index-DWu54Y28.d.cts} +107 -122
- package/packages/core/dist/{index-BsCcOKxy.d.ts → index-DlwbVqEs.d.ts} +107 -122
- package/packages/cli/dist/chunk-5GGYDIZD.mjs.map +0 -1
- package/packages/core/dist/chunk-UFP54T7F.mjs.map +0 -1
|
@@ -118,8 +118,11 @@ function outcomesMatchingStatus(outcomes, statuses) {
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
// packages/core/src/safety/sensitive-key.ts
|
|
121
|
+
function keyHasExplicitSeparator(value) {
|
|
122
|
+
return /[_\-.]/.test(value);
|
|
123
|
+
}
|
|
121
124
|
function normalizeSensitiveKey(value) {
|
|
122
|
-
return value.toLowerCase().replace(/[^a-z0-
|
|
125
|
+
return value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
123
126
|
}
|
|
124
127
|
var NON_CREDENTIAL_TOKEN_CONFIG_KEYS = new Set(
|
|
125
128
|
[
|
|
@@ -171,6 +174,7 @@ function isCredentialSensitiveKey(key, sensitiveKeys = DEFAULT_CREDENTIAL_SENSIT
|
|
|
171
174
|
const normalized = normalizeSensitiveKey(key);
|
|
172
175
|
if (!normalized) return false;
|
|
173
176
|
if (NON_CREDENTIAL_TOKEN_CONFIG_KEYS.has(normalized)) return false;
|
|
177
|
+
const allowPrefixCompound = keyHasExplicitSeparator(key);
|
|
174
178
|
for (const sensitive of sensitiveKeys) {
|
|
175
179
|
const s = normalizeSensitiveKey(sensitive);
|
|
176
180
|
if (!s) continue;
|
|
@@ -179,7 +183,8 @@ function isCredentialSensitiveKey(key, sensitiveKeys = DEFAULT_CREDENTIAL_SENSIT
|
|
|
179
183
|
continue;
|
|
180
184
|
}
|
|
181
185
|
if (normalized === s) return true;
|
|
182
|
-
if (normalized.endsWith(`_${s}`)
|
|
186
|
+
if (normalized.endsWith(`_${s}`)) return true;
|
|
187
|
+
if (allowPrefixCompound && normalized.startsWith(`${s}_`)) return true;
|
|
183
188
|
}
|
|
184
189
|
return false;
|
|
185
190
|
}
|
|
@@ -668,9 +673,13 @@ function contractToRules(contract) {
|
|
|
668
673
|
rules.push(createStructureIncompleteRule({ requireEndedAtForStarted: true }));
|
|
669
674
|
}
|
|
670
675
|
if (contract.tools) {
|
|
676
|
+
const order = contract.tools.requiredOrder ?? [];
|
|
671
677
|
const required = [
|
|
672
|
-
|
|
673
|
-
|
|
678
|
+
.../* @__PURE__ */ new Set([
|
|
679
|
+
...contract.tools.required ?? [],
|
|
680
|
+
...contract.tools.requiredTools ?? [],
|
|
681
|
+
...order
|
|
682
|
+
])
|
|
674
683
|
];
|
|
675
684
|
const forbidden = [
|
|
676
685
|
...contract.tools.forbidden ?? [],
|
|
@@ -684,9 +693,14 @@ function contractToRules(contract) {
|
|
|
684
693
|
...contract.tools.maxCalls !== void 0 ? { maxCount: contract.tools.maxCalls } : {}
|
|
685
694
|
})
|
|
686
695
|
);
|
|
687
|
-
const order = contract.tools.requiredOrder ?? [];
|
|
688
696
|
for (let i = 0; i < order.length - 1; i += 1) {
|
|
689
|
-
rules.push(
|
|
697
|
+
rules.push(
|
|
698
|
+
createToolOrderingRule({
|
|
699
|
+
before: order[i],
|
|
700
|
+
after: order[i + 1],
|
|
701
|
+
id: `contract.tool.order.${i}`
|
|
702
|
+
})
|
|
703
|
+
);
|
|
690
704
|
}
|
|
691
705
|
}
|
|
692
706
|
if (contract.llm) {
|
|
@@ -846,10 +860,11 @@ function emptySummary() {
|
|
|
846
860
|
passed: 0,
|
|
847
861
|
failed: 0,
|
|
848
862
|
warnings: 0,
|
|
849
|
-
errors: 0
|
|
863
|
+
errors: 0,
|
|
864
|
+
rulesEvaluated: 0
|
|
850
865
|
};
|
|
851
866
|
}
|
|
852
|
-
function errorResult(input, diagnostics, selectedRun) {
|
|
867
|
+
function errorResult(input, diagnostics, selectedRun, ruleExecutions = []) {
|
|
853
868
|
return {
|
|
854
869
|
ok: false,
|
|
855
870
|
status: "error",
|
|
@@ -857,10 +872,12 @@ function errorResult(input, diagnostics, selectedRun) {
|
|
|
857
872
|
...selectedRun ? { runId: selectedRun.runId } : {},
|
|
858
873
|
summary: {
|
|
859
874
|
...emptySummary(),
|
|
860
|
-
errors: diagnostics.filter((item) => item.severity === "error").length
|
|
875
|
+
errors: diagnostics.filter((item) => item.severity === "error").length,
|
|
876
|
+
rulesEvaluated: ruleExecutions.length
|
|
861
877
|
},
|
|
862
878
|
findings: [],
|
|
863
|
-
diagnostics: [...diagnostics]
|
|
879
|
+
diagnostics: [...diagnostics],
|
|
880
|
+
ruleExecutions: [...ruleExecutions]
|
|
864
881
|
};
|
|
865
882
|
}
|
|
866
883
|
function flattenNodes(nodes) {
|
|
@@ -1016,7 +1033,7 @@ function normalizeFinding(rule, finding) {
|
|
|
1016
1033
|
...finding.action !== void 0 ? { action: finding.action } : {}
|
|
1017
1034
|
};
|
|
1018
1035
|
}
|
|
1019
|
-
function summarize(findings, diagnostics) {
|
|
1036
|
+
function summarize(findings, diagnostics, rulesEvaluated) {
|
|
1020
1037
|
return {
|
|
1021
1038
|
passed: findings.filter((finding) => finding.status === "pass").length,
|
|
1022
1039
|
failed: findings.filter(
|
|
@@ -1025,9 +1042,21 @@ function summarize(findings, diagnostics) {
|
|
|
1025
1042
|
warnings: findings.filter(
|
|
1026
1043
|
(finding) => finding.status === "warning" || finding.severity === "warning"
|
|
1027
1044
|
).length,
|
|
1028
|
-
errors: diagnostics.filter((item) => item.severity === "error").length
|
|
1045
|
+
errors: diagnostics.filter((item) => item.severity === "error").length,
|
|
1046
|
+
rulesEvaluated
|
|
1029
1047
|
};
|
|
1030
1048
|
}
|
|
1049
|
+
function classifyRuleExecution(findings, threw) {
|
|
1050
|
+
if (findings.some((finding) => finding.status === "fail" && finding.severity === "error")) {
|
|
1051
|
+
return "fail";
|
|
1052
|
+
}
|
|
1053
|
+
if (findings.some(
|
|
1054
|
+
(finding) => finding.status === "warning" || finding.severity === "warning"
|
|
1055
|
+
)) {
|
|
1056
|
+
return "warning";
|
|
1057
|
+
}
|
|
1058
|
+
return "pass";
|
|
1059
|
+
}
|
|
1031
1060
|
function stringAttr(event, keys) {
|
|
1032
1061
|
for (const key of keys) {
|
|
1033
1062
|
const value = event.attributes?.[key];
|
|
@@ -1124,6 +1153,9 @@ function finishedEvents(context, kind) {
|
|
|
1124
1153
|
(event) => (kind === void 0 || event.kind === kind) && event.status !== "running"
|
|
1125
1154
|
);
|
|
1126
1155
|
}
|
|
1156
|
+
function toolInvocationEvents(context) {
|
|
1157
|
+
return semanticEvents(context).filter((event) => event.kind === "TOOL");
|
|
1158
|
+
}
|
|
1127
1159
|
function firstIndexByName(events) {
|
|
1128
1160
|
const index = /* @__PURE__ */ new Map();
|
|
1129
1161
|
for (const [position, event] of events.entries()) {
|
|
@@ -1571,7 +1603,7 @@ function createToolUsageRule(options) {
|
|
|
1571
1603
|
category: "tool",
|
|
1572
1604
|
defaultSeverity: "error",
|
|
1573
1605
|
evaluate(context) {
|
|
1574
|
-
const tools =
|
|
1606
|
+
const tools = toolInvocationEvents(context);
|
|
1575
1607
|
const names = tools.map(toolName);
|
|
1576
1608
|
const nameSet = new Set(names);
|
|
1577
1609
|
const findings = [];
|
|
@@ -1612,8 +1644,9 @@ function createToolUsageRule(options) {
|
|
|
1612
1644
|
};
|
|
1613
1645
|
}
|
|
1614
1646
|
function createToolOrderingRule(options) {
|
|
1647
|
+
const ruleId = options.id ?? "tool.order";
|
|
1615
1648
|
return {
|
|
1616
|
-
id:
|
|
1649
|
+
id: ruleId,
|
|
1617
1650
|
category: "tool",
|
|
1618
1651
|
defaultSeverity: "error",
|
|
1619
1652
|
evaluate(context) {
|
|
@@ -1621,18 +1654,42 @@ function createToolOrderingRule(options) {
|
|
|
1621
1654
|
const index = firstIndexByName(tools);
|
|
1622
1655
|
const beforeIndex = index.get(options.before);
|
|
1623
1656
|
const afterIndex = index.get(options.after);
|
|
1624
|
-
if (beforeIndex === void 0 || afterIndex === void 0
|
|
1657
|
+
if (beforeIndex === void 0 || afterIndex === void 0) {
|
|
1625
1658
|
return [];
|
|
1626
1659
|
}
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1660
|
+
if (beforeIndex >= afterIndex) {
|
|
1661
|
+
return [
|
|
1662
|
+
failFinding(
|
|
1663
|
+
ruleId,
|
|
1664
|
+
`Tool ${options.before} must appear before ${options.after}.`,
|
|
1665
|
+
[eventEvidence(tools[beforeIndex]), eventEvidence(tools[afterIndex])],
|
|
1666
|
+
{ before: options.before, after: options.after },
|
|
1667
|
+
tools.map(toolName)
|
|
1668
|
+
)
|
|
1669
|
+
];
|
|
1670
|
+
}
|
|
1671
|
+
const beforeEvent = tools[beforeIndex];
|
|
1672
|
+
const afterEvent = tools[afterIndex];
|
|
1673
|
+
const beforeEnd = eventEndMs(beforeEvent);
|
|
1674
|
+
const afterStart = eventStartMs(afterEvent);
|
|
1675
|
+
if (beforeEnd !== void 0 && afterStart !== void 0 && beforeEnd > afterStart) {
|
|
1676
|
+
return [
|
|
1677
|
+
{
|
|
1678
|
+
ruleId,
|
|
1679
|
+
severity: "warning",
|
|
1680
|
+
status: "warning",
|
|
1681
|
+
message: `Tool order ${options.before} before ${options.after} satisfied start/encounter order, but intervals overlap so causal completion before the next tool was not established.`,
|
|
1682
|
+
expected: { before: options.before, after: options.after, mode: "first-occurrence" },
|
|
1683
|
+
actual: {
|
|
1684
|
+
code: "tool.order.overlap",
|
|
1685
|
+
beforeEndedAt: beforeEvent.endedAt,
|
|
1686
|
+
afterStartedAt: afterEvent.startedAt ?? afterEvent.timestamp
|
|
1687
|
+
},
|
|
1688
|
+
evidence: [eventEvidence(beforeEvent), eventEvidence(afterEvent)]
|
|
1689
|
+
}
|
|
1690
|
+
];
|
|
1691
|
+
}
|
|
1692
|
+
return [];
|
|
1636
1693
|
}
|
|
1637
1694
|
};
|
|
1638
1695
|
}
|
|
@@ -2429,12 +2486,24 @@ function createBaselineRegressionRule(options) {
|
|
|
2429
2486
|
}
|
|
2430
2487
|
function createObservedOutcomeRule(options = {}) {
|
|
2431
2488
|
const failOn = options.failOn ?? ["failed"];
|
|
2489
|
+
const requireAny = options.requireAny === true;
|
|
2432
2490
|
return {
|
|
2433
2491
|
id: "outcome.status",
|
|
2434
2492
|
category: "run",
|
|
2435
2493
|
defaultSeverity: "error",
|
|
2436
2494
|
evaluate(context) {
|
|
2437
2495
|
const outcomes = extractOutcomesFromPersistedEvents(context.events);
|
|
2496
|
+
if (requireAny && outcomes.length === 0) {
|
|
2497
|
+
return [
|
|
2498
|
+
failFinding(
|
|
2499
|
+
"outcome.status",
|
|
2500
|
+
"Expected at least one observed outcome.",
|
|
2501
|
+
runEvidence(context.selectedRun),
|
|
2502
|
+
{ requireAny: true, expected: "at least one observed outcome" },
|
|
2503
|
+
{ code: "outcome.missing", actual: 0 }
|
|
2504
|
+
)
|
|
2505
|
+
];
|
|
2506
|
+
}
|
|
2438
2507
|
const matching = outcomesMatchingStatus(outcomes, failOn);
|
|
2439
2508
|
if (matching.length === 0) return [];
|
|
2440
2509
|
return [
|
|
@@ -2470,6 +2539,18 @@ function runTraceChecks(input, options = {}) {
|
|
|
2470
2539
|
if (rules.diagnostics.length > 0) {
|
|
2471
2540
|
return errorResult(input, rules.diagnostics, selected.run);
|
|
2472
2541
|
}
|
|
2542
|
+
if (rules.rules.length === 0) {
|
|
2543
|
+
return errorResult(
|
|
2544
|
+
input,
|
|
2545
|
+
[
|
|
2546
|
+
diagnostic(
|
|
2547
|
+
"AI_CHECK_NO_RULES_EVALUATED",
|
|
2548
|
+
"No trace check rules were evaluated. Configure at least one rule, contract, or CLI check option."
|
|
2549
|
+
)
|
|
2550
|
+
],
|
|
2551
|
+
selected.run
|
|
2552
|
+
);
|
|
2553
|
+
}
|
|
2473
2554
|
const facts = buildFacts(input, selected.run);
|
|
2474
2555
|
const context = {
|
|
2475
2556
|
...facts,
|
|
@@ -2478,22 +2559,38 @@ function runTraceChecks(input, options = {}) {
|
|
|
2478
2559
|
};
|
|
2479
2560
|
const diagnostics = [];
|
|
2480
2561
|
const findings = [];
|
|
2562
|
+
const ruleExecutions = [];
|
|
2481
2563
|
for (const rule of rules.rules) {
|
|
2482
2564
|
try {
|
|
2483
|
-
|
|
2565
|
+
const ruleFindings = rule.evaluate(context).map((finding) => normalizeFinding(rule, finding));
|
|
2566
|
+
findings.push(...ruleFindings);
|
|
2567
|
+
ruleExecutions.push({
|
|
2568
|
+
ruleId: rule.id,
|
|
2569
|
+
category: rule.category,
|
|
2570
|
+
status: classifyRuleExecution(ruleFindings, false),
|
|
2571
|
+
findingCount: ruleFindings.length,
|
|
2572
|
+
...selected.run ? { runId: selected.run.runId } : {}
|
|
2573
|
+
});
|
|
2484
2574
|
} catch (error) {
|
|
2485
2575
|
const message = error instanceof Error ? error.message : String(error);
|
|
2486
2576
|
diagnostics.push(
|
|
2487
2577
|
diagnostic("AI_CHECK_INTERNAL_ERROR", `Rule ${rule.id} failed: ${message}`, rule.id)
|
|
2488
2578
|
);
|
|
2579
|
+
ruleExecutions.push({
|
|
2580
|
+
ruleId: rule.id,
|
|
2581
|
+
category: rule.category,
|
|
2582
|
+
status: "error",
|
|
2583
|
+
findingCount: 0,
|
|
2584
|
+
...selected.run ? { runId: selected.run.runId } : {}
|
|
2585
|
+
});
|
|
2489
2586
|
}
|
|
2490
2587
|
}
|
|
2491
2588
|
if (diagnostics.length > 0) {
|
|
2492
|
-
return errorResult(input, diagnostics, selected.run);
|
|
2589
|
+
return errorResult(input, diagnostics, selected.run, ruleExecutions);
|
|
2493
2590
|
}
|
|
2494
2591
|
const eventById = new Map(input.read.events.map((event) => [event.eventId, event]));
|
|
2495
2592
|
const sortedFindings = findings.sort(compareFindings(eventById));
|
|
2496
|
-
const summary = summarize(sortedFindings, diagnostics);
|
|
2593
|
+
const summary = summarize(sortedFindings, diagnostics, ruleExecutions.length);
|
|
2497
2594
|
const status = summary.failed > 0 ? "fail" : "pass";
|
|
2498
2595
|
return {
|
|
2499
2596
|
ok: status === "pass",
|
|
@@ -2502,7 +2599,8 @@ function runTraceChecks(input, options = {}) {
|
|
|
2502
2599
|
...selected.run ? { runId: selected.run.runId } : {},
|
|
2503
2600
|
summary,
|
|
2504
2601
|
findings: sortedFindings,
|
|
2505
|
-
diagnostics
|
|
2602
|
+
diagnostics,
|
|
2603
|
+
ruleExecutions
|
|
2506
2604
|
};
|
|
2507
2605
|
}
|
|
2508
2606
|
|