agent-inspect 6.17.4 → 6.17.6

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.
Files changed (33) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +1 -1
  3. package/docs/ADAPTERS.md +3 -3
  4. package/docs/API.md +13 -7
  5. package/docs/CLI.md +2 -0
  6. package/docs/LIMITATIONS.md +1 -0
  7. package/docs/TRACE-CONTRACTS.md +90 -0
  8. package/package.json +8 -5
  9. package/packages/cli/dist/{chunk-YX6RFLV5.mjs → chunk-GOHYSE7W.mjs} +86 -17
  10. package/packages/cli/dist/chunk-GOHYSE7W.mjs.map +1 -0
  11. package/packages/cli/dist/index.cjs +522 -81
  12. package/packages/cli/dist/index.cjs.map +1 -1
  13. package/packages/cli/dist/index.mjs +441 -69
  14. package/packages/cli/dist/index.mjs.map +1 -1
  15. package/packages/cli/dist/{src-X6SLMBNH.mjs → src-OYGG7QAZ.mjs} +3 -3
  16. package/packages/cli/dist/{src-X6SLMBNH.mjs.map → src-OYGG7QAZ.mjs.map} +1 -1
  17. package/packages/core/dist/advanced.cjs +85 -16
  18. package/packages/core/dist/advanced.cjs.map +1 -1
  19. package/packages/core/dist/advanced.d.cts +1 -1
  20. package/packages/core/dist/advanced.d.ts +1 -1
  21. package/packages/core/dist/advanced.mjs +15 -5
  22. package/packages/core/dist/advanced.mjs.map +1 -1
  23. package/packages/core/dist/checks.cjs +119 -26
  24. package/packages/core/dist/checks.cjs.map +1 -1
  25. package/packages/core/dist/checks.d.cts +28 -2
  26. package/packages/core/dist/checks.d.ts +28 -2
  27. package/packages/core/dist/checks.mjs +1 -1
  28. package/packages/core/dist/{chunk-HN377P23.mjs → chunk-DIZPIPY2.mjs} +121 -28
  29. package/packages/core/dist/chunk-DIZPIPY2.mjs.map +1 -0
  30. package/packages/core/dist/{index-Xk9X-yjY.d.cts → index-DWu54Y28.d.cts} +107 -122
  31. package/packages/core/dist/{index-BsCcOKxy.d.ts → index-DlwbVqEs.d.ts} +107 -122
  32. package/packages/cli/dist/chunk-YX6RFLV5.mjs.map +0 -1
  33. package/packages/core/dist/chunk-HN377P23.mjs.map +0 -1
@@ -673,9 +673,13 @@ function contractToRules(contract) {
673
673
  rules.push(createStructureIncompleteRule({ requireEndedAtForStarted: true }));
674
674
  }
675
675
  if (contract.tools) {
676
+ const order = contract.tools.requiredOrder ?? [];
676
677
  const required = [
677
- ...contract.tools.required ?? [],
678
- ...contract.tools.requiredTools ?? []
678
+ .../* @__PURE__ */ new Set([
679
+ ...contract.tools.required ?? [],
680
+ ...contract.tools.requiredTools ?? [],
681
+ ...order
682
+ ])
679
683
  ];
680
684
  const forbidden = [
681
685
  ...contract.tools.forbidden ?? [],
@@ -689,9 +693,14 @@ function contractToRules(contract) {
689
693
  ...contract.tools.maxCalls !== void 0 ? { maxCount: contract.tools.maxCalls } : {}
690
694
  })
691
695
  );
692
- const order = contract.tools.requiredOrder ?? [];
693
696
  for (let i = 0; i < order.length - 1; i += 1) {
694
- rules.push(createToolOrderingRule({ before: order[i], after: order[i + 1] }));
697
+ rules.push(
698
+ createToolOrderingRule({
699
+ before: order[i],
700
+ after: order[i + 1],
701
+ id: `contract.tool.order.${i}`
702
+ })
703
+ );
695
704
  }
