agent-inspect 1.2.0 → 1.4.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 +41 -4
- package/README.md +41 -38
- package/docs/ADAPTERS.md +122 -21
- package/docs/API.md +25 -7
- package/docs/ARCHITECTURE.md +1 -1
- package/docs/CLI.md +71 -2
- package/docs/EXPORTS.md +80 -8
- package/docs/GETTING-STARTED.md +29 -1
- package/docs/KNOWN-ISSUES.md +6 -0
- package/docs/LIMITATIONS.md +17 -0
- package/docs/SCHEMA.md +6 -4
- package/package.json +1 -1
- package/packages/cli/dist/index.cjs +1019 -14
- package/packages/cli/dist/index.cjs.map +1 -1
- package/packages/cli/dist/index.mjs +1019 -14
- package/packages/cli/dist/index.mjs.map +1 -1
- package/packages/core/dist/index.cjs +890 -14
- package/packages/core/dist/index.cjs.map +1 -1
- package/packages/core/dist/index.d.cts +176 -3
- package/packages/core/dist/index.d.ts +176 -3
- package/packages/core/dist/index.mjs +881 -15
- package/packages/core/dist/index.mjs.map +1 -1
|
@@ -478,7 +478,7 @@ function stableHash(value) {
|
|
|
478
478
|
const h = crypto__default.default.createHash("sha256").update(value, "utf8").digest("hex");
|
|
479
479
|
return h.slice(0, 8);
|
|
480
480
|
}
|
|
481
|
-
function compileRules(rules) {
|
|
481
|
+
function compileRules(rules, extraKeys) {
|
|
482
482
|
const out = /* @__PURE__ */ new Map();
|
|
483
483
|
const set = (r) => {
|
|
484
484
|
const k = toKey(r.key);
|
|
@@ -487,6 +487,11 @@ function compileRules(rules) {
|
|
|
487
487
|
for (const k of DEFAULT_REDACT_KEYS) {
|
|
488
488
|
set({ key: k, strategy: "full" });
|
|
489
489
|
}
|
|
490
|
+
for (const k of extraKeys ?? []) {
|
|
491
|
+
if (typeof k === "string" && k.length > 0) {
|
|
492
|
+
set({ key: k, strategy: "full" });
|
|
493
|
+
}
|
|
494
|
+
}
|
|
490
495
|
for (const r of rules ?? []) {
|
|
491
496
|
if (typeof r === "string") {
|
|
492
497
|
set({ key: r, strategy: "full" });
|
|
@@ -504,7 +509,7 @@ function compileRules(rules) {
|
|
|
504
509
|
var Redactor = class {
|
|
505
510
|
#rules;
|
|
506
511
|
constructor(options) {
|
|
507
|
-
this.#rules = compileRules(options?.rules);
|
|
512
|
+
this.#rules = compileRules(options?.rules, options?.extraKeys);
|
|
508
513
|
}
|
|
509
514
|
redactValue(key, value) {
|
|
510
515
|
const k = toKey(key);
|
|
@@ -1206,6 +1211,91 @@ function warn(message, error) {
|
|
|
1206
1211
|
}
|
|
1207
1212
|
console.warn(`${base}: ${formatError(error).message}`);
|
|
1208
1213
|
}
|
|
1214
|
+
|
|
1215
|
+
// packages/core/src/redaction-profiles.ts
|
|
1216
|
+
var SHARE_PROFILE_EXTRA_KEYS = [
|
|
1217
|
+
"userEmail",
|
|
1218
|
+
"customerEmail",
|
|
1219
|
+
"phone",
|
|
1220
|
+
"phoneNumber",
|
|
1221
|
+
"address",
|
|
1222
|
+
"ip",
|
|
1223
|
+
"ipAddress",
|
|
1224
|
+
"sessionId",
|
|
1225
|
+
"requestId",
|
|
1226
|
+
"correlationId",
|
|
1227
|
+
"decisionId",
|
|
1228
|
+
"groupId",
|
|
1229
|
+
"customerId",
|
|
1230
|
+
"userId",
|
|
1231
|
+
"accountId",
|
|
1232
|
+
"tenantId",
|
|
1233
|
+
"orgId",
|
|
1234
|
+
"organizationId",
|
|
1235
|
+
"traceId",
|
|
1236
|
+
"spanId",
|
|
1237
|
+
"parentSpanId"
|
|
1238
|
+
];
|
|
1239
|
+
var STRICT_PROFILE_EXTRA_KEYS = [
|
|
1240
|
+
"prompt",
|
|
1241
|
+
"completion",
|
|
1242
|
+
"input",
|
|
1243
|
+
"output",
|
|
1244
|
+
"inputPreview",
|
|
1245
|
+
"outputPreview",
|
|
1246
|
+
"message",
|
|
1247
|
+
"messages",
|
|
1248
|
+
"transcript",
|
|
1249
|
+
"context",
|
|
1250
|
+
"document",
|
|
1251
|
+
"documents",
|
|
1252
|
+
"chunk",
|
|
1253
|
+
"chunks",
|
|
1254
|
+
"retrieval",
|
|
1255
|
+
"query"
|
|
1256
|
+
];
|
|
1257
|
+
function resolveRedactionProfile(profile = "local") {
|
|
1258
|
+
switch (profile) {
|
|
1259
|
+
case "local":
|
|
1260
|
+
return { profile: "local", extraKeys: [] };
|
|
1261
|
+
case "share":
|
|
1262
|
+
return {
|
|
1263
|
+
profile: "share",
|
|
1264
|
+
extraKeys: SHARE_PROFILE_EXTRA_KEYS,
|
|
1265
|
+
maxMetadataValueLengthCap: 500,
|
|
1266
|
+
maxPreviewLengthCap: 200
|
|
1267
|
+
};
|
|
1268
|
+
case "strict":
|
|
1269
|
+
return {
|
|
1270
|
+
profile: "strict",
|
|
1271
|
+
extraKeys: [...SHARE_PROFILE_EXTRA_KEYS, ...STRICT_PROFILE_EXTRA_KEYS],
|
|
1272
|
+
maxMetadataValueLengthCap: 200,
|
|
1273
|
+
maxPreviewLengthCap: 80
|
|
1274
|
+
};
|
|
1275
|
+
default:
|
|
1276
|
+
return { profile: "local", extraKeys: [] };
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
function isPreviewKey(key) {
|
|
1280
|
+
return key.toLowerCase().includes("preview");
|
|
1281
|
+
}
|
|
1282
|
+
function applyProfileMetadataCaps(maxMetadataValueLength, maxPreviewLength, resolved) {
|
|
1283
|
+
let meta = maxMetadataValueLength;
|
|
1284
|
+
let preview = maxPreviewLength;
|
|
1285
|
+
if (resolved.maxMetadataValueLengthCap !== void 0) {
|
|
1286
|
+
meta = Math.min(meta, resolved.maxMetadataValueLengthCap);
|
|
1287
|
+
}
|
|
1288
|
+
if (resolved.maxPreviewLengthCap !== void 0) {
|
|
1289
|
+
preview = Math.min(preview, resolved.maxPreviewLengthCap);
|
|
1290
|
+
}
|
|
1291
|
+
return { maxMetadataValueLength: meta, maxPreviewLength: preview };
|
|
1292
|
+
}
|
|
1293
|
+
function truncateStringForProfile(value, key, maxMetadataValueLength, maxPreviewLength) {
|
|
1294
|
+
const max = isPreviewKey(key) ? maxPreviewLength : maxMetadataValueLength;
|
|
1295
|
+
if (max <= 0) return "\u2026";
|
|
1296
|
+
if (value.length <= max) return value;
|
|
1297
|
+
return `${value.slice(0, max)}\u2026`;
|
|
1298
|
+
}
|
|
1209
1299
|
function isRecord6(value) {
|
|
1210
1300
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1211
1301
|
}
|
|
@@ -1356,7 +1446,7 @@ var DEFAULT_MAX_EVENT_BYTES = 65536;
|
|
|
1356
1446
|
function isRecord7(value) {
|
|
1357
1447
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1358
1448
|
}
|
|
1359
|
-
function
|
|
1449
|
+
function isPreviewKey2(key) {
|
|
1360
1450
|
return key.toLowerCase().includes("preview");
|
|
1361
1451
|
}
|
|
1362
1452
|
function truncateString(value, maxLen) {
|
|
@@ -1373,11 +1463,28 @@ function resolveTraceSafetyOptions(options) {
|
|
|
1373
1463
|
{
|
|
1374
1464
|
redactEnabled = true;
|
|
1375
1465
|
}
|
|
1466
|
+
const profile = options?.redactionProfile ?? "local";
|
|
1467
|
+
const resolvedProfile = resolveRedactionProfile(profile);
|
|
1468
|
+
const userMaxMetadata = "undefined" === "number" && Number.isFinite(options.maxMetadataValueLength) && options.maxMetadataValueLength >= 0 ? Math.floor(options.maxMetadataValueLength) : void 0;
|
|
1469
|
+
const userMaxPreview = "undefined" === "number" && Number.isFinite(options.maxPreviewLength) && options.maxPreviewLength >= 0 ? Math.floor(options.maxPreviewLength) : void 0;
|
|
1470
|
+
let maxMetadataValueLength = userMaxMetadata ?? DEFAULT_MAX_METADATA_VALUE_LENGTH;
|
|
1471
|
+
let maxPreviewLength = userMaxPreview ?? DEFAULT_MAX_PREVIEW_LENGTH;
|
|
1472
|
+
if (redactEnabled && profile !== "local") {
|
|
1473
|
+
const capped = applyProfileMetadataCaps(
|
|
1474
|
+
maxMetadataValueLength,
|
|
1475
|
+
maxPreviewLength,
|
|
1476
|
+
resolvedProfile
|
|
1477
|
+
);
|
|
1478
|
+
maxMetadataValueLength = capped.maxMetadataValueLength;
|
|
1479
|
+
maxPreviewLength = capped.maxPreviewLength;
|
|
1480
|
+
}
|
|
1376
1481
|
return {
|
|
1377
1482
|
redactEnabled,
|
|
1378
1483
|
redactionRules,
|
|
1379
|
-
|
|
1380
|
-
|
|
1484
|
+
redactionProfile: profile,
|
|
1485
|
+
profileExtraKeys: redactEnabled ? resolvedProfile.extraKeys : [],
|
|
1486
|
+
maxMetadataValueLength,
|
|
1487
|
+
maxPreviewLength,
|
|
1381
1488
|
maxEventBytes: "undefined" === "number" && Number.isFinite(options.maxEventBytes) && options.maxEventBytes > 0 ? Math.floor(options.maxEventBytes) : DEFAULT_MAX_EVENT_BYTES
|
|
1382
1489
|
};
|
|
1383
1490
|
}
|
|
@@ -1385,7 +1492,7 @@ function boundMetadataValue(key, value, opts, seen, depth) {
|
|
|
1385
1492
|
if (depth > 32) return "[MaxDepth]";
|
|
1386
1493
|
if (value === null || typeof value !== "object") {
|
|
1387
1494
|
if (typeof value === "string") {
|
|
1388
|
-
const max =
|
|
1495
|
+
const max = isPreviewKey2(key) ? opts.maxPreviewLength : opts.maxMetadataValueLength;
|
|
1389
1496
|
return truncateString(value, max);
|
|
1390
1497
|
}
|
|
1391
1498
|
return value;
|
|
@@ -1411,7 +1518,10 @@ function boundMetadataValue(key, value, opts, seen, depth) {
|
|
|
1411
1518
|
}
|
|
1412
1519
|
function redactMetadata(metadata, opts) {
|
|
1413
1520
|
if (!opts.redactEnabled) return { ...metadata };
|
|
1414
|
-
const redactor = new Redactor({
|
|
1521
|
+
const redactor = new Redactor({
|
|
1522
|
+
rules: opts.redactionRules,
|
|
1523
|
+
extraKeys: opts.profileExtraKeys
|
|
1524
|
+
});
|
|
1415
1525
|
return redactor.redactRecord(metadata);
|
|
1416
1526
|
}
|
|
1417
1527
|
function prepareMetadataForDisk(metadata, opts) {
|
|
@@ -1897,6 +2007,574 @@ function filterTraces(traces, options) {
|
|
|
1897
2007
|
}
|
|
1898
2008
|
return out;
|
|
1899
2009
|
}
|
|
2010
|
+
|
|
2011
|
+
// packages/core/src/timeline.ts
|
|
2012
|
+
function finite(n) {
|
|
2013
|
+
return typeof n === "number" && Number.isFinite(n);
|
|
2014
|
+
}
|
|
2015
|
+
function pickStreamingMeta(metadata) {
|
|
2016
|
+
if (!metadata || typeof metadata !== "object") return void 0;
|
|
2017
|
+
const chunkCount = metadata.chunkCount;
|
|
2018
|
+
const streamDurationMs = metadata.streamDurationMs;
|
|
2019
|
+
const streamedCharCount = metadata.streamedCharCount;
|
|
2020
|
+
if (!finite(chunkCount) && !finite(streamDurationMs) && !finite(streamedCharCount)) {
|
|
2021
|
+
return void 0;
|
|
2022
|
+
}
|
|
2023
|
+
return {
|
|
2024
|
+
...finite(chunkCount) ? { chunkCount } : {},
|
|
2025
|
+
...finite(streamDurationMs) ? { streamDurationMs } : {},
|
|
2026
|
+
...finite(streamedCharCount) ? { streamedCharCount } : {}
|
|
2027
|
+
};
|
|
2028
|
+
}
|
|
2029
|
+
function pickCorrelation(metadata) {
|
|
2030
|
+
if (!metadata || typeof metadata !== "object") return void 0;
|
|
2031
|
+
const out = {};
|
|
2032
|
+
for (const key of [
|
|
2033
|
+
"correlationId",
|
|
2034
|
+
"requestId",
|
|
2035
|
+
"decisionId",
|
|
2036
|
+
"groupId"
|
|
2037
|
+
]) {
|
|
2038
|
+
const v = metadata[key];
|
|
2039
|
+
if (typeof v === "string" && v.trim() !== "") {
|
|
2040
|
+
out[key] = v;
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
2044
|
+
}
|
|
2045
|
+
function buildRunTimeline(events, options = {}) {
|
|
2046
|
+
const started = events.find(
|
|
2047
|
+
(e) => e.event === "run_started"
|
|
2048
|
+
);
|
|
2049
|
+
const completed = events.filter(
|
|
2050
|
+
(e) => e.event === "run_completed"
|
|
2051
|
+
);
|
|
2052
|
+
const lastCompleted = completed[completed.length - 1];
|
|
2053
|
+
const runId = started?.runId ?? events.find((e) => typeof e.runId === "string")?.runId ?? "unknown-run";
|
|
2054
|
+
const runStart = started && finite(started.startTime) ? started.startTime : started && finite(started.timestamp) ? started.timestamp : void 0;
|
|
2055
|
+
const status = lastCompleted ? lastCompleted.status : started ? "running" : "unknown";
|
|
2056
|
+
const steps = /* @__PURE__ */ new Map();
|
|
2057
|
+
for (const e of events) {
|
|
2058
|
+
if (e.event === "step_started") {
|
|
2059
|
+
const s = e;
|
|
2060
|
+
steps.set(s.stepId, {
|
|
2061
|
+
name: s.name,
|
|
2062
|
+
type: s.type,
|
|
2063
|
+
parentId: s.parentId,
|
|
2064
|
+
startedAt: finite(s.startTime) ? s.startTime : s.timestamp,
|
|
2065
|
+
status: "running",
|
|
2066
|
+
metadata: s.metadata
|
|
2067
|
+
});
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
for (const e of events) {
|
|
2071
|
+
if (e.event !== "step_completed") continue;
|
|
2072
|
+
const c = e;
|
|
2073
|
+
const node = steps.get(c.stepId);
|
|
2074
|
+
if (!node) continue;
|
|
2075
|
+
node.status = c.status;
|
|
2076
|
+
if (finite(c.durationMs)) node.durationMs = c.durationMs;
|
|
2077
|
+
}
|
|
2078
|
+
const depthCache = /* @__PURE__ */ new Map();
|
|
2079
|
+
const computeDepth = (stepId) => {
|
|
2080
|
+
const cached = depthCache.get(stepId);
|
|
2081
|
+
if (cached !== void 0) return cached;
|
|
2082
|
+
const node = steps.get(stepId);
|
|
2083
|
+
if (!node) return 0;
|
|
2084
|
+
const parent = node.parentId;
|
|
2085
|
+
if (typeof parent !== "string" || parent.trim() === "" || !steps.has(parent)) {
|
|
2086
|
+
depthCache.set(stepId, 0);
|
|
2087
|
+
return 0;
|
|
2088
|
+
}
|
|
2089
|
+
const d = Math.min(1e3, computeDepth(parent) + 1);
|
|
2090
|
+
depthCache.set(stepId, d);
|
|
2091
|
+
return d;
|
|
2092
|
+
};
|
|
2093
|
+
const entries = [];
|
|
2094
|
+
for (const [stepId, s] of steps.entries()) {
|
|
2095
|
+
const offsetMs = runStart !== void 0 && finite(s.startedAt) ? Math.max(0, s.startedAt - runStart) : 0;
|
|
2096
|
+
entries.push({
|
|
2097
|
+
stepId,
|
|
2098
|
+
name: s.name,
|
|
2099
|
+
type: s.type,
|
|
2100
|
+
status: s.status,
|
|
2101
|
+
depth: computeDepth(stepId),
|
|
2102
|
+
startedAt: s.startedAt,
|
|
2103
|
+
offsetMs,
|
|
2104
|
+
durationMs: s.durationMs,
|
|
2105
|
+
isError: s.status === "error",
|
|
2106
|
+
streaming: pickStreamingMeta(s.metadata)
|
|
2107
|
+
});
|
|
2108
|
+
}
|
|
2109
|
+
entries.sort((a, b) => a.startedAt - b.startedAt);
|
|
2110
|
+
const slowTopN = options.slowTopN ?? 3;
|
|
2111
|
+
if (options.focus === "slow" && entries.length > 0) {
|
|
2112
|
+
const ranked = [...entries].filter((e) => finite(e.durationMs)).sort((a, b) => (b.durationMs ?? 0) - (a.durationMs ?? 0));
|
|
2113
|
+
const slowIds = new Set(
|
|
2114
|
+
ranked.slice(0, slowTopN).map((e) => e.stepId)
|
|
2115
|
+
);
|
|
2116
|
+
for (const e of entries) {
|
|
2117
|
+
if (slowIds.has(e.stepId)) e.slow = true;
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
return {
|
|
2121
|
+
runId,
|
|
2122
|
+
name: typeof started?.name === "string" && started.name.trim() !== "" ? started.name : void 0,
|
|
2123
|
+
status,
|
|
2124
|
+
startedAt: runStart,
|
|
2125
|
+
endedAt: lastCompleted && finite(lastCompleted.endTime) ? lastCompleted.endTime : void 0,
|
|
2126
|
+
durationMs: lastCompleted && finite(lastCompleted.durationMs) ? lastCompleted.durationMs : void 0,
|
|
2127
|
+
correlation: pickCorrelation(
|
|
2128
|
+
started?.metadata
|
|
2129
|
+
),
|
|
2130
|
+
entries
|
|
2131
|
+
};
|
|
2132
|
+
}
|
|
2133
|
+
function renderTimeline(timeline, options = {}) {
|
|
2134
|
+
const lines = [];
|
|
2135
|
+
lines.push(`Timeline: ${timeline.name ?? timeline.runId}`);
|
|
2136
|
+
lines.push(`Run ID: ${timeline.runId}`);
|
|
2137
|
+
lines.push(`Status: ${timeline.status}`);
|
|
2138
|
+
if (timeline.startedAt !== void 0) {
|
|
2139
|
+
lines.push(`Started: ${formatTimestamp(timeline.startedAt)}`);
|
|
2140
|
+
}
|
|
2141
|
+
if (timeline.durationMs !== void 0) {
|
|
2142
|
+
lines.push(`Duration: ${formatDuration2(timeline.durationMs)}`);
|
|
2143
|
+
}
|
|
2144
|
+
if (timeline.correlation) {
|
|
2145
|
+
const parts = Object.entries(timeline.correlation).filter(([, v]) => typeof v === "string").map(([k, v]) => `${k}=${v}`);
|
|
2146
|
+
if (parts.length > 0) {
|
|
2147
|
+
lines.push(`Correlation: ${parts.join(", ")}`);
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
lines.push("");
|
|
2151
|
+
lines.push("Steps (chronological):");
|
|
2152
|
+
const show = timeline.entries.filter((e) => {
|
|
2153
|
+
if (options.focus === "slow") return e.slow === true;
|
|
2154
|
+
return true;
|
|
2155
|
+
});
|
|
2156
|
+
if (show.length === 0) {
|
|
2157
|
+
lines.push(
|
|
2158
|
+
options.focus === "slow" ? "(no steps with duration for slow focus)" : "(no steps)"
|
|
2159
|
+
);
|
|
2160
|
+
return lines.join("\n");
|
|
2161
|
+
}
|
|
2162
|
+
for (const e of show) {
|
|
2163
|
+
const prefix = e.slow ? "[slow] " : "";
|
|
2164
|
+
const typeTag = e.type === "llm" ? "llm" : e.type === "tool" ? "tool" : e.type;
|
|
2165
|
+
const dur = e.durationMs !== void 0 ? formatDuration2(e.durationMs) : "-";
|
|
2166
|
+
const err = e.isError ? " error" : "";
|
|
2167
|
+
const off = formatDuration2(e.offsetMs);
|
|
2168
|
+
let line = `${prefix}+${off} ${typeTag}:${e.name} (${dur})${err}`;
|
|
2169
|
+
if (e.streaming?.chunkCount !== void 0) {
|
|
2170
|
+
line += ` chunks=${e.streaming.chunkCount}`;
|
|
2171
|
+
}
|
|
2172
|
+
if (e.streaming?.streamDurationMs !== void 0) {
|
|
2173
|
+
line += ` stream=${formatDuration2(e.streaming.streamDurationMs)}`;
|
|
2174
|
+
}
|
|
2175
|
+
lines.push(line);
|
|
2176
|
+
}
|
|
2177
|
+
return lines.join("\n");
|
|
2178
|
+
}
|
|
2179
|
+
function percentile(sorted, p) {
|
|
2180
|
+
if (sorted.length === 0) return void 0;
|
|
2181
|
+
const idx = Math.min(
|
|
2182
|
+
sorted.length - 1,
|
|
2183
|
+
Math.max(0, Math.ceil(p / 100 * sorted.length) - 1)
|
|
2184
|
+
);
|
|
2185
|
+
return sorted[idx];
|
|
2186
|
+
}
|
|
2187
|
+
async function readRunStartedMetadata(filePath) {
|
|
2188
|
+
try {
|
|
2189
|
+
const raw = await promises.readFile(filePath, "utf-8");
|
|
2190
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
2191
|
+
const trimmed = line.trim();
|
|
2192
|
+
if (trimmed === "") continue;
|
|
2193
|
+
let parsed;
|
|
2194
|
+
try {
|
|
2195
|
+
parsed = JSON.parse(trimmed);
|
|
2196
|
+
} catch {
|
|
2197
|
+
continue;
|
|
2198
|
+
}
|
|
2199
|
+
if (!isTraceEvent(parsed)) continue;
|
|
2200
|
+
if (parsed.event !== "run_started") continue;
|
|
2201
|
+
const rs = parsed;
|
|
2202
|
+
if (rs.metadata && typeof rs.metadata === "object") {
|
|
2203
|
+
return rs.metadata;
|
|
2204
|
+
}
|
|
2205
|
+
return void 0;
|
|
2206
|
+
}
|
|
2207
|
+
} catch {
|
|
2208
|
+
}
|
|
2209
|
+
return void 0;
|
|
2210
|
+
}
|
|
2211
|
+
function metaMatchesCorrelation(metadata, correlationId, groupId) {
|
|
2212
|
+
if (correlationId) {
|
|
2213
|
+
const v = metadata?.correlationId;
|
|
2214
|
+
if (typeof v !== "string" || v !== correlationId) return false;
|
|
2215
|
+
}
|
|
2216
|
+
if (groupId) {
|
|
2217
|
+
const v = metadata?.groupId;
|
|
2218
|
+
if (typeof v !== "string" || v !== groupId) return false;
|
|
2219
|
+
}
|
|
2220
|
+
return true;
|
|
2221
|
+
}
|
|
2222
|
+
async function buildTraceStats(metas, options) {
|
|
2223
|
+
let filtered = filterTraces(metas, { since: options.since });
|
|
2224
|
+
if (options.correlationId || options.groupId) {
|
|
2225
|
+
const next = [];
|
|
2226
|
+
for (const m of filtered) {
|
|
2227
|
+
const md = await readRunStartedMetadata(m.filePath);
|
|
2228
|
+
if (metaMatchesCorrelation(md, options.correlationId, options.groupId)) {
|
|
2229
|
+
next.push(m);
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
filtered = next;
|
|
2233
|
+
}
|
|
2234
|
+
let successCount = 0;
|
|
2235
|
+
let errorCount = 0;
|
|
2236
|
+
let runningCount = 0;
|
|
2237
|
+
let unknownCount = 0;
|
|
2238
|
+
const durations = [];
|
|
2239
|
+
let totalSteps = 0;
|
|
2240
|
+
let totalLlmSteps = 0;
|
|
2241
|
+
let totalToolSteps = 0;
|
|
2242
|
+
let totalErrorSteps = 0;
|
|
2243
|
+
const slowestRuns = [];
|
|
2244
|
+
const slowestSteps = [];
|
|
2245
|
+
for (const m of filtered) {
|
|
2246
|
+
if (m.status === "success") successCount += 1;
|
|
2247
|
+
else if (m.status === "error") errorCount += 1;
|
|
2248
|
+
else if (m.status === "running") runningCount += 1;
|
|
2249
|
+
else unknownCount += 1;
|
|
2250
|
+
if (typeof m.durationMs === "number" && Number.isFinite(m.durationMs) && m.durationMs >= 0) {
|
|
2251
|
+
durations.push(m.durationMs);
|
|
2252
|
+
slowestRuns.push({
|
|
2253
|
+
runId: m.runId,
|
|
2254
|
+
name: m.name,
|
|
2255
|
+
durationMs: m.durationMs,
|
|
2256
|
+
status: m.status
|
|
2257
|
+
});
|
|
2258
|
+
}
|
|
2259
|
+
try {
|
|
2260
|
+
const events = await readTraceEvents(m.runId, options.traceDir);
|
|
2261
|
+
if (events.length === 0) continue;
|
|
2262
|
+
const summary = buildRunSummary(events);
|
|
2263
|
+
totalSteps += summary.totalSteps;
|
|
2264
|
+
totalLlmSteps += summary.llmSteps;
|
|
2265
|
+
totalToolSteps += summary.toolSteps;
|
|
2266
|
+
totalErrorSteps += summary.errorSteps;
|
|
2267
|
+
const steps = collectCompletedSteps(events, m.runId);
|
|
2268
|
+
for (const s of steps) {
|
|
2269
|
+
slowestSteps.push(s);
|
|
2270
|
+
}
|
|
2271
|
+
} catch {
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
slowestRuns.sort((a, b) => (b.durationMs ?? 0) - (a.durationMs ?? 0));
|
|
2275
|
+
slowestSteps.sort((a, b) => b.durationMs - a.durationMs);
|
|
2276
|
+
const runLimit = options.slowRunLimit ?? 5;
|
|
2277
|
+
const stepLimit = options.slowStepLimit ?? 5;
|
|
2278
|
+
const sortedDur = [...durations].sort((a, b) => a - b);
|
|
2279
|
+
const totalRuns = filtered.length;
|
|
2280
|
+
const errorRate = totalRuns > 0 ? errorCount / totalRuns : 0;
|
|
2281
|
+
const sumDur = durations.reduce((a, b) => a + b, 0);
|
|
2282
|
+
return {
|
|
2283
|
+
traceDir: options.traceDir,
|
|
2284
|
+
...options.since ? { since: options.since } : {},
|
|
2285
|
+
...options.correlationId ? { correlationId: options.correlationId } : {},
|
|
2286
|
+
...options.groupId ? { groupId: options.groupId } : {},
|
|
2287
|
+
totalRuns,
|
|
2288
|
+
successCount,
|
|
2289
|
+
errorCount,
|
|
2290
|
+
runningCount,
|
|
2291
|
+
unknownCount,
|
|
2292
|
+
errorRate,
|
|
2293
|
+
duration: {
|
|
2294
|
+
...sortedDur.length > 0 ? {
|
|
2295
|
+
minMs: sortedDur[0],
|
|
2296
|
+
maxMs: sortedDur[sortedDur.length - 1],
|
|
2297
|
+
avgMs: sumDur / sortedDur.length,
|
|
2298
|
+
p50Ms: percentile(sortedDur, 50),
|
|
2299
|
+
p95Ms: percentile(sortedDur, 95)
|
|
2300
|
+
} : {}
|
|
2301
|
+
},
|
|
2302
|
+
totalSteps,
|
|
2303
|
+
avgStepsPerRun: totalRuns > 0 ? totalSteps / totalRuns : 0,
|
|
2304
|
+
totalLlmSteps,
|
|
2305
|
+
totalToolSteps,
|
|
2306
|
+
totalErrorSteps,
|
|
2307
|
+
slowestRuns: slowestRuns.slice(0, runLimit),
|
|
2308
|
+
slowestSteps: slowestSteps.slice(0, stepLimit)
|
|
2309
|
+
};
|
|
2310
|
+
}
|
|
2311
|
+
function collectCompletedSteps(events, runId) {
|
|
2312
|
+
const started = /* @__PURE__ */ new Map();
|
|
2313
|
+
const out = [];
|
|
2314
|
+
for (const e of events) {
|
|
2315
|
+
if (e.event === "step_started") {
|
|
2316
|
+
const s = e;
|
|
2317
|
+
started.set(s.stepId, { name: s.name, type: s.type });
|
|
2318
|
+
}
|
|
2319
|
+
if (e.event === "step_completed") {
|
|
2320
|
+
const c = e;
|
|
2321
|
+
if (c.status !== "success" && c.status !== "error") continue;
|
|
2322
|
+
if (typeof c.durationMs !== "number" || !Number.isFinite(c.durationMs)) {
|
|
2323
|
+
continue;
|
|
2324
|
+
}
|
|
2325
|
+
const meta = started.get(c.stepId);
|
|
2326
|
+
out.push({
|
|
2327
|
+
runId,
|
|
2328
|
+
stepName: meta?.name ?? c.stepId,
|
|
2329
|
+
stepType: meta?.type ?? "logic",
|
|
2330
|
+
durationMs: c.durationMs
|
|
2331
|
+
});
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
return out;
|
|
2335
|
+
}
|
|
2336
|
+
function renderTraceStats(stats) {
|
|
2337
|
+
const lines = [];
|
|
2338
|
+
lines.push("Trace stats (local)");
|
|
2339
|
+
lines.push(`Directory: ${stats.traceDir}`);
|
|
2340
|
+
if (stats.since) lines.push(`Since: ${stats.since}`);
|
|
2341
|
+
if (stats.correlationId) lines.push(`Correlation ID: ${stats.correlationId}`);
|
|
2342
|
+
if (stats.groupId) lines.push(`Group ID: ${stats.groupId}`);
|
|
2343
|
+
lines.push("");
|
|
2344
|
+
lines.push(`Runs: ${stats.totalRuns}`);
|
|
2345
|
+
lines.push(
|
|
2346
|
+
` success: ${stats.successCount} error: ${stats.errorCount} running: ${stats.runningCount} unknown: ${stats.unknownCount}`
|
|
2347
|
+
);
|
|
2348
|
+
lines.push(`Error rate: ${(stats.errorRate * 100).toFixed(1)}%`);
|
|
2349
|
+
if (stats.duration.avgMs !== void 0) {
|
|
2350
|
+
lines.push(
|
|
2351
|
+
`Duration: min ${formatDuration2(stats.duration.minMs ?? 0)} | avg ${formatDuration2(stats.duration.avgMs)} | p50 ${formatDuration2(stats.duration.p50Ms ?? 0)} | p95 ${formatDuration2(stats.duration.p95Ms ?? 0)} | max ${formatDuration2(stats.duration.maxMs ?? 0)}`
|
|
2352
|
+
);
|
|
2353
|
+
}
|
|
2354
|
+
lines.push("");
|
|
2355
|
+
lines.push(`Steps: ${stats.totalSteps} (avg ${stats.avgStepsPerRun.toFixed(1)} per run)`);
|
|
2356
|
+
lines.push(
|
|
2357
|
+
` LLM: ${stats.totalLlmSteps} tool: ${stats.totalToolSteps} errors: ${stats.totalErrorSteps}`
|
|
2358
|
+
);
|
|
2359
|
+
if (stats.slowestRuns.length > 0) {
|
|
2360
|
+
lines.push("");
|
|
2361
|
+
lines.push("Slowest runs:");
|
|
2362
|
+
for (const r of stats.slowestRuns) {
|
|
2363
|
+
lines.push(
|
|
2364
|
+
` ${r.runId} | ${r.name ?? "-"} | ${formatDuration2(r.durationMs ?? 0)} | ${r.status}`
|
|
2365
|
+
);
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
if (stats.slowestSteps.length > 0) {
|
|
2369
|
+
lines.push("");
|
|
2370
|
+
lines.push("Slowest steps:");
|
|
2371
|
+
for (const s of stats.slowestSteps) {
|
|
2372
|
+
lines.push(
|
|
2373
|
+
` ${s.runId} | ${s.stepType}:${s.stepName} | ${formatDuration2(s.durationMs)}`
|
|
2374
|
+
);
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
return lines.join("\n");
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
// packages/core/src/search.ts
|
|
2381
|
+
function parseDurationFilter(expr) {
|
|
2382
|
+
const raw = expr.trim();
|
|
2383
|
+
const m = raw.match(/^(>=|<=|>|<)\s*(.+)$/);
|
|
2384
|
+
if (!m) {
|
|
2385
|
+
throw new Error(
|
|
2386
|
+
`Invalid --duration "${expr}". Use forms like >5s, >=500ms, <2m.`
|
|
2387
|
+
);
|
|
2388
|
+
}
|
|
2389
|
+
const op = m[1];
|
|
2390
|
+
const ms = parseDuration(m[2].trim());
|
|
2391
|
+
return { op, ms };
|
|
2392
|
+
}
|
|
2393
|
+
function durationMatches(valueMs, filter) {
|
|
2394
|
+
if (valueMs === void 0 || !Number.isFinite(valueMs)) return false;
|
|
2395
|
+
switch (filter.op) {
|
|
2396
|
+
case ">":
|
|
2397
|
+
return valueMs > filter.ms;
|
|
2398
|
+
case ">=":
|
|
2399
|
+
return valueMs >= filter.ms;
|
|
2400
|
+
case "<":
|
|
2401
|
+
return valueMs < filter.ms;
|
|
2402
|
+
case "<=":
|
|
2403
|
+
return valueMs <= filter.ms;
|
|
2404
|
+
default:
|
|
2405
|
+
return false;
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
function normalizeStepTypeFilter(kind, type) {
|
|
2409
|
+
const v = (kind ?? type)?.trim().toLowerCase();
|
|
2410
|
+
return v && v !== "" ? v : void 0;
|
|
2411
|
+
}
|
|
2412
|
+
function nameMatches(hay, needle) {
|
|
2413
|
+
return hay.toLowerCase().includes(needle.toLowerCase());
|
|
2414
|
+
}
|
|
2415
|
+
async function searchTraces(metas, options) {
|
|
2416
|
+
let filtered = filterTraces(metas, { since: options.since });
|
|
2417
|
+
const stepTypeFilter = normalizeStepTypeFilter(options.kind, options.type);
|
|
2418
|
+
const nameQuery = options.name?.trim();
|
|
2419
|
+
const toolQuery = options.tool?.trim();
|
|
2420
|
+
let durationFilter;
|
|
2421
|
+
if (options.duration) {
|
|
2422
|
+
durationFilter = parseDurationFilter(options.duration);
|
|
2423
|
+
}
|
|
2424
|
+
const limit = options.limit ?? 50;
|
|
2425
|
+
const hasContentFilter = Boolean(
|
|
2426
|
+
options.status || stepTypeFilter || nameQuery || toolQuery || durationFilter
|
|
2427
|
+
);
|
|
2428
|
+
const results = [];
|
|
2429
|
+
if (!hasContentFilter) {
|
|
2430
|
+
for (const m of filtered) {
|
|
2431
|
+
results.push({
|
|
2432
|
+
runId: m.runId,
|
|
2433
|
+
runName: m.name,
|
|
2434
|
+
runStatus: m.status,
|
|
2435
|
+
timestamp: m.startedAt,
|
|
2436
|
+
durationMs: m.durationMs,
|
|
2437
|
+
matchReason: "trace in directory",
|
|
2438
|
+
matchedFields: ["run"],
|
|
2439
|
+
filePath: m.filePath
|
|
2440
|
+
});
|
|
2441
|
+
}
|
|
2442
|
+
return results.slice(0, limit);
|
|
2443
|
+
}
|
|
2444
|
+
for (const m of filtered) {
|
|
2445
|
+
if (options.status && m.status !== options.status) continue;
|
|
2446
|
+
let events = [];
|
|
2447
|
+
try {
|
|
2448
|
+
events = await readTraceEvents(m.runId, options.traceDir);
|
|
2449
|
+
} catch {
|
|
2450
|
+
continue;
|
|
2451
|
+
}
|
|
2452
|
+
if (events.length === 0) continue;
|
|
2453
|
+
const runMatches = matchRunLevel(m, {
|
|
2454
|
+
stepTypeFilter,
|
|
2455
|
+
nameQuery,
|
|
2456
|
+
toolQuery,
|
|
2457
|
+
durationFilter,
|
|
2458
|
+
statusFilter: options.status
|
|
2459
|
+
});
|
|
2460
|
+
results.push(...runMatches);
|
|
2461
|
+
const stepMatches = matchStepLevel(m, events, {
|
|
2462
|
+
stepTypeFilter,
|
|
2463
|
+
nameQuery,
|
|
2464
|
+
toolQuery,
|
|
2465
|
+
durationFilter,
|
|
2466
|
+
statusFilter: options.status
|
|
2467
|
+
});
|
|
2468
|
+
results.push(...stepMatches);
|
|
2469
|
+
}
|
|
2470
|
+
results.sort((a, b) => {
|
|
2471
|
+
const ta = a.timestamp ?? 0;
|
|
2472
|
+
const tb = b.timestamp ?? 0;
|
|
2473
|
+
if (ta !== tb) return ta - tb;
|
|
2474
|
+
const runCmp = a.runId.localeCompare(b.runId);
|
|
2475
|
+
if (runCmp !== 0) return runCmp;
|
|
2476
|
+
return (a.stepName ?? "").localeCompare(b.stepName ?? "");
|
|
2477
|
+
});
|
|
2478
|
+
return results.slice(0, limit);
|
|
2479
|
+
}
|
|
2480
|
+
function matchRunLevel(m, opts) {
|
|
2481
|
+
if (opts.stepTypeFilter || opts.toolQuery) return [];
|
|
2482
|
+
const out = [];
|
|
2483
|
+
const fields = [];
|
|
2484
|
+
if (opts.statusFilter && m.status === opts.statusFilter) {
|
|
2485
|
+
fields.push("run.status");
|
|
2486
|
+
}
|
|
2487
|
+
if (opts.nameQuery && nameMatches(m.name ?? m.runId, opts.nameQuery)) {
|
|
2488
|
+
fields.push("run.name");
|
|
2489
|
+
}
|
|
2490
|
+
if (opts.durationFilter && durationMatches(m.durationMs, opts.durationFilter)) {
|
|
2491
|
+
fields.push("run.durationMs");
|
|
2492
|
+
}
|
|
2493
|
+
if (fields.length === 0) return out;
|
|
2494
|
+
out.push({
|
|
2495
|
+
runId: m.runId,
|
|
2496
|
+
runName: m.name,
|
|
2497
|
+
runStatus: m.status,
|
|
2498
|
+
timestamp: m.startedAt,
|
|
2499
|
+
durationMs: m.durationMs,
|
|
2500
|
+
matchReason: `run match: ${fields.join(", ")}`,
|
|
2501
|
+
matchedFields: fields,
|
|
2502
|
+
filePath: m.filePath
|
|
2503
|
+
});
|
|
2504
|
+
return out;
|
|
2505
|
+
}
|
|
2506
|
+
function matchStepLevel(m, events, opts) {
|
|
2507
|
+
const out = [];
|
|
2508
|
+
const started = /* @__PURE__ */ new Map();
|
|
2509
|
+
for (const e of events) {
|
|
2510
|
+
if (e.event === "step_started") {
|
|
2511
|
+
started.set(e.stepId, e);
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2514
|
+
for (const e of events) {
|
|
2515
|
+
if (e.event !== "step_completed") continue;
|
|
2516
|
+
const c = e;
|
|
2517
|
+
const s = started.get(c.stepId);
|
|
2518
|
+
if (!s) continue;
|
|
2519
|
+
const fields = [];
|
|
2520
|
+
const stepType = s.type;
|
|
2521
|
+
if (opts.stepTypeFilter && stepType !== opts.stepTypeFilter) {
|
|
2522
|
+
continue;
|
|
2523
|
+
}
|
|
2524
|
+
const hasStepFilters = opts.stepTypeFilter || opts.nameQuery || opts.toolQuery || opts.durationFilter || opts.statusFilter === "error" || opts.statusFilter === "success";
|
|
2525
|
+
if (!hasStepFilters) continue;
|
|
2526
|
+
if (opts.statusFilter === "error" && c.status === "error") {
|
|
2527
|
+
fields.push("step.status");
|
|
2528
|
+
} else if (opts.statusFilter === "success" && c.status === "success") {
|
|
2529
|
+
fields.push("step.status");
|
|
2530
|
+
} else if (opts.statusFilter === "error" || opts.statusFilter === "success") {
|
|
2531
|
+
continue;
|
|
2532
|
+
}
|
|
2533
|
+
if (opts.nameQuery) {
|
|
2534
|
+
if (!nameMatches(s.name, opts.nameQuery)) continue;
|
|
2535
|
+
fields.push("step.name");
|
|
2536
|
+
}
|
|
2537
|
+
if (opts.toolQuery) {
|
|
2538
|
+
const toolName = typeof s.metadata?.toolName === "string" ? s.metadata.toolName : s.name;
|
|
2539
|
+
if (!nameMatches(toolName, opts.toolQuery)) continue;
|
|
2540
|
+
fields.push("step.tool");
|
|
2541
|
+
}
|
|
2542
|
+
if (opts.durationFilter) {
|
|
2543
|
+
if (!durationMatches(c.durationMs, opts.durationFilter)) continue;
|
|
2544
|
+
fields.push("step.durationMs");
|
|
2545
|
+
}
|
|
2546
|
+
if (opts.stepTypeFilter) {
|
|
2547
|
+
fields.push("step.type");
|
|
2548
|
+
}
|
|
2549
|
+
if (fields.length === 0) continue;
|
|
2550
|
+
out.push({
|
|
2551
|
+
runId: m.runId,
|
|
2552
|
+
runName: m.name,
|
|
2553
|
+
runStatus: m.status,
|
|
2554
|
+
stepId: c.stepId,
|
|
2555
|
+
stepName: s.name,
|
|
2556
|
+
stepType,
|
|
2557
|
+
timestamp: s.startTime ?? s.timestamp,
|
|
2558
|
+
durationMs: c.durationMs,
|
|
2559
|
+
matchReason: `step match: ${fields.join(", ")}`,
|
|
2560
|
+
matchedFields: fields,
|
|
2561
|
+
filePath: m.filePath
|
|
2562
|
+
});
|
|
2563
|
+
}
|
|
2564
|
+
return out;
|
|
2565
|
+
}
|
|
2566
|
+
async function loadTraceMetadataList(_traceDir, fileNames, getPath) {
|
|
2567
|
+
const metas = [];
|
|
2568
|
+
for (const fileName of fileNames) {
|
|
2569
|
+
try {
|
|
2570
|
+
const filePath = getPath(fileName);
|
|
2571
|
+
const meta = await extractMetadata(filePath);
|
|
2572
|
+
metas.push(meta);
|
|
2573
|
+
} catch {
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
return metas;
|
|
2577
|
+
}
|
|
1900
2578
|
var KNOWN_EVENTS = /* @__PURE__ */ new Set([
|
|
1901
2579
|
"run_started",
|
|
1902
2580
|
"run_completed",
|
|
@@ -3218,6 +3896,134 @@ Object.assign(stepImpl, {
|
|
|
3218
3896
|
// packages/core/src/exporters/types.ts
|
|
3219
3897
|
var EXPORT_PAYLOAD_VERSION = "0.1.2";
|
|
3220
3898
|
|
|
3899
|
+
// packages/core/src/exporters/redact-export.ts
|
|
3900
|
+
function isRecord9(value) {
|
|
3901
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3902
|
+
}
|
|
3903
|
+
function deepClone(value) {
|
|
3904
|
+
if (value === null || typeof value !== "object") {
|
|
3905
|
+
return value;
|
|
3906
|
+
}
|
|
3907
|
+
if (Array.isArray(value)) {
|
|
3908
|
+
return value.map((item) => deepClone(item));
|
|
3909
|
+
}
|
|
3910
|
+
const out = {};
|
|
3911
|
+
for (const [k, v] of Object.entries(value)) {
|
|
3912
|
+
out[k] = deepClone(v);
|
|
3913
|
+
}
|
|
3914
|
+
return out;
|
|
3915
|
+
}
|
|
3916
|
+
function boundAttributeValues(record, maxMetadataValueLength, maxPreviewLength, seen, depth) {
|
|
3917
|
+
if (depth > 32) {
|
|
3918
|
+
return { truncated: true, reason: "maxDepth" };
|
|
3919
|
+
}
|
|
3920
|
+
const out = {};
|
|
3921
|
+
for (const [key, value] of Object.entries(record)) {
|
|
3922
|
+
out[key] = boundValue(value, key, maxMetadataValueLength, maxPreviewLength, seen, depth);
|
|
3923
|
+
}
|
|
3924
|
+
return out;
|
|
3925
|
+
}
|
|
3926
|
+
function boundValue(value, key, maxMetadataValueLength, maxPreviewLength, seen, depth) {
|
|
3927
|
+
if (value === null || typeof value !== "object") {
|
|
3928
|
+
if (typeof value === "string") {
|
|
3929
|
+
return truncateStringForProfile(
|
|
3930
|
+
value,
|
|
3931
|
+
key,
|
|
3932
|
+
maxMetadataValueLength,
|
|
3933
|
+
maxPreviewLength
|
|
3934
|
+
);
|
|
3935
|
+
}
|
|
3936
|
+
return value;
|
|
3937
|
+
}
|
|
3938
|
+
if (seen.has(value)) return "[Circular]";
|
|
3939
|
+
seen.add(value);
|
|
3940
|
+
if (Array.isArray(value)) {
|
|
3941
|
+
return value.slice(0, 50).map(
|
|
3942
|
+
(item, index) => boundValue(
|
|
3943
|
+
item,
|
|
3944
|
+
String(index),
|
|
3945
|
+
maxMetadataValueLength,
|
|
3946
|
+
maxPreviewLength,
|
|
3947
|
+
seen,
|
|
3948
|
+
depth + 1
|
|
3949
|
+
)
|
|
3950
|
+
);
|
|
3951
|
+
}
|
|
3952
|
+
return boundAttributeValues(
|
|
3953
|
+
value,
|
|
3954
|
+
maxMetadataValueLength,
|
|
3955
|
+
maxPreviewLength,
|
|
3956
|
+
seen,
|
|
3957
|
+
depth + 1
|
|
3958
|
+
);
|
|
3959
|
+
}
|
|
3960
|
+
function redactEventAttributes(attrs, redactor, maxMetadataValueLength, maxPreviewLength) {
|
|
3961
|
+
if (!attrs || Object.keys(attrs).length === 0) {
|
|
3962
|
+
return attrs;
|
|
3963
|
+
}
|
|
3964
|
+
const redacted = redactor.redactRecord(attrs);
|
|
3965
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
3966
|
+
const bounded = boundAttributeValues(
|
|
3967
|
+
redacted,
|
|
3968
|
+
maxMetadataValueLength,
|
|
3969
|
+
maxPreviewLength,
|
|
3970
|
+
seen,
|
|
3971
|
+
0
|
|
3972
|
+
);
|
|
3973
|
+
const err = bounded.error;
|
|
3974
|
+
if (isRecord9(err) && typeof err.message === "string") {
|
|
3975
|
+
bounded.error = {
|
|
3976
|
+
...err,
|
|
3977
|
+
message: truncateStringForProfile(
|
|
3978
|
+
err.message,
|
|
3979
|
+
"message",
|
|
3980
|
+
maxMetadataValueLength,
|
|
3981
|
+
maxPreviewLength
|
|
3982
|
+
),
|
|
3983
|
+
...typeof err.stack === "string" ? {
|
|
3984
|
+
stack: truncateStringForProfile(
|
|
3985
|
+
err.stack,
|
|
3986
|
+
"stack",
|
|
3987
|
+
maxMetadataValueLength,
|
|
3988
|
+
maxPreviewLength
|
|
3989
|
+
)
|
|
3990
|
+
} : {}
|
|
3991
|
+
};
|
|
3992
|
+
}
|
|
3993
|
+
return bounded;
|
|
3994
|
+
}
|
|
3995
|
+
function redactRunTreeForExport(tree, options) {
|
|
3996
|
+
const profile = options?.redactionProfile ?? "local";
|
|
3997
|
+
if (profile === "local") {
|
|
3998
|
+
return deepClone(tree);
|
|
3999
|
+
}
|
|
4000
|
+
const resolved = resolveRedactionProfile(profile);
|
|
4001
|
+
const { maxMetadataValueLength, maxPreviewLength } = applyProfileMetadataCaps(
|
|
4002
|
+
2e3,
|
|
4003
|
+
500,
|
|
4004
|
+
resolved
|
|
4005
|
+
);
|
|
4006
|
+
const redactor = new Redactor({ extraKeys: resolved.extraKeys });
|
|
4007
|
+
const clone = deepClone(tree);
|
|
4008
|
+
function walk(nodes) {
|
|
4009
|
+
for (const node of nodes) {
|
|
4010
|
+
if (node.event.attributes !== void 0) {
|
|
4011
|
+
node.event.attributes = redactEventAttributes(
|
|
4012
|
+
node.event.attributes,
|
|
4013
|
+
redactor,
|
|
4014
|
+
maxMetadataValueLength,
|
|
4015
|
+
maxPreviewLength
|
|
4016
|
+
);
|
|
4017
|
+
}
|
|
4018
|
+
if (node.children.length > 0) {
|
|
4019
|
+
walk(node.children);
|
|
4020
|
+
}
|
|
4021
|
+
}
|
|
4022
|
+
}
|
|
4023
|
+
walk(clone.children);
|
|
4024
|
+
return clone;
|
|
4025
|
+
}
|
|
4026
|
+
|
|
3221
4027
|
// packages/core/src/exporters/html-exporter.ts
|
|
3222
4028
|
function renderTreeHtml(nodes, ulClass = "tree") {
|
|
3223
4029
|
if (nodes.length === 0) return "";
|
|
@@ -3952,20 +4758,22 @@ function mergeExportDefaults(options) {
|
|
|
3952
4758
|
includeErrors: options.includeErrors ?? true,
|
|
3953
4759
|
pretty: options.pretty,
|
|
3954
4760
|
redacted: options.redacted,
|
|
3955
|
-
maxAttributeLength: options.maxAttributeLength
|
|
4761
|
+
maxAttributeLength: options.maxAttributeLength,
|
|
4762
|
+
redactionProfile: options.redactionProfile ?? "local"
|
|
3956
4763
|
};
|
|
3957
4764
|
}
|
|
3958
4765
|
function exportRunTree(tree, options) {
|
|
3959
4766
|
const opts = mergeExportDefaults(options);
|
|
4767
|
+
const exportTree = opts.redactionProfile === "local" ? tree : redactRunTreeForExport(tree, { redactionProfile: opts.redactionProfile });
|
|
3960
4768
|
switch (opts.format) {
|
|
3961
4769
|
case "markdown":
|
|
3962
|
-
return exportMarkdown(
|
|
4770
|
+
return exportMarkdown(exportTree, opts);
|
|
3963
4771
|
case "html":
|
|
3964
|
-
return exportHtml(
|
|
4772
|
+
return exportHtml(exportTree, opts);
|
|
3965
4773
|
case "openinference":
|
|
3966
|
-
return exportOpenInference(
|
|
4774
|
+
return exportOpenInference(exportTree, opts);
|
|
3967
4775
|
case "otlp-json":
|
|
3968
|
-
return exportOtlpJson(
|
|
4776
|
+
return exportOtlpJson(exportTree, opts);
|
|
3969
4777
|
default: {
|
|
3970
4778
|
const _x = opts.format;
|
|
3971
4779
|
throw new Error(`Unsupported export format: ${String(_x)}`);
|
|
@@ -4801,6 +5609,15 @@ async function tail(options = {}) {
|
|
|
4801
5609
|
process.exitCode = 1;
|
|
4802
5610
|
}
|
|
4803
5611
|
}
|
|
5612
|
+
function parseRedactionProfile(s) {
|
|
5613
|
+
const v = (s ?? "local").trim().toLowerCase();
|
|
5614
|
+
if (v === "local" || v === "share" || v === "strict") {
|
|
5615
|
+
return v;
|
|
5616
|
+
}
|
|
5617
|
+
throw new Error(
|
|
5618
|
+
`Unsupported --redaction-profile "${s ?? ""}". Use local, share, or strict.`
|
|
5619
|
+
);
|
|
5620
|
+
}
|
|
4804
5621
|
function parseExportFormat(s) {
|
|
4805
5622
|
const v = (s ?? "markdown").trim().toLowerCase();
|
|
4806
5623
|
if (v === "markdown" || v === "html" || v === "openinference" || v === "otlp-json") {
|
|
@@ -4818,8 +5635,10 @@ async function exportCommand(runId, options = {}) {
|
|
|
4818
5635
|
return;
|
|
4819
5636
|
}
|
|
4820
5637
|
let format;
|
|
5638
|
+
let redactionProfile;
|
|
4821
5639
|
try {
|
|
4822
5640
|
format = parseExportFormat(options.format);
|
|
5641
|
+
redactionProfile = parseRedactionProfile(options.redactionProfile);
|
|
4823
5642
|
} catch (e) {
|
|
4824
5643
|
const msg = e instanceof Error ? e.message : String(e);
|
|
4825
5644
|
console.error(msg);
|
|
@@ -4858,7 +5677,8 @@ Trace directory: ${traceDir}`);
|
|
|
4858
5677
|
includeErrors: options.noErrors === true ? false : true,
|
|
4859
5678
|
pretty: true,
|
|
4860
5679
|
redacted: true,
|
|
4861
|
-
maxAttributeLength: 500
|
|
5680
|
+
maxAttributeLength: 500,
|
|
5681
|
+
redactionProfile
|
|
4862
5682
|
};
|
|
4863
5683
|
const result = exportRunTree(tree, exportOpts);
|
|
4864
5684
|
const validation = options.validate === true ? validateExport(result) : void 0;
|
|
@@ -5007,6 +5827,163 @@ Trace directory: ${traceDir}`
|
|
|
5007
5827
|
);
|
|
5008
5828
|
}
|
|
5009
5829
|
|
|
5830
|
+
// packages/cli/src/timeline.ts
|
|
5831
|
+
async function timelineCommand(runId, options = {}) {
|
|
5832
|
+
const id = typeof runId === "string" && runId.trim() !== "" ? runId.trim() : "";
|
|
5833
|
+
if (id === "") {
|
|
5834
|
+
console.error("Run id is required");
|
|
5835
|
+
process.exitCode = 1;
|
|
5836
|
+
return;
|
|
5837
|
+
}
|
|
5838
|
+
const traceDir = resolveTraceDir({ dir: options.dir });
|
|
5839
|
+
let events;
|
|
5840
|
+
try {
|
|
5841
|
+
events = await readTraceEvents(id, traceDir);
|
|
5842
|
+
} catch (e) {
|
|
5843
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
5844
|
+
console.error(`[AgentInspect] timeline failed: ${msg}`);
|
|
5845
|
+
process.exitCode = 1;
|
|
5846
|
+
return;
|
|
5847
|
+
}
|
|
5848
|
+
if (events.length === 0) {
|
|
5849
|
+
console.log(`Run not found: ${id}`);
|
|
5850
|
+
console.log(`Trace directory: ${traceDir}`);
|
|
5851
|
+
process.exitCode = 1;
|
|
5852
|
+
return;
|
|
5853
|
+
}
|
|
5854
|
+
const focus = options.focus?.trim().toLowerCase() === "slow" ? "slow" : "all";
|
|
5855
|
+
const timeline = buildRunTimeline(events, {
|
|
5856
|
+
focus: focus === "slow" ? "slow" : "all"
|
|
5857
|
+
});
|
|
5858
|
+
if (options.json) {
|
|
5859
|
+
console.log(JSON.stringify(timeline, null, 2));
|
|
5860
|
+
return;
|
|
5861
|
+
}
|
|
5862
|
+
console.log(renderTimeline(timeline, { focus }));
|
|
5863
|
+
}
|
|
5864
|
+
|
|
5865
|
+
// packages/cli/src/stats.ts
|
|
5866
|
+
async function statsCommand(options = {}) {
|
|
5867
|
+
try {
|
|
5868
|
+
const traceDir = resolveTraceDir({ dir: options.dir });
|
|
5869
|
+
const td = new TraceDirectory({ dir: traceDir });
|
|
5870
|
+
if (typeof options.since === "string" && options.since.trim() !== "") {
|
|
5871
|
+
parseDuration(options.since.trim());
|
|
5872
|
+
}
|
|
5873
|
+
const files = await td.list();
|
|
5874
|
+
if (files.length === 0) {
|
|
5875
|
+
if (options.json) {
|
|
5876
|
+
console.log(
|
|
5877
|
+
JSON.stringify({
|
|
5878
|
+
traceDir,
|
|
5879
|
+
totalRuns: 0,
|
|
5880
|
+
successCount: 0,
|
|
5881
|
+
errorCount: 0,
|
|
5882
|
+
runningCount: 0,
|
|
5883
|
+
unknownCount: 0,
|
|
5884
|
+
errorRate: 0,
|
|
5885
|
+
duration: {},
|
|
5886
|
+
totalSteps: 0,
|
|
5887
|
+
avgStepsPerRun: 0,
|
|
5888
|
+
totalLlmSteps: 0,
|
|
5889
|
+
totalToolSteps: 0,
|
|
5890
|
+
totalErrorSteps: 0,
|
|
5891
|
+
slowestRuns: [],
|
|
5892
|
+
slowestSteps: []
|
|
5893
|
+
})
|
|
5894
|
+
);
|
|
5895
|
+
} else {
|
|
5896
|
+
console.log("No AgentInspect runs found");
|
|
5897
|
+
console.log(`Trace directory: ${traceDir}`);
|
|
5898
|
+
}
|
|
5899
|
+
return;
|
|
5900
|
+
}
|
|
5901
|
+
const metas = [];
|
|
5902
|
+
for (const fileName of files) {
|
|
5903
|
+
try {
|
|
5904
|
+
metas.push(await extractMetadata(td.getPath(fileName)));
|
|
5905
|
+
} catch {
|
|
5906
|
+
}
|
|
5907
|
+
}
|
|
5908
|
+
const stats = await buildTraceStats(metas, {
|
|
5909
|
+
traceDir,
|
|
5910
|
+
since: options.since,
|
|
5911
|
+
correlationId: options.correlationId,
|
|
5912
|
+
groupId: options.groupId
|
|
5913
|
+
});
|
|
5914
|
+
if (options.json) {
|
|
5915
|
+
console.log(JSON.stringify(stats, null, 2));
|
|
5916
|
+
return;
|
|
5917
|
+
}
|
|
5918
|
+
console.log(renderTraceStats(stats));
|
|
5919
|
+
} catch (e) {
|
|
5920
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
5921
|
+
console.error(`[AgentInspect] stats failed: ${msg}`);
|
|
5922
|
+
process.exitCode = 1;
|
|
5923
|
+
}
|
|
5924
|
+
}
|
|
5925
|
+
|
|
5926
|
+
// packages/cli/src/search.ts
|
|
5927
|
+
function parseLimit2(raw) {
|
|
5928
|
+
if (raw === void 0 || raw.trim() === "") return 50;
|
|
5929
|
+
const n = Number.parseInt(raw, 10);
|
|
5930
|
+
if (!Number.isFinite(n) || n <= 0) return 50;
|
|
5931
|
+
return Math.min(n, 500);
|
|
5932
|
+
}
|
|
5933
|
+
async function searchCommand(options = {}) {
|
|
5934
|
+
try {
|
|
5935
|
+
const traceDir = resolveTraceDir({ dir: options.dir });
|
|
5936
|
+
const td = new TraceDirectory({ dir: traceDir });
|
|
5937
|
+
if (typeof options.since === "string" && options.since.trim() !== "") {
|
|
5938
|
+
parseDuration(options.since.trim());
|
|
5939
|
+
}
|
|
5940
|
+
if (options.duration) {
|
|
5941
|
+
parseDurationFilter(options.duration);
|
|
5942
|
+
}
|
|
5943
|
+
const files = await td.list();
|
|
5944
|
+
const metas = await loadTraceMetadataList(
|
|
5945
|
+
traceDir,
|
|
5946
|
+
files,
|
|
5947
|
+
(f) => td.getPath(f)
|
|
5948
|
+
);
|
|
5949
|
+
const status = options.status === "success" || options.status === "error" || options.status === "running" || options.status === "unknown" ? options.status : void 0;
|
|
5950
|
+
const results = await searchTraces(metas, {
|
|
5951
|
+
traceDir,
|
|
5952
|
+
since: options.since,
|
|
5953
|
+
status,
|
|
5954
|
+
kind: options.kind,
|
|
5955
|
+
type: options.type,
|
|
5956
|
+
name: options.name,
|
|
5957
|
+
tool: options.tool,
|
|
5958
|
+
duration: options.duration,
|
|
5959
|
+
limit: parseLimit2(options.limit)
|
|
5960
|
+
});
|
|
5961
|
+
if (options.json) {
|
|
5962
|
+
console.log(JSON.stringify(results, null, 2));
|
|
5963
|
+
return;
|
|
5964
|
+
}
|
|
5965
|
+
if (results.length === 0) {
|
|
5966
|
+
console.log("No matching traces found");
|
|
5967
|
+
console.log(`Trace directory: ${traceDir}`);
|
|
5968
|
+
return;
|
|
5969
|
+
}
|
|
5970
|
+
console.log(`Search results (${results.length})`);
|
|
5971
|
+
for (const r of results) {
|
|
5972
|
+
const step2 = r.stepName !== void 0 ? ` | ${r.stepType ?? "step"}:${r.stepName}` : "";
|
|
5973
|
+
const dur = r.durationMs !== void 0 ? ` | ${r.durationMs}ms` : "";
|
|
5974
|
+
console.log(
|
|
5975
|
+
`${r.runId}${step2} | ${r.runStatus}${dur} | ${r.matchReason}`
|
|
5976
|
+
);
|
|
5977
|
+
}
|
|
5978
|
+
console.log("");
|
|
5979
|
+
console.log(`Trace directory: ${traceDir}`);
|
|
5980
|
+
} catch (e) {
|
|
5981
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
5982
|
+
console.error(`[AgentInspect] search failed: ${msg}`);
|
|
5983
|
+
process.exitCode = 1;
|
|
5984
|
+
}
|
|
5985
|
+
}
|
|
5986
|
+
|
|
5010
5987
|
// packages/cli/src/index.ts
|
|
5011
5988
|
function runCommand(action) {
|
|
5012
5989
|
void action().catch((error) => {
|
|
@@ -5091,7 +6068,12 @@ function createCliProgram() {
|
|
|
5091
6068
|
"openinference",
|
|
5092
6069
|
"otlp-json"
|
|
5093
6070
|
])
|
|
5094
|
-
).option("-o, --output <path>", "write export to file (creates parent dirs)").option("--json", "emit JSON wrapper about the export (includes content when writing to stdout)").option("--validate", "validate exported payload shape after generation").option("--include-attributes", "include bounded attributes (review before sharing)").option("--no-metadata", "omit summary / metadata sections").option("--no-errors", "omit error sections").
|
|
6071
|
+
).option("-o, --output <path>", "write export to file (creates parent dirs)").option("--json", "emit JSON wrapper about the export (includes content when writing to stdout)").option("--validate", "validate exported payload shape after generation").option("--include-attributes", "include bounded attributes (review before sharing)").option("--no-metadata", "omit summary / metadata sections").option("--no-errors", "omit error sections").addOption(
|
|
6072
|
+
new commander.Option(
|
|
6073
|
+
"--redaction-profile <profile>",
|
|
6074
|
+
"redaction profile for exported copies: local, share, strict (default: local)"
|
|
6075
|
+
).choices(["local", "share", "strict"])
|
|
6076
|
+
).action((runId, opts) => {
|
|
5095
6077
|
runCommand(() => exportCommand(runId, opts));
|
|
5096
6078
|
});
|
|
5097
6079
|
program.command("diff").description("Compare two local AgentInspect JSONL traces (read-only)").argument("<left-run-id>", "first run id").argument("<right-run-id>", "second run id").option("--dir <path>", "trace directory").option("--json", "print diff result as JSON").option("--ignore-duration", "omit duration comparisons").option(
|
|
@@ -5115,6 +6097,29 @@ function createCliProgram() {
|
|
|
5115
6097
|
).option("--verbose", "show more left/right detail").action((leftRunId, rightRunId, opts) => {
|
|
5116
6098
|
runCommand(() => diffCommand(leftRunId, rightRunId, opts));
|
|
5117
6099
|
});
|
|
6100
|
+
program.command("timeline").description("Chronological timeline for a single run (local JSONL)").argument("<run-id>", "run id (e.g. from list output)").option("--dir <path>", "trace directory").option("--json", "print timeline as JSON").addOption(
|
|
6101
|
+
new commander.Option("--focus <mode>", "highlight slowest steps by duration").choices([
|
|
6102
|
+
"slow"
|
|
6103
|
+
])
|
|
6104
|
+
).action((runId, opts) => {
|
|
6105
|
+
runCommand(() => timelineCommand(runId, opts));
|
|
6106
|
+
});
|
|
6107
|
+
program.command("stats").description("Local aggregate stats over trace files (read-only)").option("--dir <path>", "trace directory").option("--since <duration>", "only include runs since a duration (e.g. 7d)").option("--correlation-id <id>", "filter by run_started.metadata.correlationId").option("--group-id <id>", "filter by run_started.metadata.groupId").option("--json", "print stats as JSON").action((opts) => {
|
|
6108
|
+
runCommand(() => statsCommand(opts));
|
|
6109
|
+
});
|
|
6110
|
+
program.command("search").description("Deterministic local search over trace files (read-only)").option("--dir <path>", "trace directory").option("--since <duration>", "only search runs since a duration").addOption(
|
|
6111
|
+
new commander.Option("--status <status>", "filter by run or step status").choices([
|
|
6112
|
+
"success",
|
|
6113
|
+
"error",
|
|
6114
|
+
"running",
|
|
6115
|
+
"unknown"
|
|
6116
|
+
])
|
|
6117
|
+
).option("--kind <kind>", "filter by step kind/type (llm, tool, logic, \u2026)").option("--type <type>", "alias for --kind on manual trace step type").option("--name <query>", "substring match on run or step name").option("--tool <query>", "substring match on tool step name or metadata.toolName").option(
|
|
6118
|
+
"--duration <expr>",
|
|
6119
|
+
"duration filter on run or step (e.g. >5s, >=500ms)"
|
|
6120
|
+
).option("--limit <number>", "max results (default 50)").option("--json", "print results as JSON").action((opts) => {
|
|
6121
|
+
runCommand(() => searchCommand(opts));
|
|
6122
|
+
});
|
|
5118
6123
|
return program;
|
|
5119
6124
|
}
|
|
5120
6125
|
function isPrimaryModule() {
|