agent-inspect 4.4.0 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/docs/CLI.md +67 -0
- package/package.json +1 -1
- package/packages/cli/dist/chunk-TSIQUIPF.mjs +8505 -0
- package/packages/cli/dist/chunk-TSIQUIPF.mjs.map +1 -0
- package/packages/cli/dist/index.cjs +10851 -9298
- package/packages/cli/dist/index.cjs.map +1 -1
- package/packages/cli/dist/index.mjs +4769 -8027
- package/packages/cli/dist/index.mjs.map +1 -1
- package/packages/cli/dist/{src-FJOHPC4X.mjs → src-2SZWGKMM.mjs} +3 -3
- package/packages/cli/dist/{src-FJOHPC4X.mjs.map → src-2SZWGKMM.mjs.map} +1 -1
- package/packages/core/dist/advanced.cjs +3741 -16
- package/packages/core/dist/advanced.cjs.map +1 -1
- package/packages/core/dist/advanced.d.cts +222 -1
- package/packages/core/dist/advanced.d.ts +222 -1
- package/packages/core/dist/advanced.mjs +1092 -9
- package/packages/core/dist/advanced.mjs.map +1 -1
- package/packages/core/dist/checks.mjs +2 -1648
- package/packages/core/dist/checks.mjs.map +1 -1
- package/packages/core/dist/chunk-QLYOCCEF.mjs +1650 -0
- package/packages/core/dist/chunk-QLYOCCEF.mjs.map +1 -0
- package/packages/core/dist/chunk-X2FLDF7M.mjs +1581 -0
- package/packages/core/dist/chunk-X2FLDF7M.mjs.map +1 -0
- package/packages/core/dist/readers.mjs +3 -1579
- package/packages/core/dist/readers.mjs.map +1 -1
- package/packages/cli/dist/chunk-ORHDQUT6.mjs +0 -3862
- package/packages/cli/dist/chunk-ORHDQUT6.mjs.map +0 -1
|
@@ -1,19 +1,26 @@
|
|
|
1
|
+
import { runTraceChecks, createRunStatusRule, createToolUsageRule, createRunDurationRule, createLlmUsageRule, createObservedOutcomeRule } from './chunk-QLYOCCEF.mjs';
|
|
1
2
|
export { MAX_TERMINAL_DEPTH, MAX_TERMINAL_NAME_LENGTH, TERMINAL_INDENT, TraceDirectory, createInspector, createInspectorRuntime, formatTerminalName, getCurrentContext, getCurrentCorrelationMetadata, getCurrentDepth, getCurrentRunId, getCurrentRunName, getCurrentStepId, getIndent, getParentStepId, getTraceDirFromContext, getTraceSafetyFromContext, hasActiveContext, isAgentInspectEnabled, isSilentContext, maybeInspectRun, printError, printFailedAt, printRunComplete, printRunStart, printStepComplete, printStepStart, renderErrorLine, renderRunSummary, renderStepLine, resolveTraceDir, runWithContext, runWithStepContext } from './chunk-ZWA5RWMV.mjs';
|
|
2
3
|
import { buildRunSummary, extractMetadata } from './chunk-3USJVBLA.mjs';
|
|
3
4
|
export { buildRunSummary, buildRunTimeline, buildRunWhatSummary, extractMetadata, renderRunWhat, renderTimeline } from './chunk-3USJVBLA.mjs';
|
|
4
|
-
import { parseObservationFilter, extractOutcomesFromTraceEvents } from './chunk-VFO76UH3.mjs';
|
|
5
|
+
import { parseObservationFilter, extractOutcomesFromTraceEvents, extractOutcomesFromPersistedEvents } from './chunk-VFO76UH3.mjs';
|
|
5
6
|
import './chunk-BT7CATSD.mjs';
|
|
7
|
+
import { escapeHtml } from './chunk-CAWPF22J.mjs';
|
|
6
8
|
import { resolveRedactionProfile, readTraceEventsFromFile } from './chunk-ULCGIRTC.mjs';
|
|
7
9
|
export { DEFAULT_MAX_EVENT_BYTES, DEFAULT_MAX_METADATA_VALUE_LENGTH, DEFAULT_MAX_PREVIEW_LENGTH, getRunIdFromTraceFileName, initializeTraceFile, listTraceFiles, prepareMetadataForDisk, prepareTraceEventForDisk, readTraceEvents, readTraceFile, resolveRedactionProfile, resolveTraceSafetyOptions, serializeEvent, validateEvent, writeTraceEvent } from './chunk-ULCGIRTC.mjs';
|
|
8
10
|
import { Redactor } from './chunk-VU6O5QAH.mjs';
|
|
11
|
+
import { openTrace } from './chunk-X2FLDF7M.mjs';
|
|
12
|
+
import './chunk-KNYL56KZ.mjs';
|
|
9
13
|
import './chunk-S4YWKV4G.mjs';
|
|
10
|
-
import
|
|
14
|
+
import './chunk-E5F2LQCX.mjs';
|
|
15
|
+
import { parseDuration, formatDuration, isTraceEvent, getTraceFilePath } from './chunk-WRE4CNPF.mjs';
|
|
11
16
|
export { DEFAULT_TRACE_DIR_NAME, FALLBACK_TRACE_DIR, MAX_NAME_LENGTH, RUNS_DIR_NAME, createRunId, createStepId, ensureTraceDir, formatDuration, formatError, formatTimestamp, getDefaultTraceDir, getTraceFilePath, isStepStatus, isStepType, isTraceEvent, parseDuration, parseTraceJsonl, truncateName, unknownTraceFormatMessage, warn } from './chunk-WRE4CNPF.mjs';
|
|
12
17
|
import './chunk-P366CCL2.mjs';
|
|
13
18
|
import './chunk-7TGZLWEE.mjs';
|
|
14
19
|
import { createReadStream } from 'fs';
|
|
15
20
|
import { createInterface } from 'readline';
|
|
16
|
-
import
|
|
21
|
+
import path3 from 'path';
|
|
22
|
+
import { access, readFile } from 'fs/promises';
|
|
23
|
+
import { pathToFileURL } from 'url';
|
|
17
24
|
|
|
18
25
|
// packages/core/src/trace-filter.ts
|
|
19
26
|
function toLower(s) {
|
|
@@ -1318,12 +1325,12 @@ function buildCriticalPath(runs, handoffs) {
|
|
|
1318
1325
|
handoffs.filter((edge) => edge.confidence === "explicit").map((edge) => edge.from)
|
|
1319
1326
|
);
|
|
1320
1327
|
const ordered = [...runs].sort(compareRuns);
|
|
1321
|
-
const
|
|
1328
|
+
const path6 = [];
|
|
1322
1329
|
const visited = /* @__PURE__ */ new Set();
|
|
1323
1330
|
const pushRun = (run, confidence, source) => {
|
|
1324
1331
|
if (visited.has(run.runId)) return;
|
|
1325
1332
|
visited.add(run.runId);
|
|
1326
|
-
|
|
1333
|
+
path6.push({
|
|
1327
1334
|
runId: run.runId,
|
|
1328
1335
|
name: run.name,
|
|
1329
1336
|
startedAt: run.startedAt,
|
|
@@ -1348,7 +1355,7 @@ function buildCriticalPath(runs, handoffs) {
|
|
|
1348
1355
|
const confidence = explicitTargets.has(run.runId) || explicitSources.has(run.runId) ? "explicit" : "correlated";
|
|
1349
1356
|
pushRun(run, confidence, confidence === "explicit" ? "manual" : "inferred");
|
|
1350
1357
|
}
|
|
1351
|
-
return
|
|
1358
|
+
return path6;
|
|
1352
1359
|
}
|
|
1353
1360
|
function metaRunIdMatches(run, token, runById) {
|
|
1354
1361
|
const meta = extractSessionWorkflowMetadata(run.metadata);
|
|
@@ -1640,7 +1647,7 @@ function normalizeBundleOutputPath(out) {
|
|
|
1640
1647
|
if (trimmed === "") {
|
|
1641
1648
|
throw new Error("--out requires a non-empty path.");
|
|
1642
1649
|
}
|
|
1643
|
-
const resolved =
|
|
1650
|
+
const resolved = path3.resolve(trimmed);
|
|
1644
1651
|
if (resolved.toLowerCase().endsWith(".zip")) {
|
|
1645
1652
|
return resolved.slice(0, -4);
|
|
1646
1653
|
}
|
|
@@ -1649,9 +1656,1085 @@ function normalizeBundleOutputPath(out) {
|
|
|
1649
1656
|
function defaultBundleOutputPath(runIds) {
|
|
1650
1657
|
const label = runIds.length === 1 ? runIds[0] : `multi-${runIds.length}`;
|
|
1651
1658
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1652
|
-
return
|
|
1659
|
+
return path3.resolve(`agent-inspect-bundle-${label}-${stamp}`);
|
|
1653
1660
|
}
|
|
1654
1661
|
|
|
1655
|
-
|
|
1662
|
+
// packages/core/src/suite/types.ts
|
|
1663
|
+
var DEFAULT_SUITE_CONFIG_NAMES = [
|
|
1664
|
+
"agent-inspect.suite.json",
|
|
1665
|
+
"agent-inspect.suite.js",
|
|
1666
|
+
"agent-inspect.suite.mjs",
|
|
1667
|
+
"agent-inspect.suite.cjs"
|
|
1668
|
+
];
|
|
1669
|
+
var DEFAULT_SUITE_ARTIFACTS_DIR = ".agent-inspect/suite-runs";
|
|
1670
|
+
function diagnostic(code, message, severity = "error", caseId) {
|
|
1671
|
+
return { code, message, severity, ...caseId !== void 0 ? { caseId } : {} };
|
|
1672
|
+
}
|
|
1673
|
+
function asString(value, label) {
|
|
1674
|
+
if (value === void 0) return void 0;
|
|
1675
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
1676
|
+
throw new Error(`${label} must be a non-empty string.`);
|
|
1677
|
+
}
|
|
1678
|
+
return value.trim();
|
|
1679
|
+
}
|
|
1680
|
+
function asStringArray(value, label) {
|
|
1681
|
+
if (value === void 0) return void 0;
|
|
1682
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
|
|
1683
|
+
throw new Error(`${label} must be an array of strings.`);
|
|
1684
|
+
}
|
|
1685
|
+
return value;
|
|
1686
|
+
}
|
|
1687
|
+
function asPositiveNumber(value, label) {
|
|
1688
|
+
if (value === void 0) return void 0;
|
|
1689
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
1690
|
+
throw new Error(`${label} must be a non-negative number.`);
|
|
1691
|
+
}
|
|
1692
|
+
return value;
|
|
1693
|
+
}
|
|
1694
|
+
function validateCaseConfig(value, index) {
|
|
1695
|
+
const diagnostics = [];
|
|
1696
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
1697
|
+
diagnostics.push(
|
|
1698
|
+
diagnostic("AI_SUITE_CONFIG_INVALID", `cases[${index}] must be an object.`)
|
|
1699
|
+
);
|
|
1700
|
+
return { diagnostics };
|
|
1701
|
+
}
|
|
1702
|
+
const raw = value;
|
|
1703
|
+
try {
|
|
1704
|
+
const id = asString(raw.id, `cases[${index}].id`);
|
|
1705
|
+
if (id === void 0) {
|
|
1706
|
+
diagnostics.push(
|
|
1707
|
+
diagnostic("AI_SUITE_CONFIG_INVALID", `cases[${index}].id is required.`)
|
|
1708
|
+
);
|
|
1709
|
+
return { diagnostics };
|
|
1710
|
+
}
|
|
1711
|
+
const trace = asString(raw.trace, `cases[${index}].trace`);
|
|
1712
|
+
const runId = asString(raw.runId, `cases[${index}].runId`);
|
|
1713
|
+
const input = asString(raw.input, `cases[${index}].input`);
|
|
1714
|
+
return {
|
|
1715
|
+
caseConfig: {
|
|
1716
|
+
id,
|
|
1717
|
+
...trace !== void 0 ? { trace } : {},
|
|
1718
|
+
...runId !== void 0 ? { runId } : {},
|
|
1719
|
+
...input !== void 0 ? { input } : {},
|
|
1720
|
+
...asStringArray(raw.requireTools, `cases[${index}].requireTools`) !== void 0 ? { requireTools: asStringArray(raw.requireTools, `cases[${index}].requireTools`) } : {},
|
|
1721
|
+
...asStringArray(raw.forbidTools, `cases[${index}].forbidTools`) !== void 0 ? { forbidTools: asStringArray(raw.forbidTools, `cases[${index}].forbidTools`) } : {},
|
|
1722
|
+
...asPositiveNumber(raw.maxDurationMs, `cases[${index}].maxDurationMs`) !== void 0 ? {
|
|
1723
|
+
maxDurationMs: asPositiveNumber(
|
|
1724
|
+
raw.maxDurationMs,
|
|
1725
|
+
`cases[${index}].maxDurationMs`
|
|
1726
|
+
)
|
|
1727
|
+
} : {},
|
|
1728
|
+
...asStringArray(
|
|
1729
|
+
raw.expectedObservations,
|
|
1730
|
+
`cases[${index}].expectedObservations`
|
|
1731
|
+
) !== void 0 ? {
|
|
1732
|
+
expectedObservations: asStringArray(
|
|
1733
|
+
raw.expectedObservations,
|
|
1734
|
+
`cases[${index}].expectedObservations`
|
|
1735
|
+
)
|
|
1736
|
+
} : {}
|
|
1737
|
+
},
|
|
1738
|
+
diagnostics
|
|
1739
|
+
};
|
|
1740
|
+
} catch (error) {
|
|
1741
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1742
|
+
diagnostics.push(diagnostic("AI_SUITE_CONFIG_INVALID", message));
|
|
1743
|
+
return { diagnostics };
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
function normalizeSuiteConfig(value) {
|
|
1747
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
1748
|
+
throw new Error("Suite config must export an object.");
|
|
1749
|
+
}
|
|
1750
|
+
const raw = value;
|
|
1751
|
+
const name = asString(raw.name, "name");
|
|
1752
|
+
const traces = asString(raw.traces, "traces");
|
|
1753
|
+
if (name === void 0) throw new Error("name is required.");
|
|
1754
|
+
if (traces === void 0) throw new Error("traces is required.");
|
|
1755
|
+
if (!Array.isArray(raw.cases) || raw.cases.length === 0) {
|
|
1756
|
+
throw new Error("cases must be a non-empty array.");
|
|
1757
|
+
}
|
|
1758
|
+
const cases = [];
|
|
1759
|
+
for (let index = 0; index < raw.cases.length; index += 1) {
|
|
1760
|
+
const { caseConfig, diagnostics } = validateCaseConfig(raw.cases[index], index);
|
|
1761
|
+
if (diagnostics.length > 0) {
|
|
1762
|
+
throw new Error(diagnostics.map((item) => item.message).join("; "));
|
|
1763
|
+
}
|
|
1764
|
+
if (caseConfig !== void 0) cases.push(caseConfig);
|
|
1765
|
+
}
|
|
1766
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1767
|
+
for (const suiteCase of cases) {
|
|
1768
|
+
if (ids.has(suiteCase.id)) {
|
|
1769
|
+
throw new Error(`Duplicate case id "${suiteCase.id}".`);
|
|
1770
|
+
}
|
|
1771
|
+
ids.add(suiteCase.id);
|
|
1772
|
+
}
|
|
1773
|
+
const redactionProfile = raw.redactionProfile === "local" || raw.redactionProfile === "share" || raw.redactionProfile === "strict" ? raw.redactionProfile : void 0;
|
|
1774
|
+
if (raw.redactionProfile !== void 0 && redactionProfile === void 0) {
|
|
1775
|
+
throw new Error('redactionProfile must be "local", "share", or "strict".');
|
|
1776
|
+
}
|
|
1777
|
+
const config = { name, traces, cases };
|
|
1778
|
+
if (raw.checks !== void 0) {
|
|
1779
|
+
if (typeof raw.checks !== "object" || Array.isArray(raw.checks)) {
|
|
1780
|
+
throw new Error("checks must be an object.");
|
|
1781
|
+
}
|
|
1782
|
+
config.checks = raw.checks;
|
|
1783
|
+
}
|
|
1784
|
+
if (raw.eval !== void 0) {
|
|
1785
|
+
if (typeof raw.eval !== "object" || Array.isArray(raw.eval)) {
|
|
1786
|
+
throw new Error("eval must be an object.");
|
|
1787
|
+
}
|
|
1788
|
+
config.eval = raw.eval;
|
|
1789
|
+
}
|
|
1790
|
+
if (raw.artifacts !== void 0) {
|
|
1791
|
+
if (typeof raw.artifacts !== "object" || Array.isArray(raw.artifacts)) {
|
|
1792
|
+
throw new Error("artifacts must be an object.");
|
|
1793
|
+
}
|
|
1794
|
+
const outputDir = asString(
|
|
1795
|
+
raw.artifacts.outputDir,
|
|
1796
|
+
"artifacts.outputDir"
|
|
1797
|
+
);
|
|
1798
|
+
config.artifacts = outputDir !== void 0 ? { outputDir } : {};
|
|
1799
|
+
}
|
|
1800
|
+
if (raw.baseline !== void 0) {
|
|
1801
|
+
const baseline = asString(raw.baseline, "baseline");
|
|
1802
|
+
if (baseline !== void 0) config.baseline = baseline;
|
|
1803
|
+
}
|
|
1804
|
+
if (raw.candidate !== void 0) {
|
|
1805
|
+
const candidate = asString(raw.candidate, "candidate");
|
|
1806
|
+
if (candidate !== void 0) config.candidate = candidate;
|
|
1807
|
+
}
|
|
1808
|
+
if (redactionProfile !== void 0) config.redactionProfile = redactionProfile;
|
|
1809
|
+
return config;
|
|
1810
|
+
}
|
|
1811
|
+
async function validateSuiteConfig(config, options) {
|
|
1812
|
+
const diagnostics = [];
|
|
1813
|
+
const tracesDir = path3.resolve(options.configDir, config.traces);
|
|
1814
|
+
try {
|
|
1815
|
+
await access(tracesDir);
|
|
1816
|
+
} catch {
|
|
1817
|
+
diagnostics.push(
|
|
1818
|
+
diagnostic("AI_SUITE_CONFIG_INVALID", `traces directory not found: ${tracesDir}`)
|
|
1819
|
+
);
|
|
1820
|
+
}
|
|
1821
|
+
for (const suiteCase of config.cases) {
|
|
1822
|
+
if (suiteCase.input !== void 0) {
|
|
1823
|
+
const inputPath = path3.resolve(options.configDir, suiteCase.input);
|
|
1824
|
+
try {
|
|
1825
|
+
await access(inputPath);
|
|
1826
|
+
} catch {
|
|
1827
|
+
diagnostics.push(
|
|
1828
|
+
diagnostic(
|
|
1829
|
+
"AI_SUITE_CONFIG_INVALID",
|
|
1830
|
+
`input fixture not found: ${suiteCase.input}`,
|
|
1831
|
+
"warning",
|
|
1832
|
+
suiteCase.id
|
|
1833
|
+
)
|
|
1834
|
+
);
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
return { ok: diagnostics.every((item) => item.severity !== "error"), diagnostics };
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
// packages/core/src/suite/load.ts
|
|
1842
|
+
var CONFIG_EXTENSIONS = /* @__PURE__ */ new Set([".json", ".js", ".mjs", ".cjs"]);
|
|
1843
|
+
var TS_CONFIG_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".mts", ".cts"]);
|
|
1844
|
+
function diagnostic2(code, message) {
|
|
1845
|
+
return { code, message, severity: "error" };
|
|
1846
|
+
}
|
|
1847
|
+
async function fileExists(filePath) {
|
|
1848
|
+
try {
|
|
1849
|
+
await access(filePath);
|
|
1850
|
+
return true;
|
|
1851
|
+
} catch {
|
|
1852
|
+
return false;
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
async function resolveSuiteConfigPath(options = {}) {
|
|
1856
|
+
const cwd = path3.resolve(options.cwd ?? process.cwd());
|
|
1857
|
+
if (options.configPath !== void 0 && options.configPath.trim() !== "") {
|
|
1858
|
+
return path3.resolve(cwd, options.configPath.trim());
|
|
1859
|
+
}
|
|
1860
|
+
for (const name of DEFAULT_SUITE_CONFIG_NAMES) {
|
|
1861
|
+
const candidate = path3.join(cwd, name);
|
|
1862
|
+
if (await fileExists(candidate)) return candidate;
|
|
1863
|
+
}
|
|
1864
|
+
throw new Error(
|
|
1865
|
+
`No suite config found. Create one with \`agent-inspect suite init\` or pass --config.`
|
|
1866
|
+
);
|
|
1867
|
+
}
|
|
1868
|
+
async function loadSuiteConfig(options = {}) {
|
|
1869
|
+
let configPath;
|
|
1870
|
+
try {
|
|
1871
|
+
configPath = await resolveSuiteConfigPath(options);
|
|
1872
|
+
} catch (error) {
|
|
1873
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1874
|
+
throw Object.assign(new Error(message), {
|
|
1875
|
+
diagnostics: [diagnostic2("AI_SUITE_CONFIG_LOAD_FAILED", message)]
|
|
1876
|
+
});
|
|
1877
|
+
}
|
|
1878
|
+
const extension = path3.extname(configPath);
|
|
1879
|
+
if (TS_CONFIG_EXTENSIONS.has(extension)) {
|
|
1880
|
+
const message = "TypeScript suite configs require an explicit precompiled JavaScript config or future --config-loader support.";
|
|
1881
|
+
throw Object.assign(new Error(message), {
|
|
1882
|
+
diagnostics: [diagnostic2("AI_SUITE_CONFIG_LOAD_FAILED", message)]
|
|
1883
|
+
});
|
|
1884
|
+
}
|
|
1885
|
+
if (!CONFIG_EXTENSIONS.has(extension)) {
|
|
1886
|
+
const message = "Unsupported suite config extension. Use .json, .js, .mjs, or .cjs.";
|
|
1887
|
+
throw Object.assign(new Error(message), {
|
|
1888
|
+
diagnostics: [diagnostic2("AI_SUITE_CONFIG_LOAD_FAILED", message)]
|
|
1889
|
+
});
|
|
1890
|
+
}
|
|
1891
|
+
try {
|
|
1892
|
+
let raw;
|
|
1893
|
+
if (extension === ".json") {
|
|
1894
|
+
raw = JSON.parse(await readFile(configPath, "utf-8"));
|
|
1895
|
+
} else {
|
|
1896
|
+
const mod = await import(pathToFileURL(configPath).href);
|
|
1897
|
+
raw = "default" in mod ? mod.default : mod;
|
|
1898
|
+
}
|
|
1899
|
+
const config = normalizeSuiteConfig(raw);
|
|
1900
|
+
return {
|
|
1901
|
+
config,
|
|
1902
|
+
configPath,
|
|
1903
|
+
configDir: path3.dirname(configPath)
|
|
1904
|
+
};
|
|
1905
|
+
} catch (error) {
|
|
1906
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1907
|
+
throw Object.assign(new Error(message), {
|
|
1908
|
+
diagnostics: [diagnostic2("AI_SUITE_CONFIG_LOAD_FAILED", message)]
|
|
1909
|
+
});
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
function defaultSuiteConfigTemplate() {
|
|
1913
|
+
return {
|
|
1914
|
+
name: "my-agent-suite",
|
|
1915
|
+
traces: "./.agent-inspect",
|
|
1916
|
+
cases: [
|
|
1917
|
+
{
|
|
1918
|
+
id: "example-case",
|
|
1919
|
+
runId: "example-run",
|
|
1920
|
+
requireTools: ["searchDocs"],
|
|
1921
|
+
maxDurationMs: 3e4,
|
|
1922
|
+
expectedObservations: ["answerReady"]
|
|
1923
|
+
}
|
|
1924
|
+
],
|
|
1925
|
+
checks: {
|
|
1926
|
+
select: ["run.status"]
|
|
1927
|
+
},
|
|
1928
|
+
redactionProfile: "local",
|
|
1929
|
+
artifacts: {
|
|
1930
|
+
outputDir: ".agent-inspect/suite-runs"
|
|
1931
|
+
}
|
|
1932
|
+
};
|
|
1933
|
+
}
|
|
1934
|
+
async function exists(filePath) {
|
|
1935
|
+
try {
|
|
1936
|
+
await access(filePath);
|
|
1937
|
+
return true;
|
|
1938
|
+
} catch {
|
|
1939
|
+
return false;
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
async function resolveSuiteCaseTrace(suiteCase, options) {
|
|
1943
|
+
if (suiteCase.trace !== void 0) {
|
|
1944
|
+
const tracePath = path3.resolve(options.configDir, suiteCase.trace);
|
|
1945
|
+
if (await exists(tracePath)) {
|
|
1946
|
+
return { caseId: suiteCase.id, tracePath, missing: false };
|
|
1947
|
+
}
|
|
1948
|
+
return {
|
|
1949
|
+
caseId: suiteCase.id,
|
|
1950
|
+
tracePath,
|
|
1951
|
+
missing: true,
|
|
1952
|
+
reason: `trace file not found: ${suiteCase.trace}`
|
|
1953
|
+
};
|
|
1954
|
+
}
|
|
1955
|
+
const runKey = suiteCase.runId ?? suiteCase.id;
|
|
1956
|
+
const directPath = getTraceFilePath(runKey, options.tracesDir);
|
|
1957
|
+
if (await exists(directPath)) {
|
|
1958
|
+
return { caseId: suiteCase.id, tracePath: directPath, runId: runKey, missing: false };
|
|
1959
|
+
}
|
|
1960
|
+
const nestedPath = path3.join(options.tracesDir, `${path3.basename(runKey)}.jsonl`);
|
|
1961
|
+
if (await exists(nestedPath)) {
|
|
1962
|
+
return { caseId: suiteCase.id, tracePath: nestedPath, runId: runKey, missing: false };
|
|
1963
|
+
}
|
|
1964
|
+
return {
|
|
1965
|
+
caseId: suiteCase.id,
|
|
1966
|
+
runId: runKey,
|
|
1967
|
+
missing: true,
|
|
1968
|
+
reason: `no trace found for run id "${runKey}" under ${options.tracesDir}`
|
|
1969
|
+
};
|
|
1970
|
+
}
|
|
1971
|
+
function diagnostic3(code, message, severity = "error", caseId) {
|
|
1972
|
+
return { code, message, severity, ...caseId !== void 0 ? { caseId } : {} };
|
|
1973
|
+
}
|
|
1974
|
+
function buildCaseRules(suiteCase, config) {
|
|
1975
|
+
const rules = [];
|
|
1976
|
+
const select = new Set(config.checks?.select ?? []);
|
|
1977
|
+
if (select.has("run.status")) {
|
|
1978
|
+
rules.push(createRunStatusRule());
|
|
1979
|
+
}
|
|
1980
|
+
const requiredTools = [
|
|
1981
|
+
...config.checks?.tool?.required ?? [],
|
|
1982
|
+
...suiteCase.requireTools ?? []
|
|
1983
|
+
];
|
|
1984
|
+
const forbiddenTools = [
|
|
1985
|
+
...config.checks?.tool?.forbidden ?? [],
|
|
1986
|
+
...suiteCase.forbidTools ?? []
|
|
1987
|
+
];
|
|
1988
|
+
if (requiredTools.length > 0 || forbiddenTools.length > 0) {
|
|
1989
|
+
rules.push(
|
|
1990
|
+
createToolUsageRule({
|
|
1991
|
+
required: requiredTools.length > 0 ? requiredTools : void 0,
|
|
1992
|
+
forbidden: forbiddenTools.length > 0 ? forbiddenTools : void 0
|
|
1993
|
+
})
|
|
1994
|
+
);
|
|
1995
|
+
select.add("tool.usage");
|
|
1996
|
+
}
|
|
1997
|
+
const maxDurationMs = suiteCase.maxDurationMs ?? config.checks?.run?.maxDurationMs ?? config.eval?.maxDurationMs;
|
|
1998
|
+
if (maxDurationMs !== void 0) {
|
|
1999
|
+
rules.push(createRunDurationRule({ maxDurationMs }));
|
|
2000
|
+
select.add("run.duration");
|
|
2001
|
+
}
|
|
2002
|
+
const llm = config.checks?.llm;
|
|
2003
|
+
if (llm?.allowedModels !== void 0 || llm?.maxTotalTokens !== void 0) {
|
|
2004
|
+
rules.push(createLlmUsageRule(llm));
|
|
2005
|
+
select.add("llm.usage");
|
|
2006
|
+
}
|
|
2007
|
+
if (select.has("outcome.status")) {
|
|
2008
|
+
rules.push(createObservedOutcomeRule({ failOn: ["failed"] }));
|
|
2009
|
+
}
|
|
2010
|
+
return { rules, select: [...select] };
|
|
2011
|
+
}
|
|
2012
|
+
function outcomesFromRead(read) {
|
|
2013
|
+
const persistedOutcomes = extractOutcomesFromPersistedEvents(
|
|
2014
|
+
read.events.filter((event) => event.kind === "OUTCOME")
|
|
2015
|
+
);
|
|
2016
|
+
if (persistedOutcomes.length > 0) return persistedOutcomes;
|
|
2017
|
+
const traceEvents = [];
|
|
2018
|
+
for (const event of read.events) {
|
|
2019
|
+
const legacy = event;
|
|
2020
|
+
if (typeof legacy === "object" && legacy !== null && "event" in legacy && legacy.event === "outcome_observed") {
|
|
2021
|
+
traceEvents.push(legacy);
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
return traceEvents.length > 0 ? extractOutcomesFromTraceEvents(traceEvents) : [];
|
|
2025
|
+
}
|
|
2026
|
+
function validateExpectedObservations(suiteCase, read) {
|
|
2027
|
+
const expected = suiteCase.expectedObservations ?? [];
|
|
2028
|
+
if (expected.length === 0) return { ok: true, diagnostics: [] };
|
|
2029
|
+
const outcomes = outcomesFromRead(read);
|
|
2030
|
+
const diagnostics = [];
|
|
2031
|
+
for (const name of expected) {
|
|
2032
|
+
const match = outcomes.find((outcome) => outcome.name === name);
|
|
2033
|
+
if (!match) {
|
|
2034
|
+
diagnostics.push(
|
|
2035
|
+
diagnostic3(
|
|
2036
|
+
"AI_SUITE_CASE_OBSERVATION_FAILED",
|
|
2037
|
+
`Expected observation "${name}" was not recorded.`,
|
|
2038
|
+
"error",
|
|
2039
|
+
suiteCase.id
|
|
2040
|
+
)
|
|
2041
|
+
);
|
|
2042
|
+
continue;
|
|
2043
|
+
}
|
|
2044
|
+
if (match.status !== "passed") {
|
|
2045
|
+
diagnostics.push(
|
|
2046
|
+
diagnostic3(
|
|
2047
|
+
"AI_SUITE_CASE_OBSERVATION_FAILED",
|
|
2048
|
+
`Observation "${name}" has status "${match.status}", expected "passed".`,
|
|
2049
|
+
"error",
|
|
2050
|
+
suiteCase.id
|
|
2051
|
+
)
|
|
2052
|
+
);
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
return { ok: diagnostics.length === 0, diagnostics };
|
|
2056
|
+
}
|
|
2057
|
+
async function runSuiteCase(suiteCase, config, options) {
|
|
2058
|
+
const resolved = await resolveSuiteCaseTrace(suiteCase, options);
|
|
2059
|
+
if (resolved.missing || resolved.tracePath === void 0) {
|
|
2060
|
+
return {
|
|
2061
|
+
id: suiteCase.id,
|
|
2062
|
+
status: "skipped",
|
|
2063
|
+
...resolved.runId !== void 0 ? { runId: resolved.runId } : {},
|
|
2064
|
+
message: resolved.reason,
|
|
2065
|
+
diagnostics: [
|
|
2066
|
+
diagnostic3(
|
|
2067
|
+
"AI_SUITE_CASE_TRACE_MISSING",
|
|
2068
|
+
resolved.reason ?? "Trace not found.",
|
|
2069
|
+
"warning",
|
|
2070
|
+
suiteCase.id
|
|
2071
|
+
)
|
|
2072
|
+
]
|
|
2073
|
+
};
|
|
2074
|
+
}
|
|
2075
|
+
let read;
|
|
2076
|
+
try {
|
|
2077
|
+
read = await openTrace({ type: "file", path: resolved.tracePath });
|
|
2078
|
+
} catch (error) {
|
|
2079
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2080
|
+
return {
|
|
2081
|
+
id: suiteCase.id,
|
|
2082
|
+
status: "error",
|
|
2083
|
+
tracePath: resolved.tracePath,
|
|
2084
|
+
...resolved.runId !== void 0 ? { runId: resolved.runId } : {},
|
|
2085
|
+
message,
|
|
2086
|
+
diagnostics: [
|
|
2087
|
+
diagnostic3("AI_SUITE_TRACE_UNREADABLE", message, "error", suiteCase.id)
|
|
2088
|
+
]
|
|
2089
|
+
};
|
|
2090
|
+
}
|
|
2091
|
+
const { rules, select } = buildCaseRules(suiteCase, config);
|
|
2092
|
+
const checkResult = rules.length > 0 ? runTraceChecks({ read }, { rules, select }) : {
|
|
2093
|
+
ok: true,
|
|
2094
|
+
status: "pass",
|
|
2095
|
+
format: read.format,
|
|
2096
|
+
findings: [],
|
|
2097
|
+
diagnostics: []
|
|
2098
|
+
};
|
|
2099
|
+
const observationResult = validateExpectedObservations(suiteCase, read);
|
|
2100
|
+
const diagnostics = [
|
|
2101
|
+
...checkResult.diagnostics.map(
|
|
2102
|
+
(item) => diagnostic3(
|
|
2103
|
+
"AI_SUITE_CASE_CHECK_FAILED",
|
|
2104
|
+
item.message,
|
|
2105
|
+
item.severity,
|
|
2106
|
+
suiteCase.id
|
|
2107
|
+
)
|
|
2108
|
+
),
|
|
2109
|
+
...checkResult.findings.filter((finding) => finding.status === "fail").map(
|
|
2110
|
+
(finding) => diagnostic3(
|
|
2111
|
+
"AI_SUITE_CASE_CHECK_FAILED",
|
|
2112
|
+
finding.message,
|
|
2113
|
+
finding.severity,
|
|
2114
|
+
suiteCase.id
|
|
2115
|
+
)
|
|
2116
|
+
),
|
|
2117
|
+
...observationResult.diagnostics
|
|
2118
|
+
];
|
|
2119
|
+
const checkOk = checkResult.ok;
|
|
2120
|
+
const observationsOk = observationResult.ok;
|
|
2121
|
+
const ok = checkOk && observationsOk;
|
|
2122
|
+
const status = ok ? "pass" : checkResult.status === "error" ? "error" : "fail";
|
|
2123
|
+
return {
|
|
2124
|
+
id: suiteCase.id,
|
|
2125
|
+
status,
|
|
2126
|
+
tracePath: resolved.tracePath,
|
|
2127
|
+
...resolved.runId !== void 0 ? { runId: resolved.runId } : {},
|
|
2128
|
+
checkOk,
|
|
2129
|
+
observationsOk,
|
|
2130
|
+
diagnostics,
|
|
2131
|
+
...ok ? {} : {
|
|
2132
|
+
message: diagnostics.map((item) => item.message).join("; ") || checkResult.findings.map((item) => item.message).join("; ")
|
|
2133
|
+
}
|
|
2134
|
+
};
|
|
2135
|
+
}
|
|
2136
|
+
async function runSuite(options = {}) {
|
|
2137
|
+
const startedAt = new Date(options.nowMs ?? Date.now()).toISOString();
|
|
2138
|
+
const { config, configPath, configDir } = await loadSuiteConfig(options);
|
|
2139
|
+
const tracesDir = path3.resolve(configDir, config.traces);
|
|
2140
|
+
const cases = [];
|
|
2141
|
+
const diagnostics = [];
|
|
2142
|
+
for (const suiteCase of config.cases) {
|
|
2143
|
+
cases.push(
|
|
2144
|
+
await runSuiteCase(suiteCase, config, {
|
|
2145
|
+
configDir,
|
|
2146
|
+
tracesDir
|
|
2147
|
+
})
|
|
2148
|
+
);
|
|
2149
|
+
}
|
|
2150
|
+
const summary = {
|
|
2151
|
+
passed: cases.filter((item) => item.status === "pass").length,
|
|
2152
|
+
failed: cases.filter((item) => item.status === "fail").length,
|
|
2153
|
+
errors: cases.filter((item) => item.status === "error").length,
|
|
2154
|
+
skipped: cases.filter((item) => item.status === "skipped").length
|
|
2155
|
+
};
|
|
2156
|
+
const finishedAt = new Date(options.nowMs ?? Date.now()).toISOString();
|
|
2157
|
+
const ok = summary.failed === 0 && summary.errors === 0;
|
|
2158
|
+
const status = summary.errors > 0 ? "error" : summary.failed > 0 || !ok ? "fail" : "pass";
|
|
2159
|
+
return {
|
|
2160
|
+
ok,
|
|
2161
|
+
status,
|
|
2162
|
+
suiteName: config.name,
|
|
2163
|
+
configPath,
|
|
2164
|
+
tracesDir,
|
|
2165
|
+
startedAt,
|
|
2166
|
+
finishedAt,
|
|
2167
|
+
summary,
|
|
2168
|
+
cases,
|
|
2169
|
+
diagnostics
|
|
2170
|
+
};
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
// packages/core/src/suite/report.ts
|
|
2174
|
+
function statusLabel(status) {
|
|
2175
|
+
return status.toUpperCase();
|
|
2176
|
+
}
|
|
2177
|
+
function renderSuiteReportMarkdown(result) {
|
|
2178
|
+
const lines = [];
|
|
2179
|
+
lines.push(`# Suite: ${result.suiteName}`);
|
|
2180
|
+
lines.push("");
|
|
2181
|
+
lines.push(`Status: **${statusLabel(result.status)}**`);
|
|
2182
|
+
lines.push(`Config: \`${result.configPath}\``);
|
|
2183
|
+
lines.push(`Traces: \`${result.tracesDir}\``);
|
|
2184
|
+
lines.push(`Started: ${result.startedAt}`);
|
|
2185
|
+
lines.push(`Finished: ${result.finishedAt}`);
|
|
2186
|
+
lines.push("");
|
|
2187
|
+
lines.push("## Summary");
|
|
2188
|
+
lines.push(`- Passed: ${result.summary.passed}`);
|
|
2189
|
+
lines.push(`- Failed: ${result.summary.failed}`);
|
|
2190
|
+
lines.push(`- Errors: ${result.summary.errors}`);
|
|
2191
|
+
lines.push(`- Skipped: ${result.summary.skipped}`);
|
|
2192
|
+
lines.push("");
|
|
2193
|
+
lines.push("## Cases");
|
|
2194
|
+
for (const suiteCase of result.cases) {
|
|
2195
|
+
lines.push(`### ${suiteCase.id} \u2014 ${statusLabel(suiteCase.status)}`);
|
|
2196
|
+
if (suiteCase.tracePath !== void 0) {
|
|
2197
|
+
lines.push(`- Trace: \`${suiteCase.tracePath}\``);
|
|
2198
|
+
}
|
|
2199
|
+
if (suiteCase.runId !== void 0) {
|
|
2200
|
+
lines.push(`- Run: \`${suiteCase.runId}\``);
|
|
2201
|
+
}
|
|
2202
|
+
if (suiteCase.message !== void 0 && suiteCase.message.trim() !== "") {
|
|
2203
|
+
lines.push(`- ${suiteCase.message}`);
|
|
2204
|
+
}
|
|
2205
|
+
if (suiteCase.diagnostics.length > 0) {
|
|
2206
|
+
for (const item of suiteCase.diagnostics) {
|
|
2207
|
+
lines.push(`- [${item.severity}] ${item.message}`);
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
lines.push("");
|
|
2211
|
+
}
|
|
2212
|
+
return lines.join("\n").trimEnd();
|
|
2213
|
+
}
|
|
2214
|
+
function renderSuiteReport(result, options = {}) {
|
|
2215
|
+
const format = options.format ?? "markdown";
|
|
2216
|
+
if (format === "json") {
|
|
2217
|
+
return JSON.stringify(result, null, 2);
|
|
2218
|
+
}
|
|
2219
|
+
return renderSuiteReportMarkdown(result);
|
|
2220
|
+
}
|
|
2221
|
+
|
|
2222
|
+
// packages/core/src/cohort/types.ts
|
|
2223
|
+
var COHORT_METRIC_IDS = [
|
|
2224
|
+
"errorRate",
|
|
2225
|
+
"duration",
|
|
2226
|
+
"toolChoice",
|
|
2227
|
+
"toolOrdering",
|
|
2228
|
+
"llmCallCount",
|
|
2229
|
+
"tokenUsage",
|
|
2230
|
+
"retryCount",
|
|
2231
|
+
"observationFailure",
|
|
2232
|
+
"guardrailFailure",
|
|
2233
|
+
"circuitViolation",
|
|
2234
|
+
"redactionWarning"
|
|
2235
|
+
];
|
|
2236
|
+
|
|
2237
|
+
// packages/core/src/cohort/compare.ts
|
|
2238
|
+
function compareNumber(metric, label, baseline, candidate, higherIsWorse = true) {
|
|
2239
|
+
if (baseline === void 0 && candidate === void 0) return void 0;
|
|
2240
|
+
const delta = baseline !== void 0 && candidate !== void 0 ? candidate - baseline : void 0;
|
|
2241
|
+
const regression = delta !== void 0 && (higherIsWorse && delta > 0 || !higherIsWorse && delta < 0);
|
|
2242
|
+
return {
|
|
2243
|
+
metric,
|
|
2244
|
+
baseline,
|
|
2245
|
+
candidate,
|
|
2246
|
+
delta,
|
|
2247
|
+
regression,
|
|
2248
|
+
message: `${label}: baseline=${baseline ?? "n/a"} candidate=${candidate ?? "n/a"}${delta !== void 0 ? ` (delta ${delta})` : ""}`
|
|
2249
|
+
};
|
|
2250
|
+
}
|
|
2251
|
+
function pickAggregate(groups, cohortLabel, groupKey) {
|
|
2252
|
+
return groups.find(
|
|
2253
|
+
(group) => group.cohortLabel === cohortLabel && (groupKey === void 0 || group.groupKey === groupKey)
|
|
2254
|
+
);
|
|
2255
|
+
}
|
|
2256
|
+
function compareCohortAggregates(groups, options) {
|
|
2257
|
+
const baselineAgg = pickAggregate(groups, options.baseline, options.groupKey);
|
|
2258
|
+
const candidateAgg = pickAggregate(groups, options.candidate, options.groupKey);
|
|
2259
|
+
const comparisons = [];
|
|
2260
|
+
for (const metric of options.metrics) {
|
|
2261
|
+
switch (metric) {
|
|
2262
|
+
case "errorRate": {
|
|
2263
|
+
const item = compareNumber(
|
|
2264
|
+
metric,
|
|
2265
|
+
"Error rate",
|
|
2266
|
+
baselineAgg?.errorRate,
|
|
2267
|
+
candidateAgg?.errorRate
|
|
2268
|
+
);
|
|
2269
|
+
if (item) comparisons.push(item);
|
|
2270
|
+
break;
|
|
2271
|
+
}
|
|
2272
|
+
case "duration": {
|
|
2273
|
+
const item = compareNumber(
|
|
2274
|
+
metric,
|
|
2275
|
+
"Average duration (ms)",
|
|
2276
|
+
baselineAgg?.avgDurationMs,
|
|
2277
|
+
candidateAgg?.avgDurationMs
|
|
2278
|
+
);
|
|
2279
|
+
if (item) comparisons.push(item);
|
|
2280
|
+
break;
|
|
2281
|
+
}
|
|
2282
|
+
case "llmCallCount": {
|
|
2283
|
+
const item = compareNumber(
|
|
2284
|
+
metric,
|
|
2285
|
+
"Average LLM calls",
|
|
2286
|
+
baselineAgg?.avgLlmCallCount,
|
|
2287
|
+
candidateAgg?.avgLlmCallCount
|
|
2288
|
+
);
|
|
2289
|
+
if (item) comparisons.push(item);
|
|
2290
|
+
break;
|
|
2291
|
+
}
|
|
2292
|
+
case "tokenUsage": {
|
|
2293
|
+
const item = compareNumber(
|
|
2294
|
+
metric,
|
|
2295
|
+
"Average token usage",
|
|
2296
|
+
baselineAgg?.avgTokenUsage,
|
|
2297
|
+
candidateAgg?.avgTokenUsage
|
|
2298
|
+
);
|
|
2299
|
+
if (item) comparisons.push(item);
|
|
2300
|
+
break;
|
|
2301
|
+
}
|
|
2302
|
+
case "retryCount": {
|
|
2303
|
+
const item = compareNumber(
|
|
2304
|
+
metric,
|
|
2305
|
+
"Average retries",
|
|
2306
|
+
baselineAgg?.avgRetryCount,
|
|
2307
|
+
candidateAgg?.avgRetryCount
|
|
2308
|
+
);
|
|
2309
|
+
if (item) comparisons.push(item);
|
|
2310
|
+
break;
|
|
2311
|
+
}
|
|
2312
|
+
case "observationFailure": {
|
|
2313
|
+
const item = compareNumber(
|
|
2314
|
+
metric,
|
|
2315
|
+
"Observation failure rate",
|
|
2316
|
+
baselineAgg?.observationFailureRate,
|
|
2317
|
+
candidateAgg?.observationFailureRate
|
|
2318
|
+
);
|
|
2319
|
+
if (item) comparisons.push(item);
|
|
2320
|
+
break;
|
|
2321
|
+
}
|
|
2322
|
+
case "toolChoice": {
|
|
2323
|
+
const baselineValue = baselineAgg?.dominantToolChoice;
|
|
2324
|
+
const candidateValue = candidateAgg?.dominantToolChoice;
|
|
2325
|
+
comparisons.push({
|
|
2326
|
+
metric,
|
|
2327
|
+
baseline: baselineValue,
|
|
2328
|
+
candidate: candidateValue,
|
|
2329
|
+
delta: baselineValue === candidateValue ? "same" : "changed",
|
|
2330
|
+
regression: baselineValue !== candidateValue,
|
|
2331
|
+
message: `Tool choice: baseline=${baselineValue ?? "n/a"} candidate=${candidateValue ?? "n/a"}`
|
|
2332
|
+
});
|
|
2333
|
+
break;
|
|
2334
|
+
}
|
|
2335
|
+
case "toolOrdering": {
|
|
2336
|
+
const baselineValue = baselineAgg?.toolOrderingSignature;
|
|
2337
|
+
const candidateValue = candidateAgg?.toolOrderingSignature;
|
|
2338
|
+
comparisons.push({
|
|
2339
|
+
metric,
|
|
2340
|
+
baseline: baselineValue,
|
|
2341
|
+
candidate: candidateValue,
|
|
2342
|
+
delta: baselineValue === candidateValue ? "same" : "changed",
|
|
2343
|
+
regression: baselineValue !== candidateValue,
|
|
2344
|
+
message: `Tool ordering: baseline=${baselineValue ?? "n/a"} candidate=${candidateValue ?? "n/a"}`
|
|
2345
|
+
});
|
|
2346
|
+
break;
|
|
2347
|
+
}
|
|
2348
|
+
case "guardrailFailure": {
|
|
2349
|
+
const item = compareNumber(
|
|
2350
|
+
metric,
|
|
2351
|
+
"Guardrail failures",
|
|
2352
|
+
baselineAgg?.avgGuardrailFailures,
|
|
2353
|
+
candidateAgg?.avgGuardrailFailures
|
|
2354
|
+
);
|
|
2355
|
+
if (item) comparisons.push(item);
|
|
2356
|
+
break;
|
|
2357
|
+
}
|
|
2358
|
+
case "circuitViolation": {
|
|
2359
|
+
const item = compareNumber(
|
|
2360
|
+
metric,
|
|
2361
|
+
"Circuit violations",
|
|
2362
|
+
baselineAgg?.avgCircuitViolations,
|
|
2363
|
+
candidateAgg?.avgCircuitViolations
|
|
2364
|
+
);
|
|
2365
|
+
if (item) comparisons.push(item);
|
|
2366
|
+
break;
|
|
2367
|
+
}
|
|
2368
|
+
case "redactionWarning": {
|
|
2369
|
+
const item = compareNumber(
|
|
2370
|
+
metric,
|
|
2371
|
+
"Redaction warnings",
|
|
2372
|
+
baselineAgg?.avgRedactionWarnings,
|
|
2373
|
+
candidateAgg?.avgRedactionWarnings
|
|
2374
|
+
);
|
|
2375
|
+
if (item) comparisons.push(item);
|
|
2376
|
+
break;
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
return comparisons;
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
// packages/core/src/cohort/grouping.ts
|
|
2384
|
+
function parseCohortMetricList(value) {
|
|
2385
|
+
if (value === void 0 || value.trim() === "") return [];
|
|
2386
|
+
return value.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
|
|
2387
|
+
}
|
|
2388
|
+
function parseGroupBySpec(groupBy) {
|
|
2389
|
+
const raw = (groupBy ?? "model").trim();
|
|
2390
|
+
if (raw === "model") return { kind: "model" };
|
|
2391
|
+
if (raw === "session") return { kind: "session" };
|
|
2392
|
+
if (raw === "group") return { kind: "group" };
|
|
2393
|
+
if (raw.startsWith("metadata.")) {
|
|
2394
|
+
const metadataKey = raw.slice("metadata.".length).trim();
|
|
2395
|
+
if (metadataKey === "") throw new Error("metadata group-by requires a key.");
|
|
2396
|
+
return { kind: "metadata", metadataKey };
|
|
2397
|
+
}
|
|
2398
|
+
throw new Error(`Unsupported --group-by value: ${raw}`);
|
|
2399
|
+
}
|
|
2400
|
+
function metadataString(metadata, key) {
|
|
2401
|
+
const value = metadata?.[key];
|
|
2402
|
+
return typeof value === "string" && value.trim() !== "" ? value.trim() : void 0;
|
|
2403
|
+
}
|
|
2404
|
+
function resolveRunGroupKey(run, groupBy) {
|
|
2405
|
+
const metadata = run.metadata ?? {};
|
|
2406
|
+
switch (groupBy.kind) {
|
|
2407
|
+
case "model":
|
|
2408
|
+
return metadataString(metadata, "model") ?? "unknown";
|
|
2409
|
+
case "session":
|
|
2410
|
+
return extractSessionWorkflowMetadata(metadata)?.sessionId ?? metadataString(metadata, "sessionId") ?? "__unscoped__";
|
|
2411
|
+
case "group":
|
|
2412
|
+
return extractSessionWorkflowMetadata(metadata)?.groupId ?? metadataString(metadata, "groupId") ?? "__unscoped__";
|
|
2413
|
+
case "metadata":
|
|
2414
|
+
return metadataString(metadata, groupBy.metadataKey) ?? "__missing__";
|
|
2415
|
+
default:
|
|
2416
|
+
return "unknown";
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
function resolveCohortLabel(run, cohortKey, baseline, candidate) {
|
|
2420
|
+
const label = metadataString(run.metadata, cohortKey);
|
|
2421
|
+
if (label === void 0) return void 0;
|
|
2422
|
+
if (baseline !== void 0 && label === baseline) return baseline;
|
|
2423
|
+
if (candidate !== void 0 && label === candidate) return candidate;
|
|
2424
|
+
if (baseline === void 0 && candidate === void 0) return label;
|
|
2425
|
+
return void 0;
|
|
2426
|
+
}
|
|
2427
|
+
function filterRunsForCohort(runs, options) {
|
|
2428
|
+
const warnings = [];
|
|
2429
|
+
if (options.baseline === void 0 && options.candidate === void 0) {
|
|
2430
|
+
return { runs: [...runs], warnings };
|
|
2431
|
+
}
|
|
2432
|
+
const selected = [];
|
|
2433
|
+
for (const run of runs) {
|
|
2434
|
+
const label = resolveCohortLabel(
|
|
2435
|
+
run,
|
|
2436
|
+
options.cohortKey,
|
|
2437
|
+
options.baseline,
|
|
2438
|
+
options.candidate
|
|
2439
|
+
);
|
|
2440
|
+
if (label !== void 0) selected.push(run);
|
|
2441
|
+
}
|
|
2442
|
+
if (selected.length === 0) {
|
|
2443
|
+
warnings.push(
|
|
2444
|
+
`No runs matched baseline/candidate labels on metadata.${options.cohortKey}.`
|
|
2445
|
+
);
|
|
2446
|
+
}
|
|
2447
|
+
return { runs: selected, warnings };
|
|
2448
|
+
}
|
|
2449
|
+
|
|
2450
|
+
// packages/core/src/cohort/metrics.ts
|
|
2451
|
+
function asNumber(value) {
|
|
2452
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
2453
|
+
}
|
|
2454
|
+
function toolSteps(events) {
|
|
2455
|
+
const ordering = [];
|
|
2456
|
+
const choices = /* @__PURE__ */ new Set();
|
|
2457
|
+
const sorted = [...events].filter((event) => event.event === "step_started").sort((a, b) => a.timestamp - b.timestamp);
|
|
2458
|
+
for (const event of sorted) {
|
|
2459
|
+
const step = event;
|
|
2460
|
+
if (step.type !== "tool") continue;
|
|
2461
|
+
const name = typeof step.metadata?.toolName === "string" ? step.metadata.toolName : step.name;
|
|
2462
|
+
ordering.push(name);
|
|
2463
|
+
choices.add(name);
|
|
2464
|
+
}
|
|
2465
|
+
return { choices: [...choices].sort(), ordering };
|
|
2466
|
+
}
|
|
2467
|
+
async function computeCohortRunMetrics(input) {
|
|
2468
|
+
const events = await readTraceEventsFromFile(input.filePath);
|
|
2469
|
+
const summary = buildRunSummary(events);
|
|
2470
|
+
const tools = toolSteps(events);
|
|
2471
|
+
const outcomes = extractOutcomesFromTraceEvents(events);
|
|
2472
|
+
const observationFailures = outcomes.filter((item) => item.status === "failed").length;
|
|
2473
|
+
const metadata = input.metadata ?? {};
|
|
2474
|
+
const retryCount = asNumber(metadata.attempt) !== void 0 && asNumber(metadata.attempt) > 1 ? asNumber(metadata.attempt) - 1 : typeof metadata.retryOf === "string" ? 1 : 0;
|
|
2475
|
+
return {
|
|
2476
|
+
runId: input.runId,
|
|
2477
|
+
...input.cohortLabel !== void 0 ? { cohortLabel: input.cohortLabel } : {},
|
|
2478
|
+
groupKey: input.groupKey,
|
|
2479
|
+
status: summary.status,
|
|
2480
|
+
error: summary.status === "error",
|
|
2481
|
+
durationMs: summary.durationMs ?? input.durationMs,
|
|
2482
|
+
llmCallCount: summary.llmSteps,
|
|
2483
|
+
tokenUsageTotal: summary.totalTokens?.total,
|
|
2484
|
+
retryCount,
|
|
2485
|
+
observationFailures,
|
|
2486
|
+
guardrailFailures: asNumber(metadata.guardrailFailures) ?? 0,
|
|
2487
|
+
circuitViolations: asNumber(metadata.circuitViolations) ?? 0,
|
|
2488
|
+
redactionWarnings: asNumber(metadata.redactionWarnings) ?? 0,
|
|
2489
|
+
toolChoices: tools.choices,
|
|
2490
|
+
toolOrdering: tools.ordering
|
|
2491
|
+
};
|
|
2492
|
+
}
|
|
2493
|
+
function percentile2(values, p) {
|
|
2494
|
+
if (values.length === 0) return void 0;
|
|
2495
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
2496
|
+
const idx = Math.min(
|
|
2497
|
+
sorted.length - 1,
|
|
2498
|
+
Math.max(0, Math.ceil(p / 100 * sorted.length) - 1)
|
|
2499
|
+
);
|
|
2500
|
+
return sorted[idx];
|
|
2501
|
+
}
|
|
2502
|
+
function dominantToolChoice(runs) {
|
|
2503
|
+
const counts = /* @__PURE__ */ new Map();
|
|
2504
|
+
for (const run of runs) {
|
|
2505
|
+
const signature = run.toolChoices.join(",");
|
|
2506
|
+
if (signature === "") continue;
|
|
2507
|
+
counts.set(signature, (counts.get(signature) ?? 0) + 1);
|
|
2508
|
+
}
|
|
2509
|
+
let best;
|
|
2510
|
+
let bestCount = 0;
|
|
2511
|
+
for (const [key, count] of counts) {
|
|
2512
|
+
if (count > bestCount) {
|
|
2513
|
+
best = key;
|
|
2514
|
+
bestCount = count;
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
return best;
|
|
2518
|
+
}
|
|
2519
|
+
function orderingSignature(runs) {
|
|
2520
|
+
const counts = /* @__PURE__ */ new Map();
|
|
2521
|
+
for (const run of runs) {
|
|
2522
|
+
const signature = run.toolOrdering.join(">");
|
|
2523
|
+
if (signature === "") continue;
|
|
2524
|
+
counts.set(signature, (counts.get(signature) ?? 0) + 1);
|
|
2525
|
+
}
|
|
2526
|
+
let best;
|
|
2527
|
+
let bestCount = 0;
|
|
2528
|
+
for (const [key, count] of counts) {
|
|
2529
|
+
if (count > bestCount) {
|
|
2530
|
+
best = key;
|
|
2531
|
+
bestCount = count;
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
return best;
|
|
2535
|
+
}
|
|
2536
|
+
function aggregateCohortMetrics(runs, groupKey, cohortLabel) {
|
|
2537
|
+
const durations = runs.map((run) => run.durationMs).filter((value) => typeof value === "number");
|
|
2538
|
+
const tokenValues = runs.map((run) => run.tokenUsageTotal).filter((value) => typeof value === "number");
|
|
2539
|
+
const errors = runs.filter((run) => run.error).length;
|
|
2540
|
+
const observationFailures = runs.reduce((sum, run) => sum + run.observationFailures, 0);
|
|
2541
|
+
return {
|
|
2542
|
+
groupKey,
|
|
2543
|
+
...cohortLabel !== void 0 ? { cohortLabel } : {},
|
|
2544
|
+
runCount: runs.length,
|
|
2545
|
+
errorRate: runs.length > 0 ? errors / runs.length : 0,
|
|
2546
|
+
avgDurationMs: durations.length > 0 ? durations.reduce((sum, value) => sum + value, 0) / durations.length : void 0,
|
|
2547
|
+
p95DurationMs: percentile2(durations, 95),
|
|
2548
|
+
avgLlmCallCount: runs.length > 0 ? runs.reduce((sum, run) => sum + run.llmCallCount, 0) / runs.length : 0,
|
|
2549
|
+
avgTokenUsage: tokenValues.length > 0 ? tokenValues.reduce((sum, value) => sum + value, 0) / tokenValues.length : void 0,
|
|
2550
|
+
avgRetryCount: runs.length > 0 ? runs.reduce((sum, run) => sum + run.retryCount, 0) / runs.length : 0,
|
|
2551
|
+
observationFailureRate: runs.length > 0 ? observationFailures / runs.length : 0,
|
|
2552
|
+
avgGuardrailFailures: runs.length > 0 ? runs.reduce((sum, run) => sum + run.guardrailFailures, 0) / runs.length : 0,
|
|
2553
|
+
avgCircuitViolations: runs.length > 0 ? runs.reduce((sum, run) => sum + run.circuitViolations, 0) / runs.length : 0,
|
|
2554
|
+
avgRedactionWarnings: runs.length > 0 ? runs.reduce((sum, run) => sum + run.redactionWarnings, 0) / runs.length : 0,
|
|
2555
|
+
dominantToolChoice: dominantToolChoice(runs),
|
|
2556
|
+
toolOrderingSignature: orderingSignature(runs)
|
|
2557
|
+
};
|
|
2558
|
+
}
|
|
2559
|
+
|
|
2560
|
+
// packages/core/src/cohort/analyze.ts
|
|
2561
|
+
var DEFAULT_METRICS = [
|
|
2562
|
+
"errorRate",
|
|
2563
|
+
"duration",
|
|
2564
|
+
"toolChoice",
|
|
2565
|
+
"observationFailure"
|
|
2566
|
+
];
|
|
2567
|
+
function normalizeMetrics(metrics) {
|
|
2568
|
+
if (metrics === void 0 || metrics.length === 0) return [...DEFAULT_METRICS];
|
|
2569
|
+
const allowed = new Set(COHORT_METRIC_IDS);
|
|
2570
|
+
return metrics.filter((metric) => allowed.has(metric));
|
|
2571
|
+
}
|
|
2572
|
+
async function analyzeCohort(runsInput, options) {
|
|
2573
|
+
const cohortKey = options.cohortKey ?? "cohort";
|
|
2574
|
+
const groupBySpec = parseGroupBySpec(options.groupBy);
|
|
2575
|
+
const metrics = normalizeMetrics(options.metrics);
|
|
2576
|
+
const { runs: filteredRuns, warnings } = filterRunsForCohort(runsInput, {
|
|
2577
|
+
cohortKey,
|
|
2578
|
+
baseline: options.baseline,
|
|
2579
|
+
candidate: options.candidate
|
|
2580
|
+
});
|
|
2581
|
+
const runMetrics = [];
|
|
2582
|
+
for (const run of filteredRuns) {
|
|
2583
|
+
if (run.filePath === void 0) continue;
|
|
2584
|
+
const cohortLabel = resolveCohortLabel(
|
|
2585
|
+
run,
|
|
2586
|
+
cohortKey,
|
|
2587
|
+
options.baseline,
|
|
2588
|
+
options.candidate
|
|
2589
|
+
);
|
|
2590
|
+
runMetrics.push(
|
|
2591
|
+
await computeCohortRunMetrics({
|
|
2592
|
+
runId: run.runId,
|
|
2593
|
+
filePath: run.filePath,
|
|
2594
|
+
metadata: run.metadata,
|
|
2595
|
+
status: run.status,
|
|
2596
|
+
durationMs: run.durationMs,
|
|
2597
|
+
groupKey: resolveRunGroupKey(run, groupBySpec),
|
|
2598
|
+
cohortLabel
|
|
2599
|
+
})
|
|
2600
|
+
);
|
|
2601
|
+
}
|
|
2602
|
+
const groupMap = /* @__PURE__ */ new Map();
|
|
2603
|
+
for (const run of runMetrics) {
|
|
2604
|
+
const key = `${run.cohortLabel ?? "*"}::${run.groupKey}`;
|
|
2605
|
+
const bucket = groupMap.get(key) ?? [];
|
|
2606
|
+
bucket.push(run);
|
|
2607
|
+
groupMap.set(key, bucket);
|
|
2608
|
+
}
|
|
2609
|
+
const groups = [...groupMap.entries()].sort(([a], [b]) => a.localeCompare(b)).map(
|
|
2610
|
+
([, bucket]) => aggregateCohortMetrics(
|
|
2611
|
+
bucket,
|
|
2612
|
+
bucket[0].groupKey,
|
|
2613
|
+
bucket[0]?.cohortLabel
|
|
2614
|
+
)
|
|
2615
|
+
);
|
|
2616
|
+
const comparisons = options.baseline !== void 0 && options.candidate !== void 0 ? (() => {
|
|
2617
|
+
const groupKeys = [
|
|
2618
|
+
...new Set(groups.map((group) => group.groupKey))
|
|
2619
|
+
].sort((a, b) => a.localeCompare(b));
|
|
2620
|
+
const items = [];
|
|
2621
|
+
for (const groupKey of groupKeys) {
|
|
2622
|
+
items.push(
|
|
2623
|
+
...compareCohortAggregates(groups, {
|
|
2624
|
+
baseline: options.baseline,
|
|
2625
|
+
candidate: options.candidate,
|
|
2626
|
+
metrics,
|
|
2627
|
+
groupKey
|
|
2628
|
+
})
|
|
2629
|
+
);
|
|
2630
|
+
}
|
|
2631
|
+
return items;
|
|
2632
|
+
})() : [];
|
|
2633
|
+
const regression = comparisons.some((item) => item.regression);
|
|
2634
|
+
return {
|
|
2635
|
+
ok: !regression,
|
|
2636
|
+
traceDir: options.traceDir,
|
|
2637
|
+
...options.baseline !== void 0 ? { baseline: options.baseline } : {},
|
|
2638
|
+
...options.candidate !== void 0 ? { candidate: options.candidate } : {},
|
|
2639
|
+
cohortKey,
|
|
2640
|
+
groupBy: options.groupBy ?? "model",
|
|
2641
|
+
metrics,
|
|
2642
|
+
groups,
|
|
2643
|
+
comparisons,
|
|
2644
|
+
runs: runMetrics,
|
|
2645
|
+
warnings
|
|
2646
|
+
};
|
|
2647
|
+
}
|
|
2648
|
+
|
|
2649
|
+
// packages/core/src/cohort/render.ts
|
|
2650
|
+
function formatRate(value) {
|
|
2651
|
+
if (value === void 0) return "n/a";
|
|
2652
|
+
return `${(value * 100).toFixed(1)}%`;
|
|
2653
|
+
}
|
|
2654
|
+
function renderCohortSummaryMarkdown(result) {
|
|
2655
|
+
const lines = [];
|
|
2656
|
+
lines.push("# Cohort analysis");
|
|
2657
|
+
lines.push("");
|
|
2658
|
+
lines.push(`Trace directory: \`${result.traceDir}\``);
|
|
2659
|
+
lines.push(`Group by: \`${result.groupBy}\``);
|
|
2660
|
+
if (result.baseline !== void 0 && result.candidate !== void 0) {
|
|
2661
|
+
lines.push(
|
|
2662
|
+
`Baseline/Candidate key: \`${result.cohortKey}\` (${result.baseline} vs ${result.candidate})`
|
|
2663
|
+
);
|
|
2664
|
+
}
|
|
2665
|
+
lines.push(`Status: **${result.ok ? "PASS" : "REGRESSION"}**`);
|
|
2666
|
+
lines.push("");
|
|
2667
|
+
if (result.warnings.length > 0) {
|
|
2668
|
+
lines.push("## Warnings");
|
|
2669
|
+
for (const warning of result.warnings) lines.push(`- ${warning}`);
|
|
2670
|
+
lines.push("");
|
|
2671
|
+
}
|
|
2672
|
+
lines.push("## Groups");
|
|
2673
|
+
for (const group of result.groups) {
|
|
2674
|
+
lines.push(
|
|
2675
|
+
`### ${group.cohortLabel ?? "all"} / ${group.groupKey} (${group.runCount} runs)`
|
|
2676
|
+
);
|
|
2677
|
+
lines.push(`- Error rate: ${formatRate(group.errorRate)}`);
|
|
2678
|
+
if (group.avgDurationMs !== void 0) {
|
|
2679
|
+
lines.push(`- Avg duration: ${Math.round(group.avgDurationMs)} ms`);
|
|
2680
|
+
}
|
|
2681
|
+
if (group.dominantToolChoice !== void 0) {
|
|
2682
|
+
lines.push(`- Dominant tools: ${group.dominantToolChoice}`);
|
|
2683
|
+
}
|
|
2684
|
+
lines.push(
|
|
2685
|
+
`- Observation failure rate: ${formatRate(group.observationFailureRate)}`
|
|
2686
|
+
);
|
|
2687
|
+
lines.push("");
|
|
2688
|
+
}
|
|
2689
|
+
if (result.comparisons.length > 0) {
|
|
2690
|
+
lines.push("## Comparisons");
|
|
2691
|
+
for (const comparison of result.comparisons) {
|
|
2692
|
+
const flag = comparison.regression ? " **REGRESSION**" : "";
|
|
2693
|
+
lines.push(`- ${comparison.message}${flag}`);
|
|
2694
|
+
}
|
|
2695
|
+
lines.push("");
|
|
2696
|
+
}
|
|
2697
|
+
return lines.join("\n").trimEnd();
|
|
2698
|
+
}
|
|
2699
|
+
function renderCohortReportHtml(result) {
|
|
2700
|
+
const rows = result.groups.map(
|
|
2701
|
+
(group) => `<tr><td>${escapeHtml(group.cohortLabel ?? "all")}</td><td>${escapeHtml(group.groupKey)}</td><td>${group.runCount}</td><td>${escapeHtml(formatRate(group.errorRate))}</td><td>${group.avgDurationMs !== void 0 ? Math.round(group.avgDurationMs) : "n/a"}</td></tr>`
|
|
2702
|
+
).join("");
|
|
2703
|
+
const comparisons = result.comparisons.map(
|
|
2704
|
+
(item) => `<li>${escapeHtml(item.message)}${item.regression ? " <strong>REGRESSION</strong>" : ""}</li>`
|
|
2705
|
+
).join("");
|
|
2706
|
+
return `<!DOCTYPE html>
|
|
2707
|
+
<html lang="en">
|
|
2708
|
+
<head>
|
|
2709
|
+
<meta charset="utf-8" />
|
|
2710
|
+
<title>Cohort report</title>
|
|
2711
|
+
<style>
|
|
2712
|
+
body { font-family: system-ui, sans-serif; margin: 2rem; }
|
|
2713
|
+
table { border-collapse: collapse; width: 100%; }
|
|
2714
|
+
th, td { border: 1px solid #ddd; padding: 0.5rem; text-align: left; }
|
|
2715
|
+
th { background: #f6f6f6; }
|
|
2716
|
+
</style>
|
|
2717
|
+
</head>
|
|
2718
|
+
<body>
|
|
2719
|
+
<h1>Cohort analysis</h1>
|
|
2720
|
+
<p>Status: <strong>${result.ok ? "PASS" : "REGRESSION"}</strong></p>
|
|
2721
|
+
<p>Trace directory: <code>${escapeHtml(result.traceDir)}</code></p>
|
|
2722
|
+
<h2>Groups</h2>
|
|
2723
|
+
<table>
|
|
2724
|
+
<thead><tr><th>Cohort</th><th>Group</th><th>Runs</th><th>Error rate</th><th>Avg duration (ms)</th></tr></thead>
|
|
2725
|
+
<tbody>${rows}</tbody>
|
|
2726
|
+
</table>
|
|
2727
|
+
${result.comparisons.length > 0 ? `<h2>Comparisons</h2><ul>${comparisons}</ul>` : ""}
|
|
2728
|
+
</body>
|
|
2729
|
+
</html>`;
|
|
2730
|
+
}
|
|
2731
|
+
function renderCohortReport(result, options = {}) {
|
|
2732
|
+
const format = options.format ?? "markdown";
|
|
2733
|
+
if (format === "json") return JSON.stringify(result, null, 2);
|
|
2734
|
+
if (format === "html") return renderCohortReportHtml(result);
|
|
2735
|
+
return renderCohortSummaryMarkdown(result);
|
|
2736
|
+
}
|
|
2737
|
+
|
|
2738
|
+
export { COHORT_METRIC_IDS, DEFAULT_SUITE_ARTIFACTS_DIR, DEFAULT_SUITE_CONFIG_NAMES, SESSION_WORKFLOW_KEYS, aggregateBundleSafeStatus, aggregateSessionCheckResults, analyzeCohort, buildActivitySummary, buildBundleMetadata, buildBundleSummaryMarkdown, buildLocalExplanation, buildPlaceholderArtifact, buildSessionIndex, buildTraceStats, bundleFailsOnSafety, compareCohortAggregates, defaultBundleOutputPath, defaultSuiteConfigTemplate, deriveSessionStatus, enrichSessionRunRecord, enrichSessionSummary, extractSessionWorkflowMetadata, filterMetasBySessionScope, filterTraces, groupSessionCohorts, isAgentInspectTrace, loadSessionRunRecords, loadSuiteConfig, loadTraceMetadataList, normalizeBundleOutputPath, normalizeSuiteConfig, parseCohortMetricList, parseDurationFilter, parseGroupBySpec, renderActivitySummaryHuman, renderCohortReport, renderCohortSummaryMarkdown, renderSuiteReport, renderSuiteReportMarkdown, renderTraceStats, resolveBundleRunIds, resolveSuiteCaseTrace, resolveSuiteConfigPath, runSuite, searchTraces, sessionKeyForRun, toMetadataSafeStatus, traceMetasToSessionRunRecords, validateSuiteConfig };
|
|
1656
2739
|
//# sourceMappingURL=advanced.mjs.map
|
|
1657
2740
|
//# sourceMappingURL=advanced.mjs.map
|