696
705
  }
697
706
  if (contract.llm) {
@@ -851,10 +860,11 @@ function emptySummary() {
851
860
  passed: 0,
852
861
  failed: 0,
853
862
  warnings: 0,
854
- errors: 0
863
+ errors: 0,
864
+ rulesEvaluated: 0
855
865
  };
856
866
  }
857
- function errorResult(input, diagnostics, selectedRun) {
867
+ function errorResult(input, diagnostics, selectedRun, ruleExecutions = []) {
858
868
  return {
859
869
  ok: false,
860
870
  status: "error",
@@ -862,10 +872,12 @@ function errorResult(input, diagnostics, selectedRun) {
862
872
  ...selectedRun ? { runId: selectedRun.runId } : {},
863
873
  summary: {
864
874
  ...emptySummary(),
865
- errors: diagnostics.filter((item) => item.severity === "error").length
875
+ errors: diagnostics.filter((item) => item.severity === "error").length,
876
+ rulesEvaluated: ruleExecutions.length
866
877
  },
867
878
  findings: [],
868
- diagnostics: [...diagnostics]
879
+ diagnostics: [...diagnostics],
880
+ ruleExecutions: [...ruleExecutions]
869
881
  };
870
882
  }
871
883
  function flattenNodes(nodes) {
@@ -1021,7 +1033,7 @@ function normalizeFinding(rule, finding) {
1021
1033
  ...finding.action !== void 0 ? { action: finding.action } : {}
1022
1034
  };
1023
1035
  }
1024
- function summarize(findings, diagnostics) {
1036
+ function summarize(findings, diagnostics, rulesEvaluated) {
1025
1037
  return {
1026
1038
  passed: findings.filter((finding) => finding.status === "pass").length,
1027
1039
  failed: findings.filter(
@@ -1030,9 +1042,21 @@ function summarize(findings, diagnostics) {
1030
1042
  warnings: findings.filter(
1031
1043
  (finding) => finding.status === "warning" || finding.severity === "warning"
1032
1044
  ).length,
1033
- errors: diagnostics.filter((item) => item.severity === "error").length
1045
+ errors: diagnostics.filter((item) => item.severity === "error").length,
1046
+ rulesEvaluated
1034
1047
  };
1035
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
+ }
1036
1060
  function stringAttr(event, keys) {
1037
1061
  for (const key of keys) {
1038
1062
  const value = event.attributes?.[key];
@@ -1129,6 +1153,9 @@ function finishedEvents(context, kind) {
1129
1153
  (event) => (kind === void 0 || event.kind === kind) && event.status !== "running"
1130
1154
  );
1131
1155
  }
1156
+ function toolInvocationEvents(context) {
1157
+ return semanticEvents(context).filter((event) => event.kind === "TOOL");
1158
+ }
1132
1159
  function firstIndexByName(events) {
1133
1160
  const index = /* @__PURE__ */ new Map();
1134
1161
  for (const [position, event] of events.entries()) {
@@ -1576,7 +1603,7 @@ function createToolUsageRule(options) {
1576
1603
  category: "tool",
1577
1604
  defaultSeverity: "error",
1578
1605
  evaluate(context) {
1579
- const tools = finishedEvents(context, "TOOL");
1606
+ const tools = toolInvocationEvents(context);
1580
1607
  const names = tools.map(toolName);
1581
1608
  const nameSet = new Set(names);
1582
1609
  const findings = [];
@@ -1617,8 +1644,9 @@ function createToolUsageRule(options) {
1617
1644
  };
1618
1645
  }
1619
1646
  function createToolOrderingRule(options) {
1647
+ const ruleId = options.id ?? "tool.order";
1620
1648
  return {
1621
- id: "tool.order",
1649
+ id: ruleId,
1622
1650
  category: "tool",
1623
1651
  defaultSeverity: "error",
1624
1652
  evaluate(context) {
@@ -1626,18 +1654,42 @@ function createToolOrderingRule(options) {
1626
1654
  const index = firstIndexByName(tools);
1627
1655
  const beforeIndex = index.get(options.before);
1628
1656
  const afterIndex = index.get(options.after);
1629
- if (beforeIndex === void 0 || afterIndex === void 0 || beforeIndex < afterIndex) {
1657
+ if (beforeIndex === void 0 || afterIndex === void 0) {
1630
1658
  return [];
1631
1659
  }
1632
- return [
1633
- failFinding(
1634
- "tool.order",
1635
- `Tool ${options.before} must appear before ${options.after}.`,
1636
- [eventEvidence(tools[beforeIndex]), eventEvidence(tools[afterIndex])],
1637
- { before: options.before, after: options.after },
1638
- tools.map(toolName)
1639
- )
1640
- ];
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 [];
1641
1693
  }
1642
1694
  };
1643
1695
  }
@@ -2434,12 +2486,24 @@ function createBaselineRegressionRule(options) {
2434
2486
  }
2435
2487
  function createObservedOutcomeRule(options = {}) {
2436
2488
  const failOn = options.failOn ?? ["failed"];
2489
+ const requireAny = options.requireAny === true;
2437
2490
  return {
2438
2491
  id: "outcome.status",
2439
2492
  category: "run",
2440
2493
  defaultSeverity: "error",
2441
2494
  evaluate(context) {
2442
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
+ }
2443
2507
  const matching = outcomesMatchingStatus(outcomes, failOn);
2444
2508
  if (matching.length === 0) return [];
2445
2509
  return [
@@ -2475,6 +2539,18 @@ function runTraceChecks(input, options = {}) {
2475
2539
  if (rules.diagnostics.length > 0) {
2476
2540
  return errorResult(input, rules.diagnostics, selected.run);
2477
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
+ }
2478
2554
  const facts = buildFacts(input, selected.run);
2479
2555
  const context = {
2480
2556
  ...facts,
@@ -2483,22 +2559,38 @@ function runTraceChecks(input, options = {}) {
2483
2559
  };
2484
2560
  const diagnostics = [];
2485
2561
  const findings = [];
2562
+ const ruleExecutions = [];
2486
2563
  for (const rule of rules.rules) {
2487
2564
  try {
2488
- findings.push(...rule.evaluate(context).map((finding) => normalizeFinding(rule, finding)));
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
+ });
2489
2574
  } catch (error) {
2490
2575
  const message = error instanceof Error ? error.message : String(error);
2491
2576
  diagnostics.push(
2492
2577
  diagnostic("AI_CHECK_INTERNAL_ERROR", `Rule ${rule.id} failed: ${message}`, rule.id)
2493
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
+ });
2494
2586
  }
2495
2587
  }
2496
2588
  if (diagnostics.length > 0) {
2497
- return errorResult(input, diagnostics, selected.run);
2589
+ return errorResult(input, diagnostics, selected.run, ruleExecutions);
2498
2590
  }
2499
2591
  const eventById = new Map(input.read.events.map((event) => [event.eventId, event]));
2500
2592
  const sortedFindings = findings.sort(compareFindings(eventById));
2501
- const summary = summarize(sortedFindings, diagnostics);
2593
+ const summary = summarize(sortedFindings, diagnostics, ruleExecutions.length);
2502
2594
  const status = summary.failed > 0 ? "fail" : "pass";
2503
2595
  return {
2504
2596
  ok: status === "pass",
@@ -2507,7 +2599,8 @@ function runTraceChecks(input, options = {}) {
2507
2599
  ...selected.run ? { runId: selected.run.runId } : {},
2508
2600
  summary,
2509
2601
  findings: sortedFindings,
2510
- diagnostics
2602
+ diagnostics,
2603
+ ruleExecutions
2511
2604
  };
2512
2605
  }
2513
2606