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
|
@@ -467,7 +467,7 @@ function stableHash(value) {
|
|
|
467
467
|
const h = crypto.createHash("sha256").update(value, "utf8").digest("hex");
|
|
468
468
|
return h.slice(0, 8);
|
|
469
469
|
}
|
|
470
|
-
function compileRules(rules) {
|
|
470
|
+
function compileRules(rules, extraKeys) {
|
|
471
471
|
const out = /* @__PURE__ */ new Map();
|
|
472
472
|
const set = (r) => {
|
|
473
473
|
const k = toKey(r.key);
|
|
@@ -476,6 +476,11 @@ function compileRules(rules) {
|
|
|
476
476
|
for (const k of DEFAULT_REDACT_KEYS) {
|
|
477
477
|
set({ key: k, strategy: "full" });
|
|
478
478
|
}
|
|
479
|
+
for (const k of extraKeys ?? []) {
|
|
480
|
+
if (typeof k === "string" && k.length > 0) {
|
|
481
|
+
set({ key: k, strategy: "full" });
|
|
482
|
+
}
|
|
483
|
+
}
|
|
479
484
|
for (const r of rules ?? []) {
|
|
480
485
|
if (typeof r === "string") {
|
|
481
486
|
set({ key: r, strategy: "full" });
|
|
@@ -493,7 +498,7 @@ function compileRules(rules) {
|
|
|
493
498
|
var Redactor = class {
|
|
494
499
|
#rules;
|
|
495
500
|
constructor(options) {
|
|
496
|
-
this.#rules = compileRules(options?.rules);
|
|
501
|
+
this.#rules = compileRules(options?.rules, options?.extraKeys);
|
|
497
502
|
}
|
|
498
503
|
redactValue(key, value) {
|
|
499
504
|
const k = toKey(key);
|
|
@@ -1195,6 +1200,91 @@ function warn(message, error) {
|
|
|
1195
1200
|
}
|
|
1196
1201
|
console.warn(`${base}: ${formatError(error).message}`);
|
|
1197
1202
|
}
|
|
1203
|
+
|
|
1204
|
+
// packages/core/src/redaction-profiles.ts
|
|
1205
|
+
var SHARE_PROFILE_EXTRA_KEYS = [
|
|
1206
|
+
"userEmail",
|
|
1207
|
+
"customerEmail",
|
|
1208
|
+
"phone",
|
|
1209
|
+
"phoneNumber",
|
|
1210
|
+
"address",
|
|
1211
|
+
"ip",
|
|
1212
|
+
"ipAddress",
|
|
1213
|
+
"sessionId",
|
|
1214
|
+
"requestId",
|
|
1215
|
+
"correlationId",
|
|
1216
|
+
"decisionId",
|
|
1217
|
+
"groupId",
|
|
1218
|
+
"customerId",
|
|
1219
|
+
"userId",
|
|
1220
|
+
"accountId",
|
|
1221
|
+
"tenantId",
|
|
1222
|
+
"orgId",
|
|
1223
|
+
"organizationId",
|
|
1224
|
+
"traceId",
|
|
1225
|
+
"spanId",
|
|
1226
|
+
"parentSpanId"
|
|
1227
|
+
];
|
|
1228
|
+
var STRICT_PROFILE_EXTRA_KEYS = [
|
|
1229
|
+
"prompt",
|
|
1230
|
+
"completion",
|
|
1231
|
+
"input",
|
|
1232
|
+
"output",
|
|
1233
|
+
"inputPreview",
|
|
1234
|
+
"outputPreview",
|
|
1235
|
+
"message",
|
|
1236
|
+
"messages",
|
|
1237
|
+
"transcript",
|
|
1238
|
+
"context",
|
|
1239
|
+
"document",
|
|
1240
|
+
"documents",
|
|
1241
|
+
"chunk",
|
|
1242
|
+
"chunks",
|
|
1243
|
+
"retrieval",
|
|
1244
|
+
"query"
|
|
1245
|
+
];
|
|
1246
|
+
function resolveRedactionProfile(profile = "local") {
|
|
1247
|
+
switch (profile) {
|
|
1248
|
+
case "local":
|
|
1249
|
+
return { profile: "local", extraKeys: [] };
|
|
1250
|
+
case "share":
|
|
1251
|
+
return {
|
|
1252
|
+
profile: "share",
|
|
1253
|
+
extraKeys: SHARE_PROFILE_EXTRA_KEYS,
|
|
1254
|
+
maxMetadataValueLengthCap: 500,
|
|
1255
|
+
maxPreviewLengthCap: 200
|
|
1256
|
+
};
|
|
1257
|
+
case "strict":
|
|
1258
|
+
return {
|
|
1259
|
+
profile: "strict",
|
|
1260
|
+
extraKeys: [...SHARE_PROFILE_EXTRA_KEYS, ...STRICT_PROFILE_EXTRA_KEYS],
|
|
1261
|
+
maxMetadataValueLengthCap: 200,
|
|
1262
|
+
maxPreviewLengthCap: 80
|
|
1263
|
+
};
|
|
1264
|
+
default:
|
|
1265
|
+
return { profile: "local", extraKeys: [] };
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
function isPreviewKey(key) {
|
|
1269
|
+
return key.toLowerCase().includes("preview");
|
|
1270
|
+
}
|
|
1271
|
+
function applyProfileMetadataCaps(maxMetadataValueLength, maxPreviewLength, resolved) {
|
|
1272
|
+
let meta = maxMetadataValueLength;
|
|
1273
|
+
let preview = maxPreviewLength;
|
|
1274
|
+
if (resolved.maxMetadataValueLengthCap !== void 0) {
|
|
1275
|
+
meta = Math.min(meta, resolved.maxMetadataValueLengthCap);
|
|
1276
|
+
}
|
|
1277
|
+
if (resolved.maxPreviewLengthCap !== void 0) {
|
|
1278
|
+
preview = Math.min(preview, resolved.maxPreviewLengthCap);
|
|
1279
|
+
}
|
|
1280
|
+
return { maxMetadataValueLength: meta, maxPreviewLength: preview };
|
|
1281
|
+
}
|
|
1282
|
+
function truncateStringForProfile(value, key, maxMetadataValueLength, maxPreviewLength) {
|
|
1283
|
+
const max = isPreviewKey(key) ? maxPreviewLength : maxMetadataValueLength;
|
|
1284
|
+
if (max <= 0) return "\u2026";
|
|
1285
|
+
if (value.length <= max) return value;
|
|
1286
|
+
return `${value.slice(0, max)}\u2026`;
|
|
1287
|
+
}
|
|
1198
1288
|
function isRecord6(value) {
|
|
1199
1289
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1200
1290
|
}
|
|
@@ -1345,7 +1435,7 @@ var DEFAULT_MAX_EVENT_BYTES = 65536;
|
|
|
1345
1435
|
function isRecord7(value) {
|
|
1346
1436
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1347
1437
|
}
|
|
1348
|
-
function
|
|
1438
|
+
function isPreviewKey2(key) {
|
|
1349
1439
|
return key.toLowerCase().includes("preview");
|
|
1350
1440
|
}
|
|
1351
1441
|
function truncateString(value, maxLen) {
|
|
@@ -1362,11 +1452,28 @@ function resolveTraceSafetyOptions(options) {
|
|
|
1362
1452
|
{
|
|
1363
1453
|
redactEnabled = true;
|
|
1364
1454
|
}
|
|
1455
|
+
const profile = options?.redactionProfile ?? "local";
|
|
1456
|
+
const resolvedProfile = resolveRedactionProfile(profile);
|
|
1457
|
+
const userMaxMetadata = "undefined" === "number" && Number.isFinite(options.maxMetadataValueLength) && options.maxMetadataValueLength >= 0 ? Math.floor(options.maxMetadataValueLength) : void 0;
|
|
1458
|
+
const userMaxPreview = "undefined" === "number" && Number.isFinite(options.maxPreviewLength) && options.maxPreviewLength >= 0 ? Math.floor(options.maxPreviewLength) : void 0;
|
|
1459
|
+
let maxMetadataValueLength = userMaxMetadata ?? DEFAULT_MAX_METADATA_VALUE_LENGTH;
|
|
1460
|
+
let maxPreviewLength = userMaxPreview ?? DEFAULT_MAX_PREVIEW_LENGTH;
|
|
1461
|
+
if (redactEnabled && profile !== "local") {
|
|
1462
|
+
const capped = applyProfileMetadataCaps(
|
|
1463
|
+
maxMetadataValueLength,
|
|
1464
|
+
maxPreviewLength,
|
|
1465
|
+
resolvedProfile
|
|
1466
|
+
);
|
|
1467
|
+
maxMetadataValueLength = capped.maxMetadataValueLength;
|
|
1468
|
+
maxPreviewLength = capped.maxPreviewLength;
|
|
1469
|
+
}
|
|
1365
1470
|
return {
|
|
1366
1471
|
redactEnabled,
|
|
1367
1472
|
redactionRules,
|
|
1368
|
-
|
|
1369
|
-
|
|
1473
|
+
redactionProfile: profile,
|
|
1474
|
+
profileExtraKeys: redactEnabled ? resolvedProfile.extraKeys : [],
|
|
1475
|
+
maxMetadataValueLength,
|
|
1476
|
+
maxPreviewLength,
|
|
1370
1477
|
maxEventBytes: "undefined" === "number" && Number.isFinite(options.maxEventBytes) && options.maxEventBytes > 0 ? Math.floor(options.maxEventBytes) : DEFAULT_MAX_EVENT_BYTES
|
|
1371
1478
|
};
|
|
1372
1479
|
}
|
|
@@ -1374,7 +1481,7 @@ function boundMetadataValue(key, value, opts, seen, depth) {
|
|
|
1374
1481
|
if (depth > 32) return "[MaxDepth]";
|
|
1375
1482
|
if (value === null || typeof value !== "object") {
|
|
1376
1483
|
if (typeof value === "string") {
|
|
1377
|
-
const max =
|
|
1484
|
+
const max = isPreviewKey2(key) ? opts.maxPreviewLength : opts.maxMetadataValueLength;
|
|
1378
1485
|
return truncateString(value, max);
|
|
1379
1486
|
}
|
|
1380
1487
|
return value;
|
|
@@ -1400,7 +1507,10 @@ function boundMetadataValue(key, value, opts, seen, depth) {
|
|
|
1400
1507
|
}
|
|
1401
1508
|
function redactMetadata(metadata, opts) {
|
|
1402
1509
|
if (!opts.redactEnabled) return { ...metadata };
|
|
1403
|
-
const redactor = new Redactor({
|
|
1510
|
+
const redactor = new Redactor({
|
|
1511
|
+
rules: opts.redactionRules,
|
|
1512
|
+
extraKeys: opts.profileExtraKeys
|
|
1513
|
+
});
|
|
1404
1514
|
return redactor.redactRecord(metadata);
|
|
1405
1515
|
}
|
|
1406
1516
|
function prepareMetadataForDisk(metadata, opts) {
|
|
@@ -1886,6 +1996,574 @@ function filterTraces(traces, options) {
|
|
|
1886
1996
|
}
|
|
1887
1997
|
return out;
|
|
1888
1998
|
}
|
|
1999
|
+
|
|
2000
|
+
// packages/core/src/timeline.ts
|
|
2001
|
+
function finite(n) {
|
|
2002
|
+
return typeof n === "number" && Number.isFinite(n);
|
|
2003
|
+
}
|
|
2004
|
+
function pickStreamingMeta(metadata) {
|
|
2005
|
+
if (!metadata || typeof metadata !== "object") return void 0;
|
|
2006
|
+
const chunkCount = metadata.chunkCount;
|
|
2007
|
+
const streamDurationMs = metadata.streamDurationMs;
|
|
2008
|
+
const streamedCharCount = metadata.streamedCharCount;
|
|
2009
|
+
if (!finite(chunkCount) && !finite(streamDurationMs) && !finite(streamedCharCount)) {
|
|
2010
|
+
return void 0;
|
|
2011
|
+
}
|
|
2012
|
+
return {
|
|
2013
|
+
...finite(chunkCount) ? { chunkCount } : {},
|
|
2014
|
+
...finite(streamDurationMs) ? { streamDurationMs } : {},
|
|
2015
|
+
...finite(streamedCharCount) ? { streamedCharCount } : {}
|
|
2016
|
+
};
|
|
2017
|
+
}
|
|
2018
|
+
function pickCorrelation(metadata) {
|
|
2019
|
+
if (!metadata || typeof metadata !== "object") return void 0;
|
|
2020
|
+
const out = {};
|
|
2021
|
+
for (const key of [
|
|
2022
|
+
"correlationId",
|
|
2023
|
+
"requestId",
|
|
2024
|
+
"decisionId",
|
|
2025
|
+
"groupId"
|
|
2026
|
+
]) {
|
|
2027
|
+
const v = metadata[key];
|
|
2028
|
+
if (typeof v === "string" && v.trim() !== "") {
|
|
2029
|
+
out[key] = v;
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
2033
|
+
}
|
|
2034
|
+
function buildRunTimeline(events, options = {}) {
|
|
2035
|
+
const started = events.find(
|
|
2036
|
+
(e) => e.event === "run_started"
|
|
2037
|
+
);
|
|
2038
|
+
const completed = events.filter(
|
|
2039
|
+
(e) => e.event === "run_completed"
|
|
2040
|
+
);
|
|
2041
|
+
const lastCompleted = completed[completed.length - 1];
|
|
2042
|
+
const runId = started?.runId ?? events.find((e) => typeof e.runId === "string")?.runId ?? "unknown-run";
|
|
2043
|
+
const runStart = started && finite(started.startTime) ? started.startTime : started && finite(started.timestamp) ? started.timestamp : void 0;
|
|
2044
|
+
const status = lastCompleted ? lastCompleted.status : started ? "running" : "unknown";
|
|
2045
|
+
const steps = /* @__PURE__ */ new Map();
|
|
2046
|
+
for (const e of events) {
|
|
2047
|
+
if (e.event === "step_started") {
|
|
2048
|
+
const s = e;
|
|
2049
|
+
steps.set(s.stepId, {
|
|
2050
|
+
name: s.name,
|
|
2051
|
+
type: s.type,
|
|
2052
|
+
parentId: s.parentId,
|
|
2053
|
+
startedAt: finite(s.startTime) ? s.startTime : s.timestamp,
|
|
2054
|
+
status: "running",
|
|
2055
|
+
metadata: s.metadata
|
|
2056
|
+
});
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
for (const e of events) {
|
|
2060
|
+
if (e.event !== "step_completed") continue;
|
|
2061
|
+
const c = e;
|
|
2062
|
+
const node = steps.get(c.stepId);
|
|
2063
|
+
if (!node) continue;
|
|
2064
|
+
node.status = c.status;
|
|
2065
|
+
if (finite(c.durationMs)) node.durationMs = c.durationMs;
|
|
2066
|
+
}
|
|
2067
|
+
const depthCache = /* @__PURE__ */ new Map();
|
|
2068
|
+
const computeDepth = (stepId) => {
|
|
2069
|
+
const cached = depthCache.get(stepId);
|
|
2070
|
+
if (cached !== void 0) return cached;
|
|
2071
|
+
const node = steps.get(stepId);
|
|
2072
|
+
if (!node) return 0;
|
|
2073
|
+
const parent = node.parentId;
|
|
2074
|
+
if (typeof parent !== "string" || parent.trim() === "" || !steps.has(parent)) {
|
|
2075
|
+
depthCache.set(stepId, 0);
|
|
2076
|
+
return 0;
|
|
2077
|
+
}
|
|
2078
|
+
const d = Math.min(1e3, computeDepth(parent) + 1);
|
|
2079
|
+
depthCache.set(stepId, d);
|
|
2080
|
+
return d;
|
|
2081
|
+
};
|
|
2082
|
+
const entries = [];
|
|
2083
|
+
for (const [stepId, s] of steps.entries()) {
|
|
2084
|
+
const offsetMs = runStart !== void 0 && finite(s.startedAt) ? Math.max(0, s.startedAt - runStart) : 0;
|
|
2085
|
+
entries.push({
|
|
2086
|
+
stepId,
|
|
2087
|
+
name: s.name,
|
|
2088
|
+
type: s.type,
|
|
2089
|
+
status: s.status,
|
|
2090
|
+
depth: computeDepth(stepId),
|
|
2091
|
+
startedAt: s.startedAt,
|
|
2092
|
+
offsetMs,
|
|
2093
|
+
durationMs: s.durationMs,
|
|
2094
|
+
isError: s.status === "error",
|
|
2095
|
+
streaming: pickStreamingMeta(s.metadata)
|
|
2096
|
+
});
|
|
2097
|
+
}
|
|
2098
|
+
entries.sort((a, b) => a.startedAt - b.startedAt);
|
|
2099
|
+
const slowTopN = options.slowTopN ?? 3;
|
|
2100
|
+
if (options.focus === "slow" && entries.length > 0) {
|
|
2101
|
+
const ranked = [...entries].filter((e) => finite(e.durationMs)).sort((a, b) => (b.durationMs ?? 0) - (a.durationMs ?? 0));
|
|
2102
|
+
const slowIds = new Set(
|
|
2103
|
+
ranked.slice(0, slowTopN).map((e) => e.stepId)
|
|
2104
|
+
);
|
|
2105
|
+
for (const e of entries) {
|
|
2106
|
+
if (slowIds.has(e.stepId)) e.slow = true;
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
return {
|
|
2110
|
+
runId,
|
|
2111
|
+
name: typeof started?.name === "string" && started.name.trim() !== "" ? started.name : void 0,
|
|
2112
|
+
status,
|
|
2113
|
+
startedAt: runStart,
|
|
2114
|
+
endedAt: lastCompleted && finite(lastCompleted.endTime) ? lastCompleted.endTime : void 0,
|
|
2115
|
+
durationMs: lastCompleted && finite(lastCompleted.durationMs) ? lastCompleted.durationMs : void 0,
|
|
2116
|
+
correlation: pickCorrelation(
|
|
2117
|
+
started?.metadata
|
|
2118
|
+
),
|
|
2119
|
+
entries
|
|
2120
|
+
};
|
|
2121
|
+
}
|
|
2122
|
+
function renderTimeline(timeline, options = {}) {
|
|
2123
|
+
const lines = [];
|
|
2124
|
+
lines.push(`Timeline: ${timeline.name ?? timeline.runId}`);
|
|
2125
|
+
lines.push(`Run ID: ${timeline.runId}`);
|
|
2126
|
+
lines.push(`Status: ${timeline.status}`);
|
|
2127
|
+
if (timeline.startedAt !== void 0) {
|
|
2128
|
+
lines.push(`Started: ${formatTimestamp(timeline.startedAt)}`);
|
|
2129
|
+
}
|
|
2130
|
+
if (timeline.durationMs !== void 0) {
|
|
2131
|
+
lines.push(`Duration: ${formatDuration2(timeline.durationMs)}`);
|
|
2132
|
+
}
|
|
2133
|
+
if (timeline.correlation) {
|
|
2134
|
+
const parts = Object.entries(timeline.correlation).filter(([, v]) => typeof v === "string").map(([k, v]) => `${k}=${v}`);
|
|
2135
|
+
if (parts.length > 0) {
|
|
2136
|
+
lines.push(`Correlation: ${parts.join(", ")}`);
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
lines.push("");
|
|
2140
|
+
lines.push("Steps (chronological):");
|
|
2141
|
+
const show = timeline.entries.filter((e) => {
|
|
2142
|
+
if (options.focus === "slow") return e.slow === true;
|
|
2143
|
+
return true;
|
|
2144
|
+
});
|
|
2145
|
+
if (show.length === 0) {
|
|
2146
|
+
lines.push(
|
|
2147
|
+
options.focus === "slow" ? "(no steps with duration for slow focus)" : "(no steps)"
|
|
2148
|
+
);
|
|
2149
|
+
return lines.join("\n");
|
|
2150
|
+
}
|
|
2151
|
+
for (const e of show) {
|
|
2152
|
+
const prefix = e.slow ? "[slow] " : "";
|
|
2153
|
+
const typeTag = e.type === "llm" ? "llm" : e.type === "tool" ? "tool" : e.type;
|
|
2154
|
+
const dur = e.durationMs !== void 0 ? formatDuration2(e.durationMs) : "-";
|
|
2155
|
+
const err = e.isError ? " error" : "";
|
|
2156
|
+
const off = formatDuration2(e.offsetMs);
|
|
2157
|
+
let line = `${prefix}+${off} ${typeTag}:${e.name} (${dur})${err}`;
|
|
2158
|
+
if (e.streaming?.chunkCount !== void 0) {
|
|
2159
|
+
line += ` chunks=${e.streaming.chunkCount}`;
|
|
2160
|
+
}
|
|
2161
|
+
if (e.streaming?.streamDurationMs !== void 0) {
|
|
2162
|
+
line += ` stream=${formatDuration2(e.streaming.streamDurationMs)}`;
|
|
2163
|
+
}
|
|
2164
|
+
lines.push(line);
|
|
2165
|
+
}
|
|
2166
|
+
return lines.join("\n");
|
|
2167
|
+
}
|
|
2168
|
+
function percentile(sorted, p) {
|
|
2169
|
+
if (sorted.length === 0) return void 0;
|
|
2170
|
+
const idx = Math.min(
|
|
2171
|
+
sorted.length - 1,
|
|
2172
|
+
Math.max(0, Math.ceil(p / 100 * sorted.length) - 1)
|
|
2173
|
+
);
|
|
2174
|
+
return sorted[idx];
|
|
2175
|
+
}
|
|
2176
|
+
async function readRunStartedMetadata(filePath) {
|
|
2177
|
+
try {
|
|
2178
|
+
const raw = await readFile(filePath, "utf-8");
|
|
2179
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
2180
|
+
const trimmed = line.trim();
|
|
2181
|
+
if (trimmed === "") continue;
|
|
2182
|
+
let parsed;
|
|
2183
|
+
try {
|
|
2184
|
+
parsed = JSON.parse(trimmed);
|
|
2185
|
+
} catch {
|
|
2186
|
+
continue;
|
|
2187
|
+
}
|
|
2188
|
+
if (!isTraceEvent(parsed)) continue;
|
|
2189
|
+
if (parsed.event !== "run_started") continue;
|
|
2190
|
+
const rs = parsed;
|
|
2191
|
+
if (rs.metadata && typeof rs.metadata === "object") {
|
|
2192
|
+
return rs.metadata;
|
|
2193
|
+
}
|
|
2194
|
+
return void 0;
|
|
2195
|
+
}
|
|
2196
|
+
} catch {
|
|
2197
|
+
}
|
|
2198
|
+
return void 0;
|
|
2199
|
+
}
|
|
2200
|
+
function metaMatchesCorrelation(metadata, correlationId, groupId) {
|
|
2201
|
+
if (correlationId) {
|
|
2202
|
+
const v = metadata?.correlationId;
|
|
2203
|
+
if (typeof v !== "string" || v !== correlationId) return false;
|
|
2204
|
+
}
|
|
2205
|
+
if (groupId) {
|
|
2206
|
+
const v = metadata?.groupId;
|
|
2207
|
+
if (typeof v !== "string" || v !== groupId) return false;
|
|
2208
|
+
}
|
|
2209
|
+
return true;
|
|
2210
|
+
}
|
|
2211
|
+
async function buildTraceStats(metas, options) {
|
|
2212
|
+
let filtered = filterTraces(metas, { since: options.since });
|
|
2213
|
+
if (options.correlationId || options.groupId) {
|
|
2214
|
+
const next = [];
|
|
2215
|
+
for (const m of filtered) {
|
|
2216
|
+
const md = await readRunStartedMetadata(m.filePath);
|
|
2217
|
+
if (metaMatchesCorrelation(md, options.correlationId, options.groupId)) {
|
|
2218
|
+
next.push(m);
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
filtered = next;
|
|
2222
|
+
}
|
|
2223
|
+
let successCount = 0;
|
|
2224
|
+
let errorCount = 0;
|
|
2225
|
+
let runningCount = 0;
|
|
2226
|
+
let unknownCount = 0;
|
|
2227
|
+
const durations = [];
|
|
2228
|
+
let totalSteps = 0;
|
|
2229
|
+
let totalLlmSteps = 0;
|
|
2230
|
+
let totalToolSteps = 0;
|
|
2231
|
+
let totalErrorSteps = 0;
|
|
2232
|
+
const slowestRuns = [];
|
|
2233
|
+
const slowestSteps = [];
|
|
2234
|
+
for (const m of filtered) {
|
|
2235
|
+
if (m.status === "success") successCount += 1;
|
|
2236
|
+
else if (m.status === "error") errorCount += 1;
|
|
2237
|
+
else if (m.status === "running") runningCount += 1;
|
|
2238
|
+
else unknownCount += 1;
|
|
2239
|
+
if (typeof m.durationMs === "number" && Number.isFinite(m.durationMs) && m.durationMs >= 0) {
|
|
2240
|
+
durations.push(m.durationMs);
|
|
2241
|
+
slowestRuns.push({
|
|
2242
|
+
runId: m.runId,
|
|
2243
|
+
name: m.name,
|
|
2244
|
+
durationMs: m.durationMs,
|
|
2245
|
+
status: m.status
|
|
2246
|
+
});
|
|
2247
|
+
}
|
|
2248
|
+
try {
|
|
2249
|
+
const events = await readTraceEvents(m.runId, options.traceDir);
|
|
2250
|
+
if (events.length === 0) continue;
|
|
2251
|
+
const summary = buildRunSummary(events);
|
|
2252
|
+
totalSteps += summary.totalSteps;
|
|
2253
|
+
totalLlmSteps += summary.llmSteps;
|
|
2254
|
+
totalToolSteps += summary.toolSteps;
|
|
2255
|
+
totalErrorSteps += summary.errorSteps;
|
|
2256
|
+
const steps = collectCompletedSteps(events, m.runId);
|
|
2257
|
+
for (const s of steps) {
|
|
2258
|
+
slowestSteps.push(s);
|
|
2259
|
+
}
|
|
2260
|
+
} catch {
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
slowestRuns.sort((a, b) => (b.durationMs ?? 0) - (a.durationMs ?? 0));
|
|
2264
|
+
slowestSteps.sort((a, b) => b.durationMs - a.durationMs);
|
|
2265
|
+
const runLimit = options.slowRunLimit ?? 5;
|
|
2266
|
+
const stepLimit = options.slowStepLimit ?? 5;
|
|
2267
|
+
const sortedDur = [...durations].sort((a, b) => a - b);
|
|
2268
|
+
const totalRuns = filtered.length;
|
|
2269
|
+
const errorRate = totalRuns > 0 ? errorCount / totalRuns : 0;
|
|
2270
|
+
const sumDur = durations.reduce((a, b) => a + b, 0);
|
|
2271
|
+
return {
|
|
2272
|
+
traceDir: options.traceDir,
|
|
2273
|
+
...options.since ? { since: options.since } : {},
|
|
2274
|
+
...options.correlationId ? { correlationId: options.correlationId } : {},
|
|
2275
|
+
...options.groupId ? { groupId: options.groupId } : {},
|
|
2276
|
+
totalRuns,
|
|
2277
|
+
successCount,
|
|
2278
|
+
errorCount,
|
|
2279
|
+
runningCount,
|
|
2280
|
+
unknownCount,
|
|
2281
|
+
errorRate,
|
|
2282
|
+
duration: {
|
|
2283
|
+
...sortedDur.length > 0 ? {
|
|
2284
|
+
minMs: sortedDur[0],
|
|
2285
|
+
maxMs: sortedDur[sortedDur.length - 1],
|
|
2286
|
+
avgMs: sumDur / sortedDur.length,
|
|
2287
|
+
p50Ms: percentile(sortedDur, 50),
|
|
2288
|
+
p95Ms: percentile(sortedDur, 95)
|
|
2289
|
+
} : {}
|
|
2290
|
+
},
|
|
2291
|
+
totalSteps,
|
|
2292
|
+
avgStepsPerRun: totalRuns > 0 ? totalSteps / totalRuns : 0,
|
|
2293
|
+
totalLlmSteps,
|
|
2294
|
+
totalToolSteps,
|
|
2295
|
+
totalErrorSteps,
|
|
2296
|
+
slowestRuns: slowestRuns.slice(0, runLimit),
|
|
2297
|
+
slowestSteps: slowestSteps.slice(0, stepLimit)
|
|
2298
|
+
};
|
|
2299
|
+
}
|
|
2300
|
+
function collectCompletedSteps(events, runId) {
|
|
2301
|
+
const started = /* @__PURE__ */ new Map();
|
|
2302
|
+
const out = [];
|
|
2303
|
+
for (const e of events) {
|
|
2304
|
+
if (e.event === "step_started") {
|
|
2305
|
+
const s = e;
|
|
2306
|
+
started.set(s.stepId, { name: s.name, type: s.type });
|
|
2307
|
+
}
|
|
2308
|
+
if (e.event === "step_completed") {
|
|
2309
|
+
const c = e;
|
|
2310
|
+
if (c.status !== "success" && c.status !== "error") continue;
|
|
2311
|
+
if (typeof c.durationMs !== "number" || !Number.isFinite(c.durationMs)) {
|
|
2312
|
+
continue;
|
|
2313
|
+
}
|
|
2314
|
+
const meta = started.get(c.stepId);
|
|
2315
|
+
out.push({
|
|
2316
|
+
runId,
|
|
2317
|
+
stepName: meta?.name ?? c.stepId,
|
|
2318
|
+
stepType: meta?.type ?? "logic",
|
|
2319
|
+
durationMs: c.durationMs
|
|
2320
|
+
});
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
return out;
|
|
2324
|
+
}
|
|
2325
|
+
function renderTraceStats(stats) {
|
|
2326
|
+
const lines = [];
|
|
2327
|
+
lines.push("Trace stats (local)");
|
|
2328
|
+
lines.push(`Directory: ${stats.traceDir}`);
|
|
2329
|
+
if (stats.since) lines.push(`Since: ${stats.since}`);
|
|
2330
|
+
if (stats.correlationId) lines.push(`Correlation ID: ${stats.correlationId}`);
|
|
2331
|
+
if (stats.groupId) lines.push(`Group ID: ${stats.groupId}`);
|
|
2332
|
+
lines.push("");
|
|
2333
|
+
lines.push(`Runs: ${stats.totalRuns}`);
|
|
2334
|
+
lines.push(
|
|
2335
|
+
` success: ${stats.successCount} error: ${stats.errorCount} running: ${stats.runningCount} unknown: ${stats.unknownCount}`
|
|
2336
|
+
);
|
|
2337
|
+
lines.push(`Error rate: ${(stats.errorRate * 100).toFixed(1)}%`);
|
|
2338
|
+
if (stats.duration.avgMs !== void 0) {
|
|
2339
|
+
lines.push(
|
|
2340
|
+
`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)}`
|
|
2341
|
+
);
|
|
2342
|
+
}
|
|
2343
|
+
lines.push("");
|
|
2344
|
+
lines.push(`Steps: ${stats.totalSteps} (avg ${stats.avgStepsPerRun.toFixed(1)} per run)`);
|
|
2345
|
+
lines.push(
|
|
2346
|
+
` LLM: ${stats.totalLlmSteps} tool: ${stats.totalToolSteps} errors: ${stats.totalErrorSteps}`
|
|
2347
|
+
);
|
|
2348
|
+
if (stats.slowestRuns.length > 0) {
|
|
2349
|
+
lines.push("");
|
|
2350
|
+
lines.push("Slowest runs:");
|
|
2351
|
+
for (const r of stats.slowestRuns) {
|
|
2352
|
+
lines.push(
|
|
2353
|
+
` ${r.runId} | ${r.name ?? "-"} | ${formatDuration2(r.durationMs ?? 0)} | ${r.status}`
|
|
2354
|
+
);
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
if (stats.slowestSteps.length > 0) {
|
|
2358
|
+
lines.push("");
|
|
2359
|
+
lines.push("Slowest steps:");
|
|
2360
|
+
for (const s of stats.slowestSteps) {
|
|
2361
|
+
lines.push(
|
|
2362
|
+
` ${s.runId} | ${s.stepType}:${s.stepName} | ${formatDuration2(s.durationMs)}`
|
|
2363
|
+
);
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
return lines.join("\n");
|
|
2367
|
+
}
|
|
2368
|
+
|
|
2369
|
+
// packages/core/src/search.ts
|
|
2370
|
+
function parseDurationFilter(expr) {
|
|
2371
|
+
const raw = expr.trim();
|
|
2372
|
+
const m = raw.match(/^(>=|<=|>|<)\s*(.+)$/);
|
|
2373
|
+
if (!m) {
|
|
2374
|
+
throw new Error(
|
|
2375
|
+
`Invalid --duration "${expr}". Use forms like >5s, >=500ms, <2m.`
|
|
2376
|
+
);
|
|
2377
|
+
}
|
|
2378
|
+
const op = m[1];
|
|
2379
|
+
const ms = parseDuration(m[2].trim());
|
|
2380
|
+
return { op, ms };
|
|
2381
|
+
}
|
|
2382
|
+
function durationMatches(valueMs, filter) {
|
|
2383
|
+
if (valueMs === void 0 || !Number.isFinite(valueMs)) return false;
|
|
2384
|
+
switch (filter.op) {
|
|
2385
|
+
case ">":
|
|
2386
|
+
return valueMs > filter.ms;
|
|
2387
|
+
case ">=":
|
|
2388
|
+
return valueMs >= filter.ms;
|
|
2389
|
+
case "<":
|
|
2390
|
+
return valueMs < filter.ms;
|
|
2391
|
+
case "<=":
|
|
2392
|
+
return valueMs <= filter.ms;
|
|
2393
|
+
default:
|
|
2394
|
+
return false;
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
function normalizeStepTypeFilter(kind, type) {
|
|
2398
|
+
const v = (kind ?? type)?.trim().toLowerCase();
|
|
2399
|
+
return v && v !== "" ? v : void 0;
|
|
2400
|
+
}
|
|
2401
|
+
function nameMatches(hay, needle) {
|
|
2402
|
+
return hay.toLowerCase().includes(needle.toLowerCase());
|
|
2403
|
+
}
|
|
2404
|
+
async function searchTraces(metas, options) {
|
|
2405
|
+
let filtered = filterTraces(metas, { since: options.since });
|
|
2406
|
+
const stepTypeFilter = normalizeStepTypeFilter(options.kind, options.type);
|
|
2407
|
+
const nameQuery = options.name?.trim();
|
|
2408
|
+
const toolQuery = options.tool?.trim();
|
|
2409
|
+
let durationFilter;
|
|
2410
|
+
if (options.duration) {
|
|
2411
|
+
durationFilter = parseDurationFilter(options.duration);
|
|
2412
|
+
}
|
|
2413
|
+
const limit = options.limit ?? 50;
|
|
2414
|
+
const hasContentFilter = Boolean(
|
|
2415
|
+
options.status || stepTypeFilter || nameQuery || toolQuery || durationFilter
|
|
2416
|
+
);
|
|
2417
|
+
const results = [];
|
|
2418
|
+
if (!hasContentFilter) {
|
|
2419
|
+
for (const m of filtered) {
|
|
2420
|
+
results.push({
|
|
2421
|
+
runId: m.runId,
|
|
2422
|
+
runName: m.name,
|
|
2423
|
+
runStatus: m.status,
|
|
2424
|
+
timestamp: m.startedAt,
|
|
2425
|
+
durationMs: m.durationMs,
|
|
2426
|
+
matchReason: "trace in directory",
|
|
2427
|
+
matchedFields: ["run"],
|
|
2428
|
+
filePath: m.filePath
|
|
2429
|
+
});
|
|
2430
|
+
}
|
|
2431
|
+
return results.slice(0, limit);
|
|
2432
|
+
}
|
|
2433
|
+
for (const m of filtered) {
|
|
2434
|
+
if (options.status && m.status !== options.status) continue;
|
|
2435
|
+
let events = [];
|
|
2436
|
+
try {
|
|
2437
|
+
events = await readTraceEvents(m.runId, options.traceDir);
|
|
2438
|
+
} catch {
|
|
2439
|
+
continue;
|
|
2440
|
+
}
|
|
2441
|
+
if (events.length === 0) continue;
|
|
2442
|
+
const runMatches = matchRunLevel(m, {
|
|
2443
|
+
stepTypeFilter,
|
|
2444
|
+
nameQuery,
|
|
2445
|
+
toolQuery,
|
|
2446
|
+
durationFilter,
|
|
2447
|
+
statusFilter: options.status
|
|
2448
|
+
});
|
|
2449
|
+
results.push(...runMatches);
|
|
2450
|
+
const stepMatches = matchStepLevel(m, events, {
|
|
2451
|
+
stepTypeFilter,
|
|
2452
|
+
nameQuery,
|
|
2453
|
+
toolQuery,
|
|
2454
|
+
durationFilter,
|
|
2455
|
+
statusFilter: options.status
|
|
2456
|
+
});
|
|
2457
|
+
results.push(...stepMatches);
|
|
2458
|
+
}
|
|
2459
|
+
results.sort((a, b) => {
|
|
2460
|
+
const ta = a.timestamp ?? 0;
|
|
2461
|
+
const tb = b.timestamp ?? 0;
|
|
2462
|
+
if (ta !== tb) return ta - tb;
|
|
2463
|
+
const runCmp = a.runId.localeCompare(b.runId);
|
|
2464
|
+
if (runCmp !== 0) return runCmp;
|
|
2465
|
+
return (a.stepName ?? "").localeCompare(b.stepName ?? "");
|
|
2466
|
+
});
|
|
2467
|
+
return results.slice(0, limit);
|
|
2468
|
+
}
|
|
2469
|
+
function matchRunLevel(m, opts) {
|
|
2470
|
+
if (opts.stepTypeFilter || opts.toolQuery) return [];
|
|
2471
|
+
const out = [];
|
|
2472
|
+
const fields = [];
|
|
2473
|
+
if (opts.statusFilter && m.status === opts.statusFilter) {
|
|
2474
|
+
fields.push("run.status");
|
|
2475
|
+
}
|
|
2476
|
+
if (opts.nameQuery && nameMatches(m.name ?? m.runId, opts.nameQuery)) {
|
|
2477
|
+
fields.push("run.name");
|
|
2478
|
+
}
|
|
2479
|
+
if (opts.durationFilter && durationMatches(m.durationMs, opts.durationFilter)) {
|
|
2480
|
+
fields.push("run.durationMs");
|
|
2481
|
+
}
|
|
2482
|
+
if (fields.length === 0) return out;
|
|
2483
|
+
out.push({
|
|
2484
|
+
runId: m.runId,
|
|
2485
|
+
runName: m.name,
|
|
2486
|
+
runStatus: m.status,
|
|
2487
|
+
timestamp: m.startedAt,
|
|
2488
|
+
durationMs: m.durationMs,
|
|
2489
|
+
matchReason: `run match: ${fields.join(", ")}`,
|
|
2490
|
+
matchedFields: fields,
|
|
2491
|
+
filePath: m.filePath
|
|
2492
|
+
});
|
|
2493
|
+
return out;
|
|
2494
|
+
}
|
|
2495
|
+
function matchStepLevel(m, events, opts) {
|
|
2496
|
+
const out = [];
|
|
2497
|
+
const started = /* @__PURE__ */ new Map();
|
|
2498
|
+
for (const e of events) {
|
|
2499
|
+
if (e.event === "step_started") {
|
|
2500
|
+
started.set(e.stepId, e);
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
for (const e of events) {
|
|
2504
|
+
if (e.event !== "step_completed") continue;
|
|
2505
|
+
const c = e;
|
|
2506
|
+
const s = started.get(c.stepId);
|
|
2507
|
+
if (!s) continue;
|
|
2508
|
+
const fields = [];
|
|
2509
|
+
const stepType = s.type;
|
|
2510
|
+
if (opts.stepTypeFilter && stepType !== opts.stepTypeFilter) {
|
|
2511
|
+
continue;
|
|
2512
|
+
}
|
|
2513
|
+
const hasStepFilters = opts.stepTypeFilter || opts.nameQuery || opts.toolQuery || opts.durationFilter || opts.statusFilter === "error" || opts.statusFilter === "success";
|
|
2514
|
+
if (!hasStepFilters) continue;
|
|
2515
|
+
if (opts.statusFilter === "error" && c.status === "error") {
|
|
2516
|
+
fields.push("step.status");
|
|
2517
|
+
} else if (opts.statusFilter === "success" && c.status === "success") {
|
|
2518
|
+
fields.push("step.status");
|
|
2519
|
+
} else if (opts.statusFilter === "error" || opts.statusFilter === "success") {
|
|
2520
|
+
continue;
|
|
2521
|
+
}
|
|
2522
|
+
if (opts.nameQuery) {
|
|
2523
|
+
if (!nameMatches(s.name, opts.nameQuery)) continue;
|
|
2524
|
+
fields.push("step.name");
|
|
2525
|
+
}
|
|
2526
|
+
if (opts.toolQuery) {
|
|
2527
|
+
const toolName = typeof s.metadata?.toolName === "string" ? s.metadata.toolName : s.name;
|
|
2528
|
+
if (!nameMatches(toolName, opts.toolQuery)) continue;
|
|
2529
|
+
fields.push("step.tool");
|
|
2530
|
+
}
|
|
2531
|
+
if (opts.durationFilter) {
|
|
2532
|
+
if (!durationMatches(c.durationMs, opts.durationFilter)) continue;
|
|
2533
|
+
fields.push("step.durationMs");
|
|
2534
|
+
}
|
|
2535
|
+
if (opts.stepTypeFilter) {
|
|
2536
|
+
fields.push("step.type");
|
|
2537
|
+
}
|
|
2538
|
+
if (fields.length === 0) continue;
|
|
2539
|
+
out.push({
|
|
2540
|
+
runId: m.runId,
|
|
2541
|
+
runName: m.name,
|
|
2542
|
+
runStatus: m.status,
|
|
2543
|
+
stepId: c.stepId,
|
|
2544
|
+
stepName: s.name,
|
|
2545
|
+
stepType,
|
|
2546
|
+
timestamp: s.startTime ?? s.timestamp,
|
|
2547
|
+
durationMs: c.durationMs,
|
|
2548
|
+
matchReason: `step match: ${fields.join(", ")}`,
|
|
2549
|
+
matchedFields: fields,
|
|
2550
|
+
filePath: m.filePath
|
|
2551
|
+
});
|
|
2552
|
+
}
|
|
2553
|
+
return out;
|
|
2554
|
+
}
|
|
2555
|
+
async function loadTraceMetadataList(_traceDir, fileNames, getPath) {
|
|
2556
|
+
const metas = [];
|
|
2557
|
+
for (const fileName of fileNames) {
|
|
2558
|
+
try {
|
|
2559
|
+
const filePath = getPath(fileName);
|
|
2560
|
+
const meta = await extractMetadata(filePath);
|
|
2561
|
+
metas.push(meta);
|
|
2562
|
+
} catch {
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
return metas;
|
|
2566
|
+
}
|
|
1889
2567
|
var KNOWN_EVENTS = /* @__PURE__ */ new Set([
|
|
1890
2568
|
"run_started",
|
|
1891
2569
|
"run_completed",
|
|
@@ -3207,6 +3885,134 @@ Object.assign(stepImpl, {
|
|
|
3207
3885
|
// packages/core/src/exporters/types.ts
|
|
3208
3886
|
var EXPORT_PAYLOAD_VERSION = "0.1.2";
|
|
3209
3887
|
|
|
3888
|
+
// packages/core/src/exporters/redact-export.ts
|
|
3889
|
+
function isRecord9(value) {
|
|
3890
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3891
|
+
}
|
|
3892
|
+
function deepClone(value) {
|
|
3893
|
+
if (value === null || typeof value !== "object") {
|
|
3894
|
+
return value;
|
|
3895
|
+
}
|
|
3896
|
+
if (Array.isArray(value)) {
|
|
3897
|
+
return value.map((item) => deepClone(item));
|
|
3898
|
+
}
|
|
3899
|
+
const out = {};
|
|
3900
|
+
for (const [k, v] of Object.entries(value)) {
|
|
3901
|
+
out[k] = deepClone(v);
|
|
3902
|
+
}
|
|
3903
|
+
return out;
|
|
3904
|
+
}
|
|
3905
|
+
function boundAttributeValues(record, maxMetadataValueLength, maxPreviewLength, seen, depth) {
|
|
3906
|
+
if (depth > 32) {
|
|
3907
|
+
return { truncated: true, reason: "maxDepth" };
|
|
3908
|
+
}
|
|
3909
|
+
const out = {};
|
|
3910
|
+
for (const [key, value] of Object.entries(record)) {
|
|
3911
|
+
out[key] = boundValue(value, key, maxMetadataValueLength, maxPreviewLength, seen, depth);
|
|
3912
|
+
}
|
|
3913
|
+
return out;
|
|
3914
|
+
}
|
|
3915
|
+
function boundValue(value, key, maxMetadataValueLength, maxPreviewLength, seen, depth) {
|
|
3916
|
+
if (value === null || typeof value !== "object") {
|
|
3917
|
+
if (typeof value === "string") {
|
|
3918
|
+
return truncateStringForProfile(
|
|
3919
|
+
value,
|
|
3920
|
+
key,
|
|
3921
|
+
maxMetadataValueLength,
|
|
3922
|
+
maxPreviewLength
|
|
3923
|
+
);
|
|
3924
|
+
}
|
|
3925
|
+
return value;
|
|
3926
|
+
}
|
|
3927
|
+
if (seen.has(value)) return "[Circular]";
|
|
3928
|
+
seen.add(value);
|
|
3929
|
+
if (Array.isArray(value)) {
|
|
3930
|
+
return value.slice(0, 50).map(
|
|
3931
|
+
(item, index) => boundValue(
|
|
3932
|
+
item,
|
|
3933
|
+
String(index),
|
|
3934
|
+
maxMetadataValueLength,
|
|
3935
|
+
maxPreviewLength,
|
|
3936
|
+
seen,
|
|
3937
|
+
depth + 1
|
|
3938
|
+
)
|
|
3939
|
+
);
|
|
3940
|
+
}
|
|
3941
|
+
return boundAttributeValues(
|
|
3942
|
+
value,
|
|
3943
|
+
maxMetadataValueLength,
|
|
3944
|
+
maxPreviewLength,
|
|
3945
|
+
seen,
|
|
3946
|
+
depth + 1
|
|
3947
|
+
);
|
|
3948
|
+
}
|
|
3949
|
+
function redactEventAttributes(attrs, redactor, maxMetadataValueLength, maxPreviewLength) {
|
|
3950
|
+
if (!attrs || Object.keys(attrs).length === 0) {
|
|
3951
|
+
return attrs;
|
|
3952
|
+
}
|
|
3953
|
+
const redacted = redactor.redactRecord(attrs);
|
|
3954
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
3955
|
+
const bounded = boundAttributeValues(
|
|
3956
|
+
redacted,
|
|
3957
|
+
maxMetadataValueLength,
|
|
3958
|
+
maxPreviewLength,
|
|
3959
|
+
seen,
|
|
3960
|
+
0
|
|
3961
|
+
);
|
|
3962
|
+
const err = bounded.error;
|
|
3963
|
+
if (isRecord9(err) && typeof err.message === "string") {
|
|
3964
|
+
bounded.error = {
|
|
3965
|
+
...err,
|
|
3966
|
+
message: truncateStringForProfile(
|
|
3967
|
+
err.message,
|
|
3968
|
+
"message",
|
|
3969
|
+
maxMetadataValueLength,
|
|
3970
|
+
maxPreviewLength
|
|
3971
|
+
),
|
|
3972
|
+
...typeof err.stack === "string" ? {
|
|
3973
|
+
stack: truncateStringForProfile(
|
|
3974
|
+
err.stack,
|
|
3975
|
+
"stack",
|
|
3976
|
+
maxMetadataValueLength,
|
|
3977
|
+
maxPreviewLength
|
|
3978
|
+
)
|
|
3979
|
+
} : {}
|
|
3980
|
+
};
|
|
3981
|
+
}
|
|
3982
|
+
return bounded;
|
|
3983
|
+
}
|
|
3984
|
+
function redactRunTreeForExport(tree, options) {
|
|
3985
|
+
const profile = options?.redactionProfile ?? "local";
|
|
3986
|
+
if (profile === "local") {
|
|
3987
|
+
return deepClone(tree);
|
|
3988
|
+
}
|
|
3989
|
+
const resolved = resolveRedactionProfile(profile);
|
|
3990
|
+
const { maxMetadataValueLength, maxPreviewLength } = applyProfileMetadataCaps(
|
|
3991
|
+
2e3,
|
|
3992
|
+
500,
|
|
3993
|
+
resolved
|
|
3994
|
+
);
|
|
3995
|
+
const redactor = new Redactor({ extraKeys: resolved.extraKeys });
|
|
3996
|
+
const clone = deepClone(tree);
|
|
3997
|
+
function walk(nodes) {
|
|
3998
|
+
for (const node of nodes) {
|
|
3999
|
+
if (node.event.attributes !== void 0) {
|
|
4000
|
+
node.event.attributes = redactEventAttributes(
|
|
4001
|
+
node.event.attributes,
|
|
4002
|
+
redactor,
|
|
4003
|
+
maxMetadataValueLength,
|
|
4004
|
+
maxPreviewLength
|
|
4005
|
+
);
|
|
4006
|
+
}
|
|
4007
|
+
if (node.children.length > 0) {
|
|
4008
|
+
walk(node.children);
|
|
4009
|
+
}
|
|
4010
|
+
}
|
|
4011
|
+
}
|
|
4012
|
+
walk(clone.children);
|
|
4013
|
+
return clone;
|
|
4014
|
+
}
|
|
4015
|
+
|
|
3210
4016
|
// packages/core/src/exporters/html-exporter.ts
|
|
3211
4017
|
function renderTreeHtml(nodes, ulClass = "tree") {
|
|
3212
4018
|
if (nodes.length === 0) return "";
|
|
@@ -3941,20 +4747,22 @@ function mergeExportDefaults(options) {
|
|
|
3941
4747
|
includeErrors: options.includeErrors ?? true,
|
|
3942
4748
|
pretty: options.pretty,
|
|
3943
4749
|
redacted: options.redacted,
|
|
3944
|
-
maxAttributeLength: options.maxAttributeLength
|
|
4750
|
+
maxAttributeLength: options.maxAttributeLength,
|
|
4751
|
+
redactionProfile: options.redactionProfile ?? "local"
|
|
3945
4752
|
};
|
|
3946
4753
|
}
|
|
3947
4754
|
function exportRunTree(tree, options) {
|
|
3948
4755
|
const opts = mergeExportDefaults(options);
|
|
4756
|
+
const exportTree = opts.redactionProfile === "local" ? tree : redactRunTreeForExport(tree, { redactionProfile: opts.redactionProfile });
|
|
3949
4757
|
switch (opts.format) {
|
|
3950
4758
|
case "markdown":
|
|
3951
|
-
return exportMarkdown(
|
|
4759
|
+
return exportMarkdown(exportTree, opts);
|
|
3952
4760
|
case "html":
|
|
3953
|
-
return exportHtml(
|
|
4761
|
+
return exportHtml(exportTree, opts);
|
|
3954
4762
|
case "openinference":
|
|
3955
|
-
return exportOpenInference(
|
|
4763
|
+
return exportOpenInference(exportTree, opts);
|
|
3956
4764
|
case "otlp-json":
|
|
3957
|
-
return exportOtlpJson(
|
|
4765
|
+
return exportOtlpJson(exportTree, opts);
|
|
3958
4766
|
default: {
|
|
3959
4767
|
const _x = opts.format;
|
|
3960
4768
|
throw new Error(`Unsupported export format: ${String(_x)}`);
|
|
@@ -4790,6 +5598,15 @@ async function tail(options = {}) {
|
|
|
4790
5598
|
process.exitCode = 1;
|
|
4791
5599
|
}
|
|
4792
5600
|
}
|
|
5601
|
+
function parseRedactionProfile(s) {
|
|
5602
|
+
const v = (s ?? "local").trim().toLowerCase();
|
|
5603
|
+
if (v === "local" || v === "share" || v === "strict") {
|
|
5604
|
+
return v;
|
|
5605
|
+
}
|
|
5606
|
+
throw new Error(
|
|
5607
|
+
`Unsupported --redaction-profile "${s ?? ""}". Use local, share, or strict.`
|
|
5608
|
+
);
|
|
5609
|
+
}
|
|
4793
5610
|
function parseExportFormat(s) {
|
|
4794
5611
|
const v = (s ?? "markdown").trim().toLowerCase();
|
|
4795
5612
|
if (v === "markdown" || v === "html" || v === "openinference" || v === "otlp-json") {
|
|
@@ -4807,8 +5624,10 @@ async function exportCommand(runId, options = {}) {
|
|
|
4807
5624
|
return;
|
|
4808
5625
|
}
|
|
4809
5626
|
let format;
|
|
5627
|
+
let redactionProfile;
|
|
4810
5628
|
try {
|
|
4811
5629
|
format = parseExportFormat(options.format);
|
|
5630
|
+
redactionProfile = parseRedactionProfile(options.redactionProfile);
|
|
4812
5631
|
} catch (e) {
|
|
4813
5632
|
const msg = e instanceof Error ? e.message : String(e);
|
|
4814
5633
|
console.error(msg);
|
|
@@ -4847,7 +5666,8 @@ Trace directory: ${traceDir}`);
|
|
|
4847
5666
|
includeErrors: options.noErrors === true ? false : true,
|
|
4848
5667
|
pretty: true,
|
|
4849
5668
|
redacted: true,
|
|
4850
|
-
maxAttributeLength: 500
|
|
5669
|
+
maxAttributeLength: 500,
|
|
5670
|
+
redactionProfile
|
|
4851
5671
|
};
|
|
4852
5672
|
const result = exportRunTree(tree, exportOpts);
|
|
4853
5673
|
const validation = options.validate === true ? validateExport(result) : void 0;
|
|
@@ -4996,6 +5816,163 @@ Trace directory: ${traceDir}`
|
|
|
4996
5816
|
);
|
|
4997
5817
|
}
|
|
4998
5818
|
|
|
5819
|
+
// packages/cli/src/timeline.ts
|
|
5820
|
+
async function timelineCommand(runId, options = {}) {
|
|
5821
|
+
const id = typeof runId === "string" && runId.trim() !== "" ? runId.trim() : "";
|
|
5822
|
+
if (id === "") {
|
|
5823
|
+
console.error("Run id is required");
|
|
5824
|
+
process.exitCode = 1;
|
|
5825
|
+
return;
|
|
5826
|
+
}
|
|
5827
|
+
const traceDir = resolveTraceDir({ dir: options.dir });
|
|
5828
|
+
let events;
|
|
5829
|
+
try {
|
|
5830
|
+
events = await readTraceEvents(id, traceDir);
|
|
5831
|
+
} catch (e) {
|
|
5832
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
5833
|
+
console.error(`[AgentInspect] timeline failed: ${msg}`);
|
|
5834
|
+
process.exitCode = 1;
|
|
5835
|
+
return;
|
|
5836
|
+
}
|
|
5837
|
+
if (events.length === 0) {
|
|
5838
|
+
console.log(`Run not found: ${id}`);
|
|
5839
|
+
console.log(`Trace directory: ${traceDir}`);
|
|
5840
|
+
process.exitCode = 1;
|
|
5841
|
+
return;
|
|
5842
|
+
}
|
|
5843
|
+
const focus = options.focus?.trim().toLowerCase() === "slow" ? "slow" : "all";
|
|
5844
|
+
const timeline = buildRunTimeline(events, {
|
|
5845
|
+
focus: focus === "slow" ? "slow" : "all"
|
|
5846
|
+
});
|
|
5847
|
+
if (options.json) {
|
|
5848
|
+
console.log(JSON.stringify(timeline, null, 2));
|
|
5849
|
+
return;
|
|
5850
|
+
}
|
|
5851
|
+
console.log(renderTimeline(timeline, { focus }));
|
|
5852
|
+
}
|
|
5853
|
+
|
|
5854
|
+
// packages/cli/src/stats.ts
|
|
5855
|
+
async function statsCommand(options = {}) {
|
|
5856
|
+
try {
|
|
5857
|
+
const traceDir = resolveTraceDir({ dir: options.dir });
|
|
5858
|
+
const td = new TraceDirectory({ dir: traceDir });
|
|
5859
|
+
if (typeof options.since === "string" && options.since.trim() !== "") {
|
|
5860
|
+
parseDuration(options.since.trim());
|
|
5861
|
+
}
|
|
5862
|
+
const files = await td.list();
|
|
5863
|
+
if (files.length === 0) {
|
|
5864
|
+
if (options.json) {
|
|
5865
|
+
console.log(
|
|
5866
|
+
JSON.stringify({
|
|
5867
|
+
traceDir,
|
|
5868
|
+
totalRuns: 0,
|
|
5869
|
+
successCount: 0,
|
|
5870
|
+
errorCount: 0,
|
|
5871
|
+
runningCount: 0,
|
|
5872
|
+
unknownCount: 0,
|
|
5873
|
+
errorRate: 0,
|
|
5874
|
+
duration: {},
|
|
5875
|
+
totalSteps: 0,
|
|
5876
|
+
avgStepsPerRun: 0,
|
|
5877
|
+
totalLlmSteps: 0,
|
|
5878
|
+
totalToolSteps: 0,
|
|
5879
|
+
totalErrorSteps: 0,
|
|
5880
|
+
slowestRuns: [],
|
|
5881
|
+
slowestSteps: []
|
|
5882
|
+
})
|
|
5883
|
+
);
|
|
5884
|
+
} else {
|
|
5885
|
+
console.log("No AgentInspect runs found");
|
|
5886
|
+
console.log(`Trace directory: ${traceDir}`);
|
|
5887
|
+
}
|
|
5888
|
+
return;
|
|
5889
|
+
}
|
|
5890
|
+
const metas = [];
|
|
5891
|
+
for (const fileName of files) {
|
|
5892
|
+
try {
|
|
5893
|
+
metas.push(await extractMetadata(td.getPath(fileName)));
|
|
5894
|
+
} catch {
|
|
5895
|
+
}
|
|
5896
|
+
}
|
|
5897
|
+
const stats = await buildTraceStats(metas, {
|
|
5898
|
+
traceDir,
|
|
5899
|
+
since: options.since,
|
|
5900
|
+
correlationId: options.correlationId,
|
|
5901
|
+
groupId: options.groupId
|
|
5902
|
+
});
|
|
5903
|
+
if (options.json) {
|
|
5904
|
+
console.log(JSON.stringify(stats, null, 2));
|
|
5905
|
+
return;
|
|
5906
|
+
}
|
|
5907
|
+
console.log(renderTraceStats(stats));
|
|
5908
|
+
} catch (e) {
|
|
5909
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
5910
|
+
console.error(`[AgentInspect] stats failed: ${msg}`);
|
|
5911
|
+
process.exitCode = 1;
|
|
5912
|
+
}
|
|
5913
|
+
}
|
|
5914
|
+
|
|
5915
|
+
// packages/cli/src/search.ts
|
|
5916
|
+
function parseLimit2(raw) {
|
|
5917
|
+
if (raw === void 0 || raw.trim() === "") return 50;
|
|
5918
|
+
const n = Number.parseInt(raw, 10);
|
|
5919
|
+
if (!Number.isFinite(n) || n <= 0) return 50;
|
|
5920
|
+
return Math.min(n, 500);
|
|
5921
|
+
}
|
|
5922
|
+
async function searchCommand(options = {}) {
|
|
5923
|
+
try {
|
|
5924
|
+
const traceDir = resolveTraceDir({ dir: options.dir });
|
|
5925
|
+
const td = new TraceDirectory({ dir: traceDir });
|
|
5926
|
+
if (typeof options.since === "string" && options.since.trim() !== "") {
|
|
5927
|
+
parseDuration(options.since.trim());
|
|
5928
|
+
}
|
|
5929
|
+
if (options.duration) {
|
|
5930
|
+
parseDurationFilter(options.duration);
|
|
5931
|
+
}
|
|
5932
|
+
const files = await td.list();
|
|
5933
|
+
const metas = await loadTraceMetadataList(
|
|
5934
|
+
traceDir,
|
|
5935
|
+
files,
|
|
5936
|
+
(f) => td.getPath(f)
|
|
5937
|
+
);
|
|
5938
|
+
const status = options.status === "success" || options.status === "error" || options.status === "running" || options.status === "unknown" ? options.status : void 0;
|
|
5939
|
+
const results = await searchTraces(metas, {
|
|
5940
|
+
traceDir,
|
|
5941
|
+
since: options.since,
|
|
5942
|
+
status,
|
|
5943
|
+
kind: options.kind,
|
|
5944
|
+
type: options.type,
|
|
5945
|
+
name: options.name,
|
|
5946
|
+
tool: options.tool,
|
|
5947
|
+
duration: options.duration,
|
|
5948
|
+
limit: parseLimit2(options.limit)
|
|
5949
|
+
});
|
|
5950
|
+
if (options.json) {
|
|
5951
|
+
console.log(JSON.stringify(results, null, 2));
|
|
5952
|
+
return;
|
|
5953
|
+
}
|
|
5954
|
+
if (results.length === 0) {
|
|
5955
|
+
console.log("No matching traces found");
|
|
5956
|
+
console.log(`Trace directory: ${traceDir}`);
|
|
5957
|
+
return;
|
|
5958
|
+
}
|
|
5959
|
+
console.log(`Search results (${results.length})`);
|
|
5960
|
+
for (const r of results) {
|
|
5961
|
+
const step2 = r.stepName !== void 0 ? ` | ${r.stepType ?? "step"}:${r.stepName}` : "";
|
|
5962
|
+
const dur = r.durationMs !== void 0 ? ` | ${r.durationMs}ms` : "";
|
|
5963
|
+
console.log(
|
|
5964
|
+
`${r.runId}${step2} | ${r.runStatus}${dur} | ${r.matchReason}`
|
|
5965
|
+
);
|
|
5966
|
+
}
|
|
5967
|
+
console.log("");
|
|
5968
|
+
console.log(`Trace directory: ${traceDir}`);
|
|
5969
|
+
} catch (e) {
|
|
5970
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
5971
|
+
console.error(`[AgentInspect] search failed: ${msg}`);
|
|
5972
|
+
process.exitCode = 1;
|
|
5973
|
+
}
|
|
5974
|
+
}
|
|
5975
|
+
|
|
4999
5976
|
// packages/cli/src/index.ts
|
|
5000
5977
|
function runCommand(action) {
|
|
5001
5978
|
void action().catch((error) => {
|
|
@@ -5080,7 +6057,12 @@ function createCliProgram() {
|
|
|
5080
6057
|
"openinference",
|
|
5081
6058
|
"otlp-json"
|
|
5082
6059
|
])
|
|
5083
|
-
).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").
|
|
6060
|
+
).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(
|
|
6061
|
+
new Option(
|
|
6062
|
+
"--redaction-profile <profile>",
|
|
6063
|
+
"redaction profile for exported copies: local, share, strict (default: local)"
|
|
6064
|
+
).choices(["local", "share", "strict"])
|
|
6065
|
+
).action((runId, opts) => {
|
|
5084
6066
|
runCommand(() => exportCommand(runId, opts));
|
|
5085
6067
|
});
|
|
5086
6068
|
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(
|
|
@@ -5104,6 +6086,29 @@ function createCliProgram() {
|
|
|
5104
6086
|
).option("--verbose", "show more left/right detail").action((leftRunId, rightRunId, opts) => {
|
|
5105
6087
|
runCommand(() => diffCommand(leftRunId, rightRunId, opts));
|
|
5106
6088
|
});
|
|
6089
|
+
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(
|
|
6090
|
+
new Option("--focus <mode>", "highlight slowest steps by duration").choices([
|
|
6091
|
+
"slow"
|
|
6092
|
+
])
|
|
6093
|
+
).action((runId, opts) => {
|
|
6094
|
+
runCommand(() => timelineCommand(runId, opts));
|
|
6095
|
+
});
|
|
6096
|
+
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) => {
|
|
6097
|
+
runCommand(() => statsCommand(opts));
|
|
6098
|
+
});
|
|
6099
|
+
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(
|
|
6100
|
+
new Option("--status <status>", "filter by run or step status").choices([
|
|
6101
|
+
"success",
|
|
6102
|
+
"error",
|
|
6103
|
+
"running",
|
|
6104
|
+
"unknown"
|
|
6105
|
+
])
|
|
6106
|
+
).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(
|
|
6107
|
+
"--duration <expr>",
|
|
6108
|
+
"duration filter on run or step (e.g. >5s, >=500ms)"
|
|
6109
|
+
).option("--limit <number>", "max results (default 50)").option("--json", "print results as JSON").action((opts) => {
|
|
6110
|
+
runCommand(() => searchCommand(opts));
|
|
6111
|
+
});
|
|
5107
6112
|
return program;
|
|
5108
6113
|
}
|
|
5109
6114
|
function isPrimaryModule() {
|