agent-inspect 0.1.1 → 1.0.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/README.md +198 -6
- package/package.json +6 -3
- package/packages/cli/dist/index.cjs +3619 -117
- package/packages/cli/dist/index.cjs.map +1 -1
- package/packages/cli/dist/index.mjs +3619 -118
- package/packages/cli/dist/index.mjs.map +1 -1
- package/packages/core/dist/index.cjs +2732 -30
- package/packages/core/dist/index.cjs.map +1 -1
- package/packages/core/dist/index.d.cts +461 -6
- package/packages/core/dist/index.d.ts +461 -6
- package/packages/core/dist/index.mjs +2687 -30
- package/packages/core/dist/index.mjs.map +1 -1
|
@@ -6,17 +6,21 @@ var path = require('path');
|
|
|
6
6
|
var url = require('url');
|
|
7
7
|
var commander = require('commander');
|
|
8
8
|
var promises = require('fs/promises');
|
|
9
|
-
var
|
|
9
|
+
var crypto = require('crypto');
|
|
10
10
|
var nanoid = require('nanoid');
|
|
11
|
+
var os = require('os');
|
|
11
12
|
var async_hooks = require('async_hooks');
|
|
12
|
-
var
|
|
13
|
+
var readline = require('readline');
|
|
14
|
+
var chalk2 = require('chalk');
|
|
15
|
+
var process$1 = require('process');
|
|
13
16
|
|
|
14
17
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
15
18
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
16
19
|
|
|
17
20
|
var path__default = /*#__PURE__*/_interopDefault(path);
|
|
21
|
+
var crypto__default = /*#__PURE__*/_interopDefault(crypto);
|
|
18
22
|
var os__default = /*#__PURE__*/_interopDefault(os);
|
|
19
|
-
var
|
|
23
|
+
var chalk2__default = /*#__PURE__*/_interopDefault(chalk2);
|
|
20
24
|
|
|
21
25
|
// packages/core/src/types.ts
|
|
22
26
|
var STEP_TYPES = [
|
|
@@ -28,9 +32,1030 @@ var STEP_TYPES = [
|
|
|
28
32
|
"state",
|
|
29
33
|
"custom"
|
|
30
34
|
];
|
|
35
|
+
function isRecord(value) {
|
|
36
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
37
|
+
}
|
|
31
38
|
function isStepType(value) {
|
|
32
39
|
return typeof value === "string" && STEP_TYPES.includes(value);
|
|
33
40
|
}
|
|
41
|
+
function isTraceEvent(value) {
|
|
42
|
+
if (!isRecord(value)) return false;
|
|
43
|
+
if (value.schemaVersion !== "0.1") return false;
|
|
44
|
+
if (typeof value.timestamp !== "number") return false;
|
|
45
|
+
if (typeof value.event !== "string") return false;
|
|
46
|
+
switch (value.event) {
|
|
47
|
+
case "run_started": {
|
|
48
|
+
return typeof value.runId === "string" && typeof value.name === "string" && typeof value.startTime === "number";
|
|
49
|
+
}
|
|
50
|
+
case "run_completed": {
|
|
51
|
+
return typeof value.runId === "string" && (value.status === "success" || value.status === "error") && typeof value.endTime === "number" && typeof value.durationMs === "number";
|
|
52
|
+
}
|
|
53
|
+
case "step_started": {
|
|
54
|
+
return typeof value.runId === "string" && typeof value.stepId === "string" && typeof value.name === "string" && isStepType(value.type) && typeof value.startTime === "number";
|
|
55
|
+
}
|
|
56
|
+
case "step_completed": {
|
|
57
|
+
return typeof value.runId === "string" && typeof value.stepId === "string" && (value.status === "success" || value.status === "error") && typeof value.endTime === "number" && typeof value.durationMs === "number";
|
|
58
|
+
}
|
|
59
|
+
default:
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function isRecord2(v) {
|
|
64
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
65
|
+
}
|
|
66
|
+
function isNonEmptyStringArray(v) {
|
|
67
|
+
return Array.isArray(v) && v.length > 0 && v.every((x) => typeof x === "string" && x.trim() !== "");
|
|
68
|
+
}
|
|
69
|
+
function validateRedact(redact) {
|
|
70
|
+
if (!Array.isArray(redact)) {
|
|
71
|
+
throw new Error("Invalid config: redact must be an array");
|
|
72
|
+
}
|
|
73
|
+
for (const r of redact) {
|
|
74
|
+
if (typeof r === "string") continue;
|
|
75
|
+
if (!isRecord2(r)) {
|
|
76
|
+
throw new Error("Invalid config: redact entries must be strings or objects");
|
|
77
|
+
}
|
|
78
|
+
if (typeof r.key !== "string" || r.key.trim() === "") {
|
|
79
|
+
throw new Error("Invalid config: redact.key must be a non-empty string");
|
|
80
|
+
}
|
|
81
|
+
if (r.strategy !== "full" && r.strategy !== "prefix" && r.strategy !== "hash") {
|
|
82
|
+
throw new Error(
|
|
83
|
+
`Invalid config: redact.strategy must be one of full, prefix, hash (got ${String(
|
|
84
|
+
r.strategy
|
|
85
|
+
)})`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
if (r.keep !== void 0 && (typeof r.keep !== "number" || !Number.isFinite(r.keep) || r.keep < 0)) {
|
|
89
|
+
throw new Error("Invalid config: redact.keep must be a non-negative number when provided");
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function validateMappings(mappings) {
|
|
94
|
+
if (!isRecord2(mappings)) {
|
|
95
|
+
throw new Error("Invalid config: mappings must be an object");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
var DEFAULT_LOG_INGEST_CONFIG = {
|
|
99
|
+
runIdKeys: ["runId", "traceId", "requestId", "decisionId", "jobId"],
|
|
100
|
+
eventKey: "event",
|
|
101
|
+
timestampKey: "timestamp",
|
|
102
|
+
messageKey: "message",
|
|
103
|
+
levelKey: "level",
|
|
104
|
+
heuristicWindowMs: 2e3,
|
|
105
|
+
mappings: {
|
|
106
|
+
"*.error": { kind: "ERROR", status: "error" },
|
|
107
|
+
"*.failed": { kind: "ERROR", status: "error" },
|
|
108
|
+
"*.llm.*": { kind: "LLM" },
|
|
109
|
+
"*.tool.*": { kind: "TOOL" },
|
|
110
|
+
"*.agent.*": { kind: "AGENT" },
|
|
111
|
+
"*.retriever.*": { kind: "RETRIEVER" },
|
|
112
|
+
"*.result.*": { kind: "RESULT" }
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
function mergeLogIngestConfig(base, override) {
|
|
116
|
+
const merged = {
|
|
117
|
+
...base,
|
|
118
|
+
...override,
|
|
119
|
+
mappings: {
|
|
120
|
+
...base.mappings ?? {},
|
|
121
|
+
...override.mappings ?? {}
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
return merged;
|
|
125
|
+
}
|
|
126
|
+
async function loadLogIngestConfig(configPath) {
|
|
127
|
+
if (configPath === void 0 || configPath.trim() === "") {
|
|
128
|
+
return DEFAULT_LOG_INGEST_CONFIG;
|
|
129
|
+
}
|
|
130
|
+
let rawText;
|
|
131
|
+
try {
|
|
132
|
+
rawText = await promises.readFile(configPath, "utf-8");
|
|
133
|
+
} catch (e) {
|
|
134
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
135
|
+
throw new Error(`Failed to read config file: ${configPath} (${msg})`);
|
|
136
|
+
}
|
|
137
|
+
let parsed;
|
|
138
|
+
try {
|
|
139
|
+
parsed = JSON.parse(rawText);
|
|
140
|
+
} catch (e) {
|
|
141
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
142
|
+
throw new Error(`Invalid JSON in config file: ${configPath} (${msg})`);
|
|
143
|
+
}
|
|
144
|
+
if (!isRecord2(parsed)) {
|
|
145
|
+
throw new Error("Invalid config: expected a JSON object at top-level");
|
|
146
|
+
}
|
|
147
|
+
const user = parsed;
|
|
148
|
+
if (user.runIdKeys !== void 0 && !isNonEmptyStringArray(user.runIdKeys)) {
|
|
149
|
+
throw new Error("Invalid config: runIdKeys must be a non-empty array of strings");
|
|
150
|
+
}
|
|
151
|
+
if (user.eventKey !== void 0 && (typeof user.eventKey !== "string" || user.eventKey.trim() === "")) {
|
|
152
|
+
throw new Error("Invalid config: eventKey must be a non-empty string");
|
|
153
|
+
}
|
|
154
|
+
for (const k of [
|
|
155
|
+
"timestampKey",
|
|
156
|
+
"messageKey",
|
|
157
|
+
"levelKey",
|
|
158
|
+
"parentIdKey",
|
|
159
|
+
"durationKey",
|
|
160
|
+
"statusKey"
|
|
161
|
+
]) {
|
|
162
|
+
const v = user[k];
|
|
163
|
+
if (v !== void 0 && (typeof v !== "string" || v.trim() === "")) {
|
|
164
|
+
throw new Error(`Invalid config: ${k} must be a non-empty string when provided`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (user.mappings !== void 0) {
|
|
168
|
+
validateMappings(user.mappings);
|
|
169
|
+
}
|
|
170
|
+
if (user.redact !== void 0) {
|
|
171
|
+
validateRedact(user.redact);
|
|
172
|
+
}
|
|
173
|
+
if (user.heuristicWindowMs !== void 0 && (typeof user.heuristicWindowMs !== "number" || !Number.isFinite(user.heuristicWindowMs) || user.heuristicWindowMs < 0)) {
|
|
174
|
+
throw new Error("Invalid config: heuristicWindowMs must be a non-negative number when provided");
|
|
175
|
+
}
|
|
176
|
+
if (user.redact && JSON.stringify(user.redact).includes("=>")) {
|
|
177
|
+
throw new Error("Invalid config: function strings are not supported in redact rules");
|
|
178
|
+
}
|
|
179
|
+
return mergeLogIngestConfig(DEFAULT_LOG_INGEST_CONFIG, user);
|
|
180
|
+
}
|
|
181
|
+
function isRecord3(v) {
|
|
182
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
183
|
+
}
|
|
184
|
+
var JsonLogParser = class {
|
|
185
|
+
parseLines(lines, filePath) {
|
|
186
|
+
const records = [];
|
|
187
|
+
const warnings = [];
|
|
188
|
+
for (let i = 0; i < lines.length; i++) {
|
|
189
|
+
const lineNumber = i + 1;
|
|
190
|
+
const raw = lines[i] ?? "";
|
|
191
|
+
const trimmed = raw.trim();
|
|
192
|
+
if (trimmed === "") continue;
|
|
193
|
+
let parsed;
|
|
194
|
+
try {
|
|
195
|
+
parsed = JSON.parse(trimmed);
|
|
196
|
+
} catch {
|
|
197
|
+
warnings.push({
|
|
198
|
+
code: "MALFORMED_JSON",
|
|
199
|
+
message: "Malformed JSON log line",
|
|
200
|
+
file: filePath,
|
|
201
|
+
line: lineNumber,
|
|
202
|
+
raw: trimmed.slice(0, 500)
|
|
203
|
+
});
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
if (!isRecord3(parsed)) {
|
|
207
|
+
warnings.push({
|
|
208
|
+
code: "MALFORMED_JSON",
|
|
209
|
+
message: "JSON log line must be an object",
|
|
210
|
+
file: filePath,
|
|
211
|
+
line: lineNumber,
|
|
212
|
+
raw: trimmed.slice(0, 500)
|
|
213
|
+
});
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
records.push({
|
|
217
|
+
raw: parsed,
|
|
218
|
+
file: filePath,
|
|
219
|
+
line: lineNumber,
|
|
220
|
+
sourceType: "json-log"
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
return { records, warnings };
|
|
224
|
+
}
|
|
225
|
+
async parseFile(filePath) {
|
|
226
|
+
let text;
|
|
227
|
+
try {
|
|
228
|
+
text = await promises.readFile(filePath, "utf-8");
|
|
229
|
+
} catch (e) {
|
|
230
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
231
|
+
throw new Error(`Failed to read log file: ${filePath} (${msg})`);
|
|
232
|
+
}
|
|
233
|
+
const lines = text.split(/\r?\n/);
|
|
234
|
+
return this.parseLines(lines, filePath);
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
function isRecord4(v) {
|
|
238
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
239
|
+
}
|
|
240
|
+
function findLastJsonObjectSubstring(line) {
|
|
241
|
+
let last;
|
|
242
|
+
for (let i = 0; i < line.length; i++) {
|
|
243
|
+
if (line[i] !== "{") continue;
|
|
244
|
+
let depth = 0;
|
|
245
|
+
let inString = false;
|
|
246
|
+
let escape = false;
|
|
247
|
+
for (let j = i; j < line.length; j++) {
|
|
248
|
+
const ch = line[j];
|
|
249
|
+
if (inString) {
|
|
250
|
+
if (escape) {
|
|
251
|
+
escape = false;
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
if (ch === "\\") {
|
|
255
|
+
escape = true;
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
if (ch === '"') {
|
|
259
|
+
inString = false;
|
|
260
|
+
}
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
if (ch === '"') {
|
|
264
|
+
inString = true;
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
if (ch === "{") depth += 1;
|
|
268
|
+
if (ch === "}") depth -= 1;
|
|
269
|
+
if (depth === 0) {
|
|
270
|
+
last = { start: i, end: j + 1 };
|
|
271
|
+
i = j;
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
if (depth < 0) break;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (!last) return void 0;
|
|
278
|
+
return line.slice(last.start, last.end);
|
|
279
|
+
}
|
|
280
|
+
var Log4jsParser = class {
|
|
281
|
+
parseLines(lines, filePath) {
|
|
282
|
+
const records = [];
|
|
283
|
+
const warnings = [];
|
|
284
|
+
for (let i = 0; i < lines.length; i++) {
|
|
285
|
+
const lineNumber = i + 1;
|
|
286
|
+
const rawLine = lines[i] ?? "";
|
|
287
|
+
const trimmed = rawLine.trim();
|
|
288
|
+
if (trimmed === "") continue;
|
|
289
|
+
const jsonText = findLastJsonObjectSubstring(trimmed);
|
|
290
|
+
if (!jsonText) {
|
|
291
|
+
warnings.push({
|
|
292
|
+
code: "UNSUPPORTED_LOG4JS_PAYLOAD",
|
|
293
|
+
message: "No embedded JSON object found in log4js line",
|
|
294
|
+
file: filePath,
|
|
295
|
+
line: lineNumber,
|
|
296
|
+
raw: trimmed.slice(0, 500)
|
|
297
|
+
});
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
let parsed;
|
|
301
|
+
try {
|
|
302
|
+
parsed = JSON.parse(jsonText);
|
|
303
|
+
} catch {
|
|
304
|
+
warnings.push({
|
|
305
|
+
code: "MALFORMED_JSON",
|
|
306
|
+
message: "Malformed embedded JSON object in log4js line",
|
|
307
|
+
file: filePath,
|
|
308
|
+
line: lineNumber,
|
|
309
|
+
raw: jsonText.slice(0, 500)
|
|
310
|
+
});
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
if (!isRecord4(parsed)) {
|
|
314
|
+
warnings.push({
|
|
315
|
+
code: "UNSUPPORTED_LOG4JS_PAYLOAD",
|
|
316
|
+
message: "Embedded JSON payload must be an object",
|
|
317
|
+
file: filePath,
|
|
318
|
+
line: lineNumber,
|
|
319
|
+
raw: jsonText.slice(0, 500)
|
|
320
|
+
});
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
records.push({
|
|
324
|
+
raw: parsed,
|
|
325
|
+
file: filePath,
|
|
326
|
+
line: lineNumber,
|
|
327
|
+
sourceType: "log4js"
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
return { records, warnings };
|
|
331
|
+
}
|
|
332
|
+
async parseFile(filePath) {
|
|
333
|
+
let text;
|
|
334
|
+
try {
|
|
335
|
+
text = await promises.readFile(filePath, "utf-8");
|
|
336
|
+
} catch (e) {
|
|
337
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
338
|
+
throw new Error(`Failed to read log file: ${filePath} (${msg})`);
|
|
339
|
+
}
|
|
340
|
+
const lines = text.split(/\r?\n/);
|
|
341
|
+
return this.parseLines(lines, filePath);
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
// packages/core/src/logs/mapping.ts
|
|
346
|
+
function wildcardMatch(pattern, value) {
|
|
347
|
+
if (pattern === value) return true;
|
|
348
|
+
if (!pattern.includes("*")) return false;
|
|
349
|
+
const parts = pattern.split("*");
|
|
350
|
+
let idx = 0;
|
|
351
|
+
for (let i = 0; i < parts.length; i++) {
|
|
352
|
+
const part = parts[i];
|
|
353
|
+
if (part === "") continue;
|
|
354
|
+
const found = value.indexOf(part, idx);
|
|
355
|
+
if (found === -1) return false;
|
|
356
|
+
if (i === 0 && !pattern.startsWith("*") && found !== 0) return false;
|
|
357
|
+
idx = found + part.length;
|
|
358
|
+
}
|
|
359
|
+
if (!pattern.endsWith("*")) {
|
|
360
|
+
const last = parts[parts.length - 1];
|
|
361
|
+
if (last !== "" && !value.endsWith(last)) return false;
|
|
362
|
+
if (last === "" && !value.endsWith(parts[parts.length - 2] ?? "")) return false;
|
|
363
|
+
}
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
366
|
+
function matchMapping(eventName, mappings) {
|
|
367
|
+
if (!mappings) return void 0;
|
|
368
|
+
if (mappings[eventName]) return mappings[eventName];
|
|
369
|
+
let bestKey;
|
|
370
|
+
let bestScore = -1;
|
|
371
|
+
for (const key of Object.keys(mappings)) {
|
|
372
|
+
if (!key.includes("*")) continue;
|
|
373
|
+
if (!wildcardMatch(key, eventName)) continue;
|
|
374
|
+
const score = key.replaceAll("*", "").length;
|
|
375
|
+
if (score > bestScore) {
|
|
376
|
+
bestScore = score;
|
|
377
|
+
bestKey = key;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return bestKey ? mappings[bestKey] : void 0;
|
|
381
|
+
}
|
|
382
|
+
var DEFAULT_REDACT_KEYS = [
|
|
383
|
+
"authorization",
|
|
384
|
+
"cookie",
|
|
385
|
+
"token",
|
|
386
|
+
"apiKey",
|
|
387
|
+
"password",
|
|
388
|
+
"secret",
|
|
389
|
+
"email"
|
|
390
|
+
];
|
|
391
|
+
function isRecord5(v) {
|
|
392
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
393
|
+
}
|
|
394
|
+
function toKey(s) {
|
|
395
|
+
return s.toLowerCase();
|
|
396
|
+
}
|
|
397
|
+
function stableHash(value) {
|
|
398
|
+
const h = crypto__default.default.createHash("sha256").update(value, "utf8").digest("hex");
|
|
399
|
+
return h.slice(0, 8);
|
|
400
|
+
}
|
|
401
|
+
function compileRules(rules) {
|
|
402
|
+
const out = /* @__PURE__ */ new Map();
|
|
403
|
+
const set = (r) => {
|
|
404
|
+
const k = toKey(r.key);
|
|
405
|
+
out.set(k, { ...r, key: k });
|
|
406
|
+
};
|
|
407
|
+
for (const k of DEFAULT_REDACT_KEYS) {
|
|
408
|
+
set({ key: k, strategy: "full" });
|
|
409
|
+
}
|
|
410
|
+
for (const r of rules ?? []) {
|
|
411
|
+
if (typeof r === "string") {
|
|
412
|
+
set({ key: r, strategy: "full" });
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
const key = r.key;
|
|
416
|
+
if (r.strategy === "full") set({ key, strategy: "full" });
|
|
417
|
+
if (r.strategy === "hash") set({ key, strategy: "hash" });
|
|
418
|
+
if (r.strategy === "prefix") {
|
|
419
|
+
set({ key, strategy: "prefix", keep: typeof r.keep === "number" ? r.keep : 8 });
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return [...out.values()];
|
|
423
|
+
}
|
|
424
|
+
var Redactor = class {
|
|
425
|
+
#rules;
|
|
426
|
+
constructor(options) {
|
|
427
|
+
this.#rules = compileRules(options?.rules);
|
|
428
|
+
}
|
|
429
|
+
redactValue(key, value) {
|
|
430
|
+
const k = toKey(key);
|
|
431
|
+
const rule = this.#rules.find((r) => r.key === k);
|
|
432
|
+
if (!rule) {
|
|
433
|
+
return this.#redactNested(value);
|
|
434
|
+
}
|
|
435
|
+
if (rule.strategy === "full") return "[REDACTED]";
|
|
436
|
+
const asString = typeof value === "string" ? value : typeof value === "number" || typeof value === "boolean" || typeof value === "bigint" ? String(value) : void 0;
|
|
437
|
+
if (rule.strategy === "prefix") {
|
|
438
|
+
if (asString === void 0) return "[REDACTED]";
|
|
439
|
+
const keep = Math.max(0, Math.floor(rule.keep));
|
|
440
|
+
return asString.length <= keep ? `${asString}\u2026` : `${asString.slice(0, keep)}\u2026`;
|
|
441
|
+
}
|
|
442
|
+
if (rule.strategy === "hash") {
|
|
443
|
+
if (asString === void 0) return "[HASH:unknown]";
|
|
444
|
+
return `[HASH:${stableHash(asString)}]`;
|
|
445
|
+
}
|
|
446
|
+
return this.#redactNested(value);
|
|
447
|
+
}
|
|
448
|
+
redactRecord(record) {
|
|
449
|
+
const out = {};
|
|
450
|
+
for (const [k, v] of Object.entries(record)) {
|
|
451
|
+
out[k] = this.redactValue(k, v);
|
|
452
|
+
}
|
|
453
|
+
return out;
|
|
454
|
+
}
|
|
455
|
+
#redactNested(value) {
|
|
456
|
+
if (Array.isArray(value)) {
|
|
457
|
+
return value.map((v) => this.#redactNested(v));
|
|
458
|
+
}
|
|
459
|
+
if (isRecord5(value)) {
|
|
460
|
+
const out = {};
|
|
461
|
+
for (const [k, v] of Object.entries(value)) {
|
|
462
|
+
out[k] = this.redactValue(k, v);
|
|
463
|
+
}
|
|
464
|
+
return out;
|
|
465
|
+
}
|
|
466
|
+
return value;
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
function isFiniteNumber(v) {
|
|
470
|
+
return typeof v === "number" && Number.isFinite(v);
|
|
471
|
+
}
|
|
472
|
+
function safeString(v) {
|
|
473
|
+
if (typeof v !== "string") return void 0;
|
|
474
|
+
const t = v.trim();
|
|
475
|
+
return t === "" ? void 0 : t;
|
|
476
|
+
}
|
|
477
|
+
function parseTimestamp(v) {
|
|
478
|
+
if (isFiniteNumber(v)) return v;
|
|
479
|
+
if (typeof v === "string") {
|
|
480
|
+
const t = Date.parse(v);
|
|
481
|
+
if (Number.isFinite(t)) return t;
|
|
482
|
+
}
|
|
483
|
+
return void 0;
|
|
484
|
+
}
|
|
485
|
+
function hasToken(hay, token) {
|
|
486
|
+
return hay.includes(token);
|
|
487
|
+
}
|
|
488
|
+
function inferKind(eventName) {
|
|
489
|
+
if (hasToken(eventName, ".llm.")) return "LLM";
|
|
490
|
+
if (hasToken(eventName, ".tool.")) return "TOOL";
|
|
491
|
+
if (hasToken(eventName, ".agent.")) return "AGENT";
|
|
492
|
+
if (hasToken(eventName, ".retriever.")) return "RETRIEVER";
|
|
493
|
+
if (hasToken(eventName, ".result.")) return "RESULT";
|
|
494
|
+
if (eventName.endsWith(".error") || eventName.endsWith(".failed") || eventName.includes(".error") || eventName.includes(".failed")) {
|
|
495
|
+
return "ERROR";
|
|
496
|
+
}
|
|
497
|
+
return "LOG";
|
|
498
|
+
}
|
|
499
|
+
function deriveName(eventName, kind) {
|
|
500
|
+
const parts = eventName.split(".");
|
|
501
|
+
const last = parts[parts.length - 1] ?? eventName;
|
|
502
|
+
if (kind === "LLM") return `llm:${last}`;
|
|
503
|
+
if (kind === "TOOL") return `tool:${last}`;
|
|
504
|
+
if (kind === "AGENT") return `agent:${last}`;
|
|
505
|
+
if (kind === "RESULT") return `result:${last}`;
|
|
506
|
+
if (kind === "RUN") return `run:${last}`;
|
|
507
|
+
if (kind === "RETRIEVER") return `retriever:${last}`;
|
|
508
|
+
if (kind === "ERROR") return `error:${last}`;
|
|
509
|
+
return eventName;
|
|
510
|
+
}
|
|
511
|
+
var EventNormalizer = class {
|
|
512
|
+
#config;
|
|
513
|
+
constructor(options) {
|
|
514
|
+
this.#config = options.config;
|
|
515
|
+
}
|
|
516
|
+
#normalizeInternal(record) {
|
|
517
|
+
const raw = record.raw;
|
|
518
|
+
const cfg = this.#config;
|
|
519
|
+
let runId;
|
|
520
|
+
for (const k of cfg.runIdKeys) {
|
|
521
|
+
const v = raw[k];
|
|
522
|
+
const s = safeString(v);
|
|
523
|
+
if (s) {
|
|
524
|
+
runId = s;
|
|
525
|
+
break;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
if (!runId) {
|
|
529
|
+
return {
|
|
530
|
+
warning: {
|
|
531
|
+
code: "MISSING_RUN_ID",
|
|
532
|
+
message: "Missing run id (none of runIdKeys present)",
|
|
533
|
+
file: record.file,
|
|
534
|
+
line: record.line
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
const eventName = safeString(raw[cfg.eventKey]);
|
|
539
|
+
if (!eventName) {
|
|
540
|
+
return {
|
|
541
|
+
warning: {
|
|
542
|
+
code: "MISSING_EVENT",
|
|
543
|
+
message: `Missing event name (key: ${cfg.eventKey})`,
|
|
544
|
+
file: record.file,
|
|
545
|
+
line: record.line
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
const mapping = matchMapping(eventName, cfg.mappings);
|
|
550
|
+
const tsKey = cfg.timestampKey ?? "timestamp";
|
|
551
|
+
const tsRaw = raw[tsKey];
|
|
552
|
+
const parsedTs = parseTimestamp(tsRaw);
|
|
553
|
+
const timestamp = parsedTs ?? Date.now();
|
|
554
|
+
const timestampMissing = parsedTs === void 0;
|
|
555
|
+
const parentIdKey = cfg.parentIdKey;
|
|
556
|
+
const parentId = parentIdKey ? safeString(raw[parentIdKey]) : void 0;
|
|
557
|
+
let durationMs;
|
|
558
|
+
const durationKey = cfg.durationKey;
|
|
559
|
+
if (durationKey) {
|
|
560
|
+
const v = raw[durationKey];
|
|
561
|
+
if (isFiniteNumber(v)) durationMs = v;
|
|
562
|
+
} else if (isFiniteNumber(raw.durationMs)) {
|
|
563
|
+
durationMs = raw.durationMs;
|
|
564
|
+
}
|
|
565
|
+
let status;
|
|
566
|
+
const statusKey = cfg.statusKey;
|
|
567
|
+
const statusRaw = statusKey ? safeString(raw[statusKey]) : void 0;
|
|
568
|
+
if (statusRaw === "running" || statusRaw === "ok" || statusRaw === "error") {
|
|
569
|
+
status = statusRaw;
|
|
570
|
+
} else if (mapping?.status) {
|
|
571
|
+
status = mapping.status;
|
|
572
|
+
} else if (mapping?.kind === "ERROR") {
|
|
573
|
+
status = "error";
|
|
574
|
+
} else if (eventName.includes(".failed") || eventName.includes(".error")) {
|
|
575
|
+
status = "error";
|
|
576
|
+
} else if (mapping?.startsRun || mapping?.startsStep) {
|
|
577
|
+
status = "running";
|
|
578
|
+
} else if (mapping?.endsRun || mapping?.endsStep) {
|
|
579
|
+
status = "ok";
|
|
580
|
+
}
|
|
581
|
+
const kind = mapping?.kind ?? inferKind(eventName);
|
|
582
|
+
const name = mapping?.name ?? deriveName(eventName, kind);
|
|
583
|
+
let confidence = "correlated";
|
|
584
|
+
if (parentId || mapping?.startsRun) confidence = "explicit";
|
|
585
|
+
if (timestampMissing) confidence = "unknown";
|
|
586
|
+
const omit = new Set([
|
|
587
|
+
...cfg.runIdKeys,
|
|
588
|
+
cfg.eventKey,
|
|
589
|
+
tsKey,
|
|
590
|
+
cfg.messageKey ?? "message",
|
|
591
|
+
cfg.levelKey ?? "level",
|
|
592
|
+
cfg.parentIdKey ?? "",
|
|
593
|
+
cfg.durationKey ?? "",
|
|
594
|
+
cfg.statusKey ?? "",
|
|
595
|
+
"durationMs"
|
|
596
|
+
].filter((k) => k !== ""));
|
|
597
|
+
const attributes = {};
|
|
598
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
599
|
+
if (omit.has(k)) continue;
|
|
600
|
+
attributes[k] = v;
|
|
601
|
+
}
|
|
602
|
+
const event = {
|
|
603
|
+
eventId: nanoid.nanoid(10),
|
|
604
|
+
runId,
|
|
605
|
+
...parentId ? { parentId } : {},
|
|
606
|
+
name,
|
|
607
|
+
kind,
|
|
608
|
+
timestamp,
|
|
609
|
+
...status ? { status } : {},
|
|
610
|
+
...durationMs !== void 0 ? { durationMs } : {},
|
|
611
|
+
...Object.keys(attributes).length > 0 ? { attributes } : {},
|
|
612
|
+
confidence,
|
|
613
|
+
source: {
|
|
614
|
+
type: record.sourceType,
|
|
615
|
+
file: record.file,
|
|
616
|
+
line: record.line
|
|
617
|
+
}
|
|
618
|
+
};
|
|
619
|
+
if (timestampMissing) {
|
|
620
|
+
return {
|
|
621
|
+
event,
|
|
622
|
+
warning: {
|
|
623
|
+
code: "MISSING_TIMESTAMP",
|
|
624
|
+
message: `Missing or invalid timestamp (key: ${tsKey})`,
|
|
625
|
+
file: record.file,
|
|
626
|
+
line: record.line,
|
|
627
|
+
raw: JSON.stringify(raw).slice(0, 500)
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
return { event };
|
|
632
|
+
}
|
|
633
|
+
normalize(record) {
|
|
634
|
+
const r = this.#normalizeInternal(record);
|
|
635
|
+
return r.event ?? r.warning;
|
|
636
|
+
}
|
|
637
|
+
normalizeAll(records) {
|
|
638
|
+
const out = [];
|
|
639
|
+
const warnings = [];
|
|
640
|
+
for (const r of records) {
|
|
641
|
+
const normalized = this.#normalizeInternal(r);
|
|
642
|
+
if (normalized.event) out.push(normalized.event);
|
|
643
|
+
if (normalized.warning) warnings.push(normalized.warning);
|
|
644
|
+
}
|
|
645
|
+
return { records: out, warnings };
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
// packages/core/src/logs/tree-builder.ts
|
|
650
|
+
function inc(map, key) {
|
|
651
|
+
map[key] = (map[key] ?? 0) + 1;
|
|
652
|
+
}
|
|
653
|
+
function computeRunStatus(events) {
|
|
654
|
+
let hasRunning = false;
|
|
655
|
+
for (const e of events) {
|
|
656
|
+
if (e.status === "error") return "error";
|
|
657
|
+
if (e.status === "running") hasRunning = true;
|
|
658
|
+
}
|
|
659
|
+
if (hasRunning) return "running";
|
|
660
|
+
return "ok";
|
|
661
|
+
}
|
|
662
|
+
var TreeBuilder = class {
|
|
663
|
+
constructor(options) {
|
|
664
|
+
void options?.config;
|
|
665
|
+
}
|
|
666
|
+
build(events) {
|
|
667
|
+
const byRun = /* @__PURE__ */ new Map();
|
|
668
|
+
for (const e of events) {
|
|
669
|
+
if (!byRun.has(e.runId)) byRun.set(e.runId, []);
|
|
670
|
+
byRun.get(e.runId).push(e);
|
|
671
|
+
}
|
|
672
|
+
const out = [];
|
|
673
|
+
for (const [runId, runEvents] of byRun.entries()) {
|
|
674
|
+
const sorted = [...runEvents].sort((a, b) => a.timestamp - b.timestamp);
|
|
675
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
676
|
+
for (const e of sorted) {
|
|
677
|
+
nodes.set(e.eventId, { event: e, children: [], depth: 0 });
|
|
678
|
+
}
|
|
679
|
+
const roots = [];
|
|
680
|
+
for (const node of nodes.values()) {
|
|
681
|
+
const parentId = node.event.parentId;
|
|
682
|
+
if (parentId && nodes.has(parentId)) {
|
|
683
|
+
nodes.get(parentId).children.push(node);
|
|
684
|
+
} else {
|
|
685
|
+
roots.push(node);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
const assignDepth = (n, depth) => {
|
|
689
|
+
n.depth = depth;
|
|
690
|
+
for (const c of n.children) assignDepth(c, depth + 1);
|
|
691
|
+
};
|
|
692
|
+
for (const r of roots) assignDepth(r, 0);
|
|
693
|
+
const confidenceBreakdown = {
|
|
694
|
+
explicit: 0,
|
|
695
|
+
correlated: 0,
|
|
696
|
+
heuristic: 0,
|
|
697
|
+
unknown: 0
|
|
698
|
+
};
|
|
699
|
+
const kinds = {};
|
|
700
|
+
for (const e of sorted) {
|
|
701
|
+
inc(confidenceBreakdown, e.confidence);
|
|
702
|
+
kinds[e.kind] = (kinds[e.kind] ?? 0) + 1;
|
|
703
|
+
}
|
|
704
|
+
const startedAt = sorted.length > 0 ? sorted[0].timestamp : void 0;
|
|
705
|
+
const endedAt = sorted.length > 0 ? sorted[sorted.length - 1].timestamp : void 0;
|
|
706
|
+
const status = computeRunStatus(sorted);
|
|
707
|
+
const durationMs = startedAt !== void 0 && endedAt !== void 0 && Number.isFinite(startedAt) && Number.isFinite(endedAt) && endedAt >= startedAt && status !== "running" ? endedAt - startedAt : void 0;
|
|
708
|
+
const name = sorted.find((e) => e.kind === "RUN")?.name;
|
|
709
|
+
out.push({
|
|
710
|
+
runId,
|
|
711
|
+
name,
|
|
712
|
+
status,
|
|
713
|
+
startedAt,
|
|
714
|
+
endedAt: status === "running" ? void 0 : endedAt,
|
|
715
|
+
durationMs,
|
|
716
|
+
children: roots,
|
|
717
|
+
metadata: {
|
|
718
|
+
totalEvents: sorted.length,
|
|
719
|
+
confidenceBreakdown,
|
|
720
|
+
kinds
|
|
721
|
+
}
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
out.sort((a, b) => (b.startedAt ?? 0) - (a.startedAt ?? 0));
|
|
725
|
+
return out;
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
|
|
729
|
+
// packages/core/src/logs/tree-renderer.ts
|
|
730
|
+
function truncate(v, max) {
|
|
731
|
+
if (v.length <= max) return v;
|
|
732
|
+
return v.slice(0, Math.max(0, max - 1)) + "\u2026";
|
|
733
|
+
}
|
|
734
|
+
function fmtAttrValue(value, maxLen) {
|
|
735
|
+
if (value === null || value === void 0) return void 0;
|
|
736
|
+
if (typeof value === "string") return truncate(value, maxLen);
|
|
737
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
738
|
+
return String(value);
|
|
739
|
+
}
|
|
740
|
+
if (typeof value === "object") {
|
|
741
|
+
try {
|
|
742
|
+
return truncate(JSON.stringify(value), maxLen);
|
|
743
|
+
} catch {
|
|
744
|
+
return "[object]";
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
return String(value);
|
|
748
|
+
}
|
|
749
|
+
function compactAttrs(attrs, maxLen) {
|
|
750
|
+
if (!attrs) return "";
|
|
751
|
+
const entries = Object.entries(attrs);
|
|
752
|
+
if (entries.length === 0) return "";
|
|
753
|
+
const picks = /* @__PURE__ */ new Map();
|
|
754
|
+
const set = (k, v) => {
|
|
755
|
+
const s = fmtAttrValue(v, maxLen);
|
|
756
|
+
if (s !== void 0) picks.set(k, s);
|
|
757
|
+
};
|
|
758
|
+
set("job", attrs.jobId ?? attrs.job ?? attrs.jobUuid);
|
|
759
|
+
set("user", attrs.userUuid ?? attrs.userId ?? attrs.user);
|
|
760
|
+
set("trip", attrs.tripUuid ?? attrs.tripId ?? attrs.trip);
|
|
761
|
+
set("msgs", attrs.messageCount ?? attrs.msgs);
|
|
762
|
+
set("trips", attrs.trips);
|
|
763
|
+
set("model", attrs.model);
|
|
764
|
+
const tokens = attrs.tokens;
|
|
765
|
+
if (tokens && typeof tokens === "object" && tokens !== null) {
|
|
766
|
+
const input3 = tokens.input;
|
|
767
|
+
const output2 = tokens.output;
|
|
768
|
+
if (typeof input3 === "number" || typeof output2 === "number") {
|
|
769
|
+
picks.set("tokens", `${input3 ?? "?"}/${output2 ?? "?"}`);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
for (const k of ["shouldNotify", "variant"]) {
|
|
773
|
+
if (k in attrs) set(k, attrs[k]);
|
|
774
|
+
}
|
|
775
|
+
const rendered = [...picks.entries()].filter(([, v]) => v !== "").map(([k, v]) => `${k}=${v}`);
|
|
776
|
+
return rendered.length > 0 ? " " + rendered.join(" ") : "";
|
|
777
|
+
}
|
|
778
|
+
function statusMark(node) {
|
|
779
|
+
if (node.event.status === "error") return " \u2716";
|
|
780
|
+
if (node.event.status === "ok") return " \u2714";
|
|
781
|
+
return "";
|
|
782
|
+
}
|
|
783
|
+
function fmtDuration(ms) {
|
|
784
|
+
if (!Number.isFinite(ms) || ms < 0) return "";
|
|
785
|
+
if (ms < 1e3) return `${Math.round(ms)}ms`;
|
|
786
|
+
return `${(ms / 1e3).toFixed(2)}s`;
|
|
787
|
+
}
|
|
788
|
+
function renderNodeLines(node, prefix, isLast, options) {
|
|
789
|
+
const branch = prefix + (isLast ? "\u2514\u2500 " : "\u251C\u2500 ");
|
|
790
|
+
const nextPrefix = prefix + (isLast ? " " : "\u2502 ");
|
|
791
|
+
const attrs = compactAttrs(node.event.attributes, options.maxAttributeLength);
|
|
792
|
+
const dur = node.event.durationMs !== void 0 ? ` ${fmtDuration(node.event.durationMs)}` : "";
|
|
793
|
+
const line = `${branch}${node.event.name}${attrs}${statusMark(node)}${dur}`;
|
|
794
|
+
const lines = [line];
|
|
795
|
+
const showConf = options.showConfidence === "always" || options.showConfidence === "non-explicit" && node.event.confidence !== "explicit";
|
|
796
|
+
if (showConf) {
|
|
797
|
+
lines.push(`${nextPrefix}confidence: ${node.event.confidence}`);
|
|
798
|
+
}
|
|
799
|
+
const children = node.children;
|
|
800
|
+
for (let i = 0; i < children.length; i++) {
|
|
801
|
+
lines.push(...renderNodeLines(children[i], nextPrefix, i === children.length - 1, options));
|
|
802
|
+
}
|
|
803
|
+
return lines;
|
|
804
|
+
}
|
|
805
|
+
function renderRunTree(tree, options) {
|
|
806
|
+
const opts = {
|
|
807
|
+
verbose: options?.verbose ?? false,
|
|
808
|
+
showConfidence: options?.showConfidence ?? "always",
|
|
809
|
+
showMetadata: options?.showMetadata ?? false,
|
|
810
|
+
color: options?.color ?? false,
|
|
811
|
+
maxAttributeLength: options?.maxAttributeLength ?? 40,
|
|
812
|
+
summary: options?.summary ?? true
|
|
813
|
+
};
|
|
814
|
+
const header = `Run ${tree.runId}`;
|
|
815
|
+
const lines = [header];
|
|
816
|
+
const children = tree.children;
|
|
817
|
+
for (let i = 0; i < children.length; i++) {
|
|
818
|
+
lines.push(...renderNodeLines(children[i], "", i === children.length - 1, opts));
|
|
819
|
+
}
|
|
820
|
+
if (opts.summary) {
|
|
821
|
+
const cb = tree.metadata.confidenceBreakdown;
|
|
822
|
+
const tools = tree.metadata.kinds.TOOL ?? 0;
|
|
823
|
+
const llms = tree.metadata.kinds.LLM ?? 0;
|
|
824
|
+
lines.push("");
|
|
825
|
+
lines.push("Summary:");
|
|
826
|
+
lines.push(` Events: ${tree.metadata.totalEvents}`);
|
|
827
|
+
lines.push(` Tools: ${tools}`);
|
|
828
|
+
lines.push(` LLMs: ${llms}`);
|
|
829
|
+
lines.push(
|
|
830
|
+
` Confidence: ${cb.explicit} explicit, ${cb.correlated} correlated, ${cb.heuristic} heuristic, ${cb.unknown} unknown`
|
|
831
|
+
);
|
|
832
|
+
lines.push("");
|
|
833
|
+
lines.push("Note:");
|
|
834
|
+
lines.push(" Flat timeline by default. Nesting only with explicit parentId.");
|
|
835
|
+
}
|
|
836
|
+
return lines.join("\n");
|
|
837
|
+
}
|
|
838
|
+
function renderRunTrees(trees, options) {
|
|
839
|
+
return trees.map((t) => renderRunTree(t, options)).join("\n\n");
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// packages/core/src/logs/line-parser.ts
|
|
843
|
+
function shiftLineNumbers(res, options) {
|
|
844
|
+
const targetLine = typeof options.line === "number" && Number.isFinite(options.line) && options.line > 0 ? Math.floor(options.line) : void 0;
|
|
845
|
+
const file = typeof options.file === "string" && options.file.trim() !== "" ? options.file : void 0;
|
|
846
|
+
if (targetLine === void 0 && file === void 0) return res;
|
|
847
|
+
const mapRecord = (x) => ({
|
|
848
|
+
...x,
|
|
849
|
+
...targetLine !== void 0 ? { line: targetLine } : {},
|
|
850
|
+
...file !== void 0 ? { file } : {}
|
|
851
|
+
});
|
|
852
|
+
const mapWarning = (x) => ({
|
|
853
|
+
...x,
|
|
854
|
+
...targetLine !== void 0 ? { line: targetLine } : {},
|
|
855
|
+
...file !== void 0 ? { file } : {}
|
|
856
|
+
});
|
|
857
|
+
return {
|
|
858
|
+
records: res.records.map(mapRecord),
|
|
859
|
+
warnings: res.warnings.map(mapWarning)
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
function normalizeFormat(line, format) {
|
|
863
|
+
if (format && format !== "auto") return format;
|
|
864
|
+
const trimmed = line.trim();
|
|
865
|
+
if (trimmed.startsWith("{")) return "json";
|
|
866
|
+
return "log4js";
|
|
867
|
+
}
|
|
868
|
+
function parseLogLine(line, options = {}) {
|
|
869
|
+
const raw = typeof line === "string" ? line : "";
|
|
870
|
+
const trimmed = raw.trim();
|
|
871
|
+
if (trimmed === "") return { records: [], warnings: [] };
|
|
872
|
+
const format = normalizeFormat(raw, options.format);
|
|
873
|
+
const base = format === "json" ? new JsonLogParser().parseLines([raw], options.file) : new Log4jsParser().parseLines([raw], options.file);
|
|
874
|
+
return shiftLineNumbers(base, options);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// packages/core/src/logs/live-tree.ts
|
|
878
|
+
var LiveLogAccumulator = class {
|
|
879
|
+
#config;
|
|
880
|
+
#format;
|
|
881
|
+
#file;
|
|
882
|
+
#normalizer;
|
|
883
|
+
#redactor;
|
|
884
|
+
#treeBuilder;
|
|
885
|
+
#events = [];
|
|
886
|
+
#warnings = [];
|
|
887
|
+
#trees = [];
|
|
888
|
+
constructor(options) {
|
|
889
|
+
this.#config = mergeLogIngestConfig(options.config, {});
|
|
890
|
+
this.#format = options.format ?? "auto";
|
|
891
|
+
this.#file = options.file;
|
|
892
|
+
this.#normalizer = new EventNormalizer({ config: this.#config });
|
|
893
|
+
this.#redactor = new Redactor({ rules: this.#config.redact });
|
|
894
|
+
this.#treeBuilder = new TreeBuilder({ config: this.#config });
|
|
895
|
+
}
|
|
896
|
+
pushLine(line, lineNumber) {
|
|
897
|
+
try {
|
|
898
|
+
const parsed = parseLogLine(line, {
|
|
899
|
+
format: this.#format,
|
|
900
|
+
file: this.#file,
|
|
901
|
+
line: lineNumber
|
|
902
|
+
});
|
|
903
|
+
const normalized = this.#normalizer.normalizeAll(parsed.records);
|
|
904
|
+
const redactedEvents = normalized.records.map((e) => ({
|
|
905
|
+
...e,
|
|
906
|
+
attributes: e.attributes ? this.#redactor.redactRecord(e.attributes) : void 0
|
|
907
|
+
}));
|
|
908
|
+
this.#events = [...this.#events, ...redactedEvents];
|
|
909
|
+
this.#warnings = [...this.#warnings, ...parsed.warnings, ...normalized.warnings];
|
|
910
|
+
this.#trees = this.#treeBuilder.build(this.#events);
|
|
911
|
+
return {
|
|
912
|
+
events: this.#events,
|
|
913
|
+
trees: this.#trees,
|
|
914
|
+
warnings: this.#warnings
|
|
915
|
+
};
|
|
916
|
+
} catch (e) {
|
|
917
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
918
|
+
const warning = {
|
|
919
|
+
code: "UNKNOWN",
|
|
920
|
+
message: `LiveLogAccumulator failed to process line (${msg})`,
|
|
921
|
+
file: this.#file,
|
|
922
|
+
line: lineNumber,
|
|
923
|
+
raw: typeof line === "string" ? line.slice(0, 500) : void 0
|
|
924
|
+
};
|
|
925
|
+
this.#warnings = [...this.#warnings, warning];
|
|
926
|
+
return { events: this.#events, trees: this.#trees, warnings: this.#warnings };
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
getEvents() {
|
|
930
|
+
return this.#events;
|
|
931
|
+
}
|
|
932
|
+
getTrees() {
|
|
933
|
+
return this.#trees;
|
|
934
|
+
}
|
|
935
|
+
getWarnings() {
|
|
936
|
+
return this.#warnings;
|
|
937
|
+
}
|
|
938
|
+
reset() {
|
|
939
|
+
this.#events = [];
|
|
940
|
+
this.#trees = [];
|
|
941
|
+
this.#warnings = [];
|
|
942
|
+
}
|
|
943
|
+
};
|
|
944
|
+
|
|
945
|
+
// packages/core/src/logs/index.ts
|
|
946
|
+
function firstNonEmptyLine(text) {
|
|
947
|
+
for (const line of text.split(/\r?\n/)) {
|
|
948
|
+
const t = line.trim();
|
|
949
|
+
if (t !== "") return t;
|
|
950
|
+
}
|
|
951
|
+
return void 0;
|
|
952
|
+
}
|
|
953
|
+
async function detectFormat(filePath) {
|
|
954
|
+
const text = await promises.readFile(filePath, "utf-8");
|
|
955
|
+
const first = firstNonEmptyLine(text);
|
|
956
|
+
if (!first) return "log4js";
|
|
957
|
+
if (!first.startsWith("{")) return "log4js";
|
|
958
|
+
try {
|
|
959
|
+
const parsed = JSON.parse(first);
|
|
960
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return "json";
|
|
961
|
+
} catch {
|
|
962
|
+
}
|
|
963
|
+
return "log4js";
|
|
964
|
+
}
|
|
965
|
+
function applyOverrides(cfg, options) {
|
|
966
|
+
const override = {};
|
|
967
|
+
for (const k of [
|
|
968
|
+
"runIdKeys",
|
|
969
|
+
"eventKey",
|
|
970
|
+
"timestampKey",
|
|
971
|
+
"messageKey",
|
|
972
|
+
"levelKey",
|
|
973
|
+
"parentIdKey",
|
|
974
|
+
"durationKey",
|
|
975
|
+
"statusKey"
|
|
976
|
+
]) {
|
|
977
|
+
const v = options[k];
|
|
978
|
+
if (v !== void 0) override[k] = v;
|
|
979
|
+
}
|
|
980
|
+
return mergeLogIngestConfig(cfg, override);
|
|
981
|
+
}
|
|
982
|
+
async function parseLogsToTrees(filePath, options = {}) {
|
|
983
|
+
const base = options.config ?? await loadLogIngestConfig(options.configPath);
|
|
984
|
+
const config = applyOverrides(base, options);
|
|
985
|
+
const format = options.format === "auto" || options.format === void 0 ? await detectFormat(filePath) : options.format;
|
|
986
|
+
let parsed;
|
|
987
|
+
if (format === "json") {
|
|
988
|
+
parsed = await new JsonLogParser().parseFile(filePath);
|
|
989
|
+
} else {
|
|
990
|
+
parsed = await new Log4jsParser().parseFile(filePath);
|
|
991
|
+
}
|
|
992
|
+
const normalizer = new EventNormalizer({ config });
|
|
993
|
+
const normalized = normalizer.normalizeAll(parsed.records);
|
|
994
|
+
const redactor = new Redactor({ rules: config.redact });
|
|
995
|
+
const events = normalized.records.map((e) => ({
|
|
996
|
+
...e,
|
|
997
|
+
attributes: e.attributes ? redactor.redactRecord(e.attributes) : void 0
|
|
998
|
+
}));
|
|
999
|
+
const trees = new TreeBuilder({ config }).build(events);
|
|
1000
|
+
return {
|
|
1001
|
+
events,
|
|
1002
|
+
trees,
|
|
1003
|
+
warnings: [...parsed.warnings, ...normalized.warnings]
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
// packages/core/src/utils/duration.ts
|
|
1008
|
+
function parseDuration(duration) {
|
|
1009
|
+
const raw = typeof duration === "string" ? duration.trim() : "";
|
|
1010
|
+
const match = raw.match(/^(\d+)(ms|[smhd])$/);
|
|
1011
|
+
if (!match) {
|
|
1012
|
+
throw new Error(
|
|
1013
|
+
`Invalid duration format: ${duration}. Use a positive integer followed by ms, s, m, h, or d (e.g. 500ms, 30s, 5m, 2h, 7d).`
|
|
1014
|
+
);
|
|
1015
|
+
}
|
|
1016
|
+
const amount = Number.parseInt(match[1], 10);
|
|
1017
|
+
const unit = match[2];
|
|
1018
|
+
if (!Number.isFinite(amount) || amount <= 0) {
|
|
1019
|
+
throw new Error(
|
|
1020
|
+
`Invalid duration amount: ${duration}. Amount must be a positive integer.`
|
|
1021
|
+
);
|
|
1022
|
+
}
|
|
1023
|
+
switch (unit) {
|
|
1024
|
+
case "ms":
|
|
1025
|
+
return amount;
|
|
1026
|
+
case "s":
|
|
1027
|
+
return amount * 1e3;
|
|
1028
|
+
case "m":
|
|
1029
|
+
return amount * 60 * 1e3;
|
|
1030
|
+
case "h":
|
|
1031
|
+
return amount * 60 * 60 * 1e3;
|
|
1032
|
+
case "d":
|
|
1033
|
+
return amount * 24 * 60 * 60 * 1e3;
|
|
1034
|
+
default: {
|
|
1035
|
+
throw new Error(`Unknown duration unit: ${unit}`);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
function formatDuration(ms) {
|
|
1040
|
+
if (!Number.isFinite(ms)) {
|
|
1041
|
+
return "0ms";
|
|
1042
|
+
}
|
|
1043
|
+
if (ms < 0) {
|
|
1044
|
+
throw new Error(`formatDuration: ms must be non-negative (got ${ms})`);
|
|
1045
|
+
}
|
|
1046
|
+
if (ms < 1e3) {
|
|
1047
|
+
return `${Math.floor(ms)}ms`;
|
|
1048
|
+
}
|
|
1049
|
+
if (ms < 6e4) {
|
|
1050
|
+
return `${(ms / 1e3).toFixed(2)}s`;
|
|
1051
|
+
}
|
|
1052
|
+
if (ms < 36e5) {
|
|
1053
|
+
return `${(ms / 6e4).toFixed(1)}m`;
|
|
1054
|
+
}
|
|
1055
|
+
return `${(ms / 36e5).toFixed(1)}h`;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
// packages/core/src/utils.ts
|
|
34
1059
|
var DEFAULT_TRACE_DIR_NAME = ".agent-inspect";
|
|
35
1060
|
var RUNS_DIR_NAME = "runs";
|
|
36
1061
|
var FALLBACK_TRACE_DIR = path__default.default.join(
|
|
@@ -42,15 +1067,8 @@ var MAX_NAME_LENGTH = 100;
|
|
|
42
1067
|
function createStepId() {
|
|
43
1068
|
return `step_${nanoid.nanoid(10)}`;
|
|
44
1069
|
}
|
|
45
|
-
function
|
|
46
|
-
|
|
47
|
-
return "0ms";
|
|
48
|
-
}
|
|
49
|
-
if (ms < 1e3) {
|
|
50
|
-
return `${Math.floor(ms)}ms`;
|
|
51
|
-
}
|
|
52
|
-
const seconds = ms / 1e3;
|
|
53
|
-
return `${(Math.round(seconds * 10) / 10).toFixed(1)}s`;
|
|
1070
|
+
function formatDuration2(ms) {
|
|
1071
|
+
return formatDuration(ms);
|
|
54
1072
|
}
|
|
55
1073
|
function formatTimestamp(timestamp) {
|
|
56
1074
|
if (!Number.isFinite(timestamp)) {
|
|
@@ -69,6 +1087,10 @@ function formatTimestamp(timestamp) {
|
|
|
69
1087
|
return `${y}-${mo}-${day} ${h}:${min}:${s}`;
|
|
70
1088
|
}
|
|
71
1089
|
function getDefaultTraceDir() {
|
|
1090
|
+
const envDir = process.env.AGENT_INSPECT_TRACE_DIR;
|
|
1091
|
+
if (typeof envDir === "string" && envDir.trim() !== "") {
|
|
1092
|
+
return envDir.trim();
|
|
1093
|
+
}
|
|
72
1094
|
try {
|
|
73
1095
|
const home = os__default.default.homedir();
|
|
74
1096
|
if (typeof home !== "string" || home.trim() === "") {
|
|
@@ -237,7 +1259,7 @@ function runWithStepContext(stepId, fn) {
|
|
|
237
1259
|
});
|
|
238
1260
|
});
|
|
239
1261
|
}
|
|
240
|
-
function
|
|
1262
|
+
function isRecord6(value) {
|
|
241
1263
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
242
1264
|
}
|
|
243
1265
|
function nonEmptyString(value) {
|
|
@@ -248,7 +1270,7 @@ function finiteNumber(value) {
|
|
|
248
1270
|
}
|
|
249
1271
|
function optionalErrorInfo(value) {
|
|
250
1272
|
if (value === void 0) return true;
|
|
251
|
-
if (!
|
|
1273
|
+
if (!isRecord6(value)) return false;
|
|
252
1274
|
if (typeof value.message !== "string") return false;
|
|
253
1275
|
if ("stack" in value && value.stack !== void 0) {
|
|
254
1276
|
if (typeof value.stack !== "string") return false;
|
|
@@ -256,7 +1278,7 @@ function optionalErrorInfo(value) {
|
|
|
256
1278
|
return true;
|
|
257
1279
|
}
|
|
258
1280
|
function validateEvent(event) {
|
|
259
|
-
if (!
|
|
1281
|
+
if (!isRecord6(event)) return false;
|
|
260
1282
|
if (event.schemaVersion !== "0.1") return false;
|
|
261
1283
|
if (!finiteNumber(event.timestamp)) return false;
|
|
262
1284
|
if (typeof event.event !== "string") return false;
|
|
@@ -265,7 +1287,7 @@ function validateEvent(event) {
|
|
|
265
1287
|
if (!nonEmptyString(event.runId) || !nonEmptyString(event.name) || !finiteNumber(event.startTime)) {
|
|
266
1288
|
return false;
|
|
267
1289
|
}
|
|
268
|
-
if (event.metadata !== void 0 && !
|
|
1290
|
+
if (event.metadata !== void 0 && !isRecord6(event.metadata)) {
|
|
269
1291
|
return false;
|
|
270
1292
|
}
|
|
271
1293
|
return true;
|
|
@@ -280,7 +1302,7 @@ function validateEvent(event) {
|
|
|
280
1302
|
if (event.parentId !== void 0 && typeof event.parentId !== "string") {
|
|
281
1303
|
return false;
|
|
282
1304
|
}
|
|
283
|
-
if (event.metadata !== void 0 && !
|
|
1305
|
+
if (event.metadata !== void 0 && !isRecord6(event.metadata)) {
|
|
284
1306
|
return false;
|
|
285
1307
|
}
|
|
286
1308
|
return true;
|
|
@@ -371,39 +1393,872 @@ async function readTraceEvents(runId, traceDir) {
|
|
|
371
1393
|
}
|
|
372
1394
|
return out;
|
|
373
1395
|
}
|
|
374
|
-
|
|
1396
|
+
function resolveTraceDir(options = {}) {
|
|
1397
|
+
if (typeof options.dir === "string" && options.dir.trim() !== "") {
|
|
1398
|
+
return options.dir.trim();
|
|
1399
|
+
}
|
|
1400
|
+
const envDir = process.env.AGENT_INSPECT_TRACE_DIR;
|
|
1401
|
+
if (typeof envDir === "string" && envDir.trim() !== "") {
|
|
1402
|
+
return envDir.trim();
|
|
1403
|
+
}
|
|
1404
|
+
return getDefaultTraceDir();
|
|
1405
|
+
}
|
|
1406
|
+
var TraceDirectory = class {
|
|
1407
|
+
#dir;
|
|
1408
|
+
constructor(options = {}) {
|
|
1409
|
+
this.#dir = resolveTraceDir(options);
|
|
1410
|
+
}
|
|
1411
|
+
getPath(filename) {
|
|
1412
|
+
return filename ? path__default.default.join(this.#dir, filename) : this.#dir;
|
|
1413
|
+
}
|
|
1414
|
+
async list() {
|
|
1415
|
+
try {
|
|
1416
|
+
const files = await promises.readdir(this.#dir);
|
|
1417
|
+
return files.filter((f) => f.endsWith(".jsonl"));
|
|
1418
|
+
} catch (e) {
|
|
1419
|
+
if (e && typeof e === "object" && "code" in e && e.code === "ENOENT") {
|
|
1420
|
+
return [];
|
|
1421
|
+
}
|
|
1422
|
+
throw e;
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
async getFileStats(filename) {
|
|
1426
|
+
return await promises.stat(this.getPath(filename));
|
|
1427
|
+
}
|
|
1428
|
+
};
|
|
1429
|
+
function isFiniteNumber2(v) {
|
|
1430
|
+
return typeof v === "number" && Number.isFinite(v);
|
|
1431
|
+
}
|
|
1432
|
+
function safeParseJson(line) {
|
|
375
1433
|
try {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
1434
|
+
return JSON.parse(line);
|
|
1435
|
+
} catch {
|
|
1436
|
+
return void 0;
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
async function extractMetadata(filePath, _quickScan) {
|
|
1440
|
+
const stats = await promises.stat(filePath);
|
|
1441
|
+
let runIdFromFile = path__default.default.basename(filePath);
|
|
1442
|
+
if (runIdFromFile.endsWith(".jsonl")) {
|
|
1443
|
+
runIdFromFile = runIdFromFile.slice(0, -".jsonl".length);
|
|
1444
|
+
}
|
|
1445
|
+
const raw = await promises.readFile(filePath, "utf-8");
|
|
1446
|
+
const lines = raw.split(/\r?\n/);
|
|
1447
|
+
let eventCount = 0;
|
|
1448
|
+
let runId;
|
|
1449
|
+
let name;
|
|
1450
|
+
let startedAt;
|
|
1451
|
+
let endedAt;
|
|
1452
|
+
let explicitDurationMs;
|
|
1453
|
+
let hasRunStarted = false;
|
|
1454
|
+
let hasRunCompleted = false;
|
|
1455
|
+
let runCompletedStatus;
|
|
1456
|
+
let anyStepError = false;
|
|
1457
|
+
let anyKnownEvent = false;
|
|
1458
|
+
for (const line of lines) {
|
|
1459
|
+
const trimmed = line.trim();
|
|
1460
|
+
if (trimmed === "") continue;
|
|
1461
|
+
const parsed = safeParseJson(trimmed);
|
|
1462
|
+
if (!parsed) continue;
|
|
1463
|
+
if (!isTraceEvent(parsed)) continue;
|
|
1464
|
+
const e = parsed;
|
|
1465
|
+
anyKnownEvent = true;
|
|
1466
|
+
eventCount += 1;
|
|
1467
|
+
if (runId === void 0 && typeof e.runId === "string") {
|
|
1468
|
+
runId = e.runId;
|
|
1469
|
+
}
|
|
1470
|
+
if (e.event === "run_started") {
|
|
1471
|
+
hasRunStarted = true;
|
|
1472
|
+
const rs = e;
|
|
1473
|
+
if (typeof rs.name === "string" && rs.name.trim() !== "") {
|
|
1474
|
+
name = rs.name;
|
|
1475
|
+
}
|
|
1476
|
+
if (isFiniteNumber2(rs.startTime)) {
|
|
1477
|
+
startedAt = rs.startTime;
|
|
1478
|
+
} else if (isFiniteNumber2(rs.timestamp)) {
|
|
1479
|
+
startedAt = rs.timestamp;
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
if (e.event === "run_completed") {
|
|
1483
|
+
hasRunCompleted = true;
|
|
1484
|
+
const rc = e;
|
|
1485
|
+
runCompletedStatus = rc.status;
|
|
1486
|
+
if (isFiniteNumber2(rc.endTime)) endedAt = rc.endTime;
|
|
1487
|
+
else if (isFiniteNumber2(rc.timestamp)) endedAt = rc.timestamp;
|
|
1488
|
+
if (isFiniteNumber2(rc.durationMs)) explicitDurationMs = rc.durationMs;
|
|
1489
|
+
}
|
|
1490
|
+
if (e.event === "step_completed") {
|
|
1491
|
+
const sc = e;
|
|
1492
|
+
if (sc.status === "error") {
|
|
1493
|
+
anyStepError = true;
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
const resolvedRunId = runId ?? runIdFromFile;
|
|
1498
|
+
let status = "unknown";
|
|
1499
|
+
if (hasRunCompleted && (runCompletedStatus === "success" || runCompletedStatus === "error")) {
|
|
1500
|
+
status = runCompletedStatus;
|
|
1501
|
+
} else if (anyStepError) {
|
|
1502
|
+
status = "error";
|
|
1503
|
+
} else if (hasRunStarted && !hasRunCompleted) {
|
|
1504
|
+
status = "running";
|
|
1505
|
+
} else if (anyKnownEvent) {
|
|
1506
|
+
status = "unknown";
|
|
1507
|
+
} else {
|
|
1508
|
+
status = "unknown";
|
|
1509
|
+
}
|
|
1510
|
+
const durationMs = explicitDurationMs ?? (startedAt !== void 0 && endedAt !== void 0 && Number.isFinite(startedAt) && Number.isFinite(endedAt) && endedAt >= startedAt ? endedAt - startedAt : void 0);
|
|
1511
|
+
return {
|
|
1512
|
+
runId: resolvedRunId,
|
|
1513
|
+
name,
|
|
1514
|
+
status,
|
|
1515
|
+
startedAt,
|
|
1516
|
+
endedAt,
|
|
1517
|
+
durationMs,
|
|
1518
|
+
eventCount,
|
|
1519
|
+
filePath,
|
|
1520
|
+
fileSize: stats.size,
|
|
1521
|
+
createdAt: stats.birthtime
|
|
1522
|
+
};
|
|
1523
|
+
}
|
|
1524
|
+
function buildRunSummary(events) {
|
|
1525
|
+
const started = events.find(
|
|
1526
|
+
(e) => e.event === "run_started"
|
|
1527
|
+
);
|
|
1528
|
+
const completed = events.filter(
|
|
1529
|
+
(e) => e.event === "run_completed"
|
|
1530
|
+
);
|
|
1531
|
+
const lastCompleted = completed[completed.length - 1];
|
|
1532
|
+
const runId = started?.runId ?? events.find((e) => typeof e.runId === "string")?.runId ?? "unknown-run";
|
|
1533
|
+
const name = typeof started?.name === "string" && started.name.trim() !== "" ? started.name : void 0;
|
|
1534
|
+
const status = lastCompleted ? lastCompleted.status : started ? "running" : "unknown";
|
|
1535
|
+
const durationMs = lastCompleted && isFiniteNumber2(lastCompleted.durationMs) ? lastCompleted.durationMs : void 0;
|
|
1536
|
+
started && isFiniteNumber2(started.startTime) ? started.startTime : void 0;
|
|
1537
|
+
const steps = /* @__PURE__ */ new Map();
|
|
1538
|
+
for (const e of events) {
|
|
1539
|
+
if (e.event === "step_started") {
|
|
1540
|
+
const s = e;
|
|
1541
|
+
steps.set(s.stepId, {
|
|
1542
|
+
type: s.type,
|
|
1543
|
+
name: s.name,
|
|
1544
|
+
status: "running",
|
|
1545
|
+
parentId: s.parentId,
|
|
1546
|
+
tokensInput: typeof s.metadata?.tokens?.input === "number" ? s.metadata.tokens.input : void 0,
|
|
1547
|
+
tokensOutput: typeof s.metadata?.tokens?.output === "number" ? s.metadata.tokens.output : void 0
|
|
1548
|
+
});
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
for (const e of events) {
|
|
1552
|
+
if (e.event === "step_completed") {
|
|
1553
|
+
const c = e;
|
|
1554
|
+
const existing = steps.get(c.stepId);
|
|
1555
|
+
if (!existing) continue;
|
|
1556
|
+
existing.status = c.status;
|
|
1557
|
+
existing.durationMs = c.durationMs;
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
let totalSteps = 0;
|
|
1561
|
+
let llmSteps = 0;
|
|
1562
|
+
let toolSteps = 0;
|
|
1563
|
+
let logicSteps = 0;
|
|
1564
|
+
let errorSteps = 0;
|
|
1565
|
+
let maxDepth = 0;
|
|
1566
|
+
let longestStep;
|
|
1567
|
+
let totalTokensInput = 0;
|
|
1568
|
+
let totalTokensOutput = 0;
|
|
1569
|
+
let hasAnyTokens = false;
|
|
1570
|
+
const depthCache = /* @__PURE__ */ new Map();
|
|
1571
|
+
const computeDepth = (stepId) => {
|
|
1572
|
+
const cached = depthCache.get(stepId);
|
|
1573
|
+
if (cached !== void 0) return cached;
|
|
1574
|
+
const node = steps.get(stepId);
|
|
1575
|
+
if (!node) return 0;
|
|
1576
|
+
const parent = node.parentId;
|
|
1577
|
+
if (typeof parent !== "string" || parent.trim() === "" || !steps.has(parent)) {
|
|
1578
|
+
depthCache.set(stepId, 0);
|
|
1579
|
+
return 0;
|
|
1580
|
+
}
|
|
1581
|
+
const d = Math.min(1e3, computeDepth(parent) + 1);
|
|
1582
|
+
depthCache.set(stepId, d);
|
|
1583
|
+
return d;
|
|
1584
|
+
};
|
|
1585
|
+
for (const [id, s] of steps.entries()) {
|
|
1586
|
+
totalSteps += 1;
|
|
1587
|
+
if (s.type === "llm") llmSteps += 1;
|
|
1588
|
+
else if (s.type === "tool") toolSteps += 1;
|
|
1589
|
+
else logicSteps += 1;
|
|
1590
|
+
if (s.status === "error") errorSteps += 1;
|
|
1591
|
+
const depth = computeDepth(id);
|
|
1592
|
+
if (depth > maxDepth) maxDepth = depth;
|
|
1593
|
+
if (typeof s.durationMs === "number" && Number.isFinite(s.durationMs)) {
|
|
1594
|
+
if (!longestStep || s.durationMs > longestStep.durationMs) {
|
|
1595
|
+
longestStep = { name: s.name, durationMs: s.durationMs, type: s.type };
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
if (typeof s.tokensInput === "number" || typeof s.tokensOutput === "number") {
|
|
1599
|
+
hasAnyTokens = true;
|
|
1600
|
+
if (typeof s.tokensInput === "number") totalTokensInput += s.tokensInput;
|
|
1601
|
+
if (typeof s.tokensOutput === "number") totalTokensOutput += s.tokensOutput;
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
const summary = {
|
|
1605
|
+
runId,
|
|
1606
|
+
name,
|
|
1607
|
+
status,
|
|
1608
|
+
durationMs,
|
|
1609
|
+
totalSteps,
|
|
1610
|
+
llmSteps,
|
|
1611
|
+
toolSteps,
|
|
1612
|
+
logicSteps,
|
|
1613
|
+
errorSteps,
|
|
1614
|
+
maxDepth,
|
|
1615
|
+
...longestStep ? { longestStep } : {},
|
|
1616
|
+
...hasAnyTokens ? { totalTokens: { input: totalTokensInput, output: totalTokensOutput } } : {}
|
|
1617
|
+
};
|
|
1618
|
+
return summary;
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
// packages/core/src/trace-filter.ts
|
|
1622
|
+
function toLower(s) {
|
|
1623
|
+
return typeof s === "string" ? s.toLowerCase() : "";
|
|
1624
|
+
}
|
|
1625
|
+
function filterTraces(traces, options) {
|
|
1626
|
+
const input3 = [...traces];
|
|
1627
|
+
let out = input3.filter((t) => {
|
|
1628
|
+
if (options.status && t.status !== options.status) return false;
|
|
1629
|
+
if (options.name) {
|
|
1630
|
+
const q = options.name.toLowerCase();
|
|
1631
|
+
const hay = `${toLower(t.name)} ${toLower(t.runId)}`;
|
|
1632
|
+
if (!hay.includes(q)) return false;
|
|
1633
|
+
}
|
|
1634
|
+
if (options.since) {
|
|
1635
|
+
const windowMs = parseDuration(options.since);
|
|
1636
|
+
const cutoff = Date.now() - windowMs;
|
|
1637
|
+
const started = typeof t.startedAt === "number" ? t.startedAt : void 0;
|
|
1638
|
+
const basis = started ?? t.createdAt.getTime();
|
|
1639
|
+
if (!Number.isFinite(basis) || basis < cutoff) return false;
|
|
1640
|
+
}
|
|
1641
|
+
return true;
|
|
1642
|
+
});
|
|
1643
|
+
out.sort((a, b) => {
|
|
1644
|
+
const aTime = (typeof a.startedAt === "number" ? a.startedAt : void 0) ?? a.createdAt.getTime();
|
|
1645
|
+
const bTime = (typeof b.startedAt === "number" ? b.startedAt : void 0) ?? b.createdAt.getTime();
|
|
1646
|
+
return bTime - aTime;
|
|
1647
|
+
});
|
|
1648
|
+
if (typeof options.limit === "number" && Number.isFinite(options.limit)) {
|
|
1649
|
+
const n = Math.max(0, Math.floor(options.limit));
|
|
1650
|
+
out = out.slice(0, n);
|
|
1651
|
+
}
|
|
1652
|
+
return out;
|
|
1653
|
+
}
|
|
1654
|
+
var KNOWN_EVENTS = /* @__PURE__ */ new Set([
|
|
1655
|
+
"run_started",
|
|
1656
|
+
"run_completed",
|
|
1657
|
+
"step_started",
|
|
1658
|
+
"step_completed"
|
|
1659
|
+
]);
|
|
1660
|
+
function isRecord7(value) {
|
|
1661
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1662
|
+
}
|
|
1663
|
+
function safeParse(line) {
|
|
1664
|
+
try {
|
|
1665
|
+
return JSON.parse(line);
|
|
1666
|
+
} catch {
|
|
1667
|
+
return void 0;
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
async function isAgentInspectTrace(filePath) {
|
|
1671
|
+
try {
|
|
1672
|
+
const rl = readline.createInterface({
|
|
1673
|
+
input: fs.createReadStream(filePath, { encoding: "utf8" }),
|
|
1674
|
+
crlfDelay: Infinity
|
|
392
1675
|
});
|
|
393
|
-
|
|
1676
|
+
let checked = 0;
|
|
1677
|
+
for await (const line of rl) {
|
|
1678
|
+
const trimmed = line.trim();
|
|
1679
|
+
if (trimmed === "") continue;
|
|
1680
|
+
const parsed = safeParse(trimmed);
|
|
1681
|
+
if (!parsed) continue;
|
|
1682
|
+
if (!isRecord7(parsed)) continue;
|
|
1683
|
+
checked += 1;
|
|
1684
|
+
if (isTraceEvent(parsed)) return true;
|
|
1685
|
+
const ev = parsed.event;
|
|
1686
|
+
const runId = parsed.runId;
|
|
1687
|
+
if (typeof ev === "string" && KNOWN_EVENTS.has(ev) && typeof runId === "string") {
|
|
1688
|
+
return true;
|
|
1689
|
+
}
|
|
1690
|
+
if (checked >= 20) break;
|
|
1691
|
+
}
|
|
1692
|
+
return false;
|
|
394
1693
|
} catch {
|
|
395
|
-
return
|
|
1694
|
+
return false;
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
// packages/core/src/diff/comparable.ts
|
|
1699
|
+
function extractOutputPreview(meta) {
|
|
1700
|
+
if (meta === void 0) return void 0;
|
|
1701
|
+
if ("outputPreview" in meta) return meta.outputPreview;
|
|
1702
|
+
if ("resultPreview" in meta) return meta.resultPreview;
|
|
1703
|
+
return void 0;
|
|
1704
|
+
}
|
|
1705
|
+
function mapStepStatus(s) {
|
|
1706
|
+
if (s === void 0) return "running";
|
|
1707
|
+
return s;
|
|
1708
|
+
}
|
|
1709
|
+
function manualTraceEventsToComparableRun(events) {
|
|
1710
|
+
const started = events.find((e) => e.event === "run_started");
|
|
1711
|
+
if (!started || started.event !== "run_started") {
|
|
1712
|
+
throw new Error("Invalid trace: missing run_started");
|
|
1713
|
+
}
|
|
1714
|
+
const rs = started;
|
|
1715
|
+
const runId = rs.runId;
|
|
1716
|
+
const completedAll = events.filter((e) => e.event === "run_completed");
|
|
1717
|
+
const lastCompleted = completedAll[completedAll.length - 1];
|
|
1718
|
+
let runStatus;
|
|
1719
|
+
if (lastCompleted === void 0) runStatus = "running";
|
|
1720
|
+
else runStatus = lastCompleted.status;
|
|
1721
|
+
const durationMs = lastCompleted !== void 0 && Number.isFinite(lastCompleted.durationMs) ? lastCompleted.durationMs : void 0;
|
|
1722
|
+
const steps = /* @__PURE__ */ new Map();
|
|
1723
|
+
let order = 0;
|
|
1724
|
+
for (const e of events) {
|
|
1725
|
+
if (e.event !== "step_started") continue;
|
|
1726
|
+
const s = e;
|
|
1727
|
+
const meta = s.metadata ? { ...s.metadata } : void 0;
|
|
1728
|
+
steps.set(s.stepId, {
|
|
1729
|
+
id: s.stepId,
|
|
1730
|
+
parentId: s.parentId,
|
|
1731
|
+
name: s.name,
|
|
1732
|
+
type: s.type,
|
|
1733
|
+
order: order++,
|
|
1734
|
+
timestamp: s.timestamp,
|
|
1735
|
+
metadata: meta
|
|
1736
|
+
});
|
|
1737
|
+
}
|
|
1738
|
+
for (const e of events) {
|
|
1739
|
+
if (e.event !== "step_completed") continue;
|
|
1740
|
+
const acc = steps.get(e.stepId);
|
|
1741
|
+
if (!acc) continue;
|
|
1742
|
+
acc.status = e.status;
|
|
1743
|
+
acc.durationMs = e.durationMs;
|
|
1744
|
+
if (e.error?.message) acc.errorMsg = e.error.message;
|
|
1745
|
+
const extra = e;
|
|
1746
|
+
if (extra.metadata !== void 0 && typeof extra.metadata === "object") {
|
|
1747
|
+
acc.metadata = { ...acc.metadata ?? {}, ...extra.metadata };
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
1751
|
+
for (const acc of steps.values()) {
|
|
1752
|
+
let meta = acc.metadata ? { ...acc.metadata } : void 0;
|
|
1753
|
+
if (acc.parentId !== void 0 && !steps.has(acc.parentId)) {
|
|
1754
|
+
meta = { ...meta ?? {}, agent_inspect_diff_parent_missing: true };
|
|
1755
|
+
}
|
|
1756
|
+
const outputPreview = extractOutputPreview(meta);
|
|
1757
|
+
const sc = {
|
|
1758
|
+
id: acc.id,
|
|
1759
|
+
name: acc.name,
|
|
1760
|
+
type: acc.type,
|
|
1761
|
+
status: mapStepStatus(acc.status),
|
|
1762
|
+
durationMs: acc.durationMs,
|
|
1763
|
+
error: acc.errorMsg,
|
|
1764
|
+
metadata: meta && Object.keys(meta).length > 0 ? meta : void 0,
|
|
1765
|
+
outputPreview,
|
|
1766
|
+
children: []
|
|
1767
|
+
};
|
|
1768
|
+
nodes.set(acc.id, sc);
|
|
1769
|
+
}
|
|
1770
|
+
const roots = [];
|
|
1771
|
+
const sortByOrder = (a, b) => {
|
|
1772
|
+
const oa = steps.get(a.id)?.order ?? 0;
|
|
1773
|
+
const ob = steps.get(b.id)?.order ?? 0;
|
|
1774
|
+
return oa - ob;
|
|
1775
|
+
};
|
|
1776
|
+
for (const acc of steps.values()) {
|
|
1777
|
+
const node = nodes.get(acc.id);
|
|
1778
|
+
if (acc.parentId !== void 0 && nodes.has(acc.parentId)) {
|
|
1779
|
+
nodes.get(acc.parentId).children.push(node);
|
|
1780
|
+
} else {
|
|
1781
|
+
roots.push(node);
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
roots.sort(sortByOrder);
|
|
1785
|
+
for (const n of nodes.values()) {
|
|
1786
|
+
n.children.sort(sortByOrder);
|
|
1787
|
+
}
|
|
1788
|
+
return {
|
|
1789
|
+
runId,
|
|
1790
|
+
name: rs.name,
|
|
1791
|
+
status: runStatus,
|
|
1792
|
+
durationMs,
|
|
1793
|
+
steps: roots
|
|
1794
|
+
};
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
// packages/core/src/exporters/helpers.ts
|
|
1798
|
+
var REDACT_SUBSTRINGS = [
|
|
1799
|
+
"authorization",
|
|
1800
|
+
"cookie",
|
|
1801
|
+
"token",
|
|
1802
|
+
"apikey",
|
|
1803
|
+
"password",
|
|
1804
|
+
"secret",
|
|
1805
|
+
"email"
|
|
1806
|
+
];
|
|
1807
|
+
function shouldRedactKey(key) {
|
|
1808
|
+
const k = key.toLowerCase();
|
|
1809
|
+
for (const s of REDACT_SUBSTRINGS) {
|
|
1810
|
+
if (k.includes(s)) return true;
|
|
1811
|
+
}
|
|
1812
|
+
return false;
|
|
1813
|
+
}
|
|
1814
|
+
function safeString2(value, maxLength) {
|
|
1815
|
+
if (value === null || value === void 0) return "";
|
|
1816
|
+
let s;
|
|
1817
|
+
if (typeof value === "string") s = value;
|
|
1818
|
+
else if (typeof value === "number" || typeof value === "boolean") s = String(value);
|
|
1819
|
+
else s = stableJson(value, false);
|
|
1820
|
+
if (maxLength !== void 0 && maxLength >= 0 && s.length > maxLength) {
|
|
1821
|
+
return `${s.slice(0, maxLength)}\u2026`;
|
|
1822
|
+
}
|
|
1823
|
+
return s;
|
|
1824
|
+
}
|
|
1825
|
+
function escapeMarkdown(value) {
|
|
1826
|
+
return value.replace(/\|/g, "\\|").replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\n/g, " ");
|
|
1827
|
+
}
|
|
1828
|
+
function escapeHtml(value) {
|
|
1829
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1830
|
+
}
|
|
1831
|
+
function sortKeysDeep(input3) {
|
|
1832
|
+
if (input3 === null || typeof input3 !== "object") return input3;
|
|
1833
|
+
if (Array.isArray(input3)) return input3.map(sortKeysDeep);
|
|
1834
|
+
const o = input3;
|
|
1835
|
+
const out = {};
|
|
1836
|
+
for (const k of Object.keys(o).sort()) {
|
|
1837
|
+
out[k] = sortKeysDeep(o[k]);
|
|
1838
|
+
}
|
|
1839
|
+
return out;
|
|
1840
|
+
}
|
|
1841
|
+
function stableJson(value, pretty) {
|
|
1842
|
+
const sorted = sortKeysDeep(value);
|
|
1843
|
+
return pretty === true ? JSON.stringify(sorted, null, 2) : JSON.stringify(sorted);
|
|
1844
|
+
}
|
|
1845
|
+
function compactAttributes(attrs, options) {
|
|
1846
|
+
if (attrs === void 0) return {};
|
|
1847
|
+
const maxLen = options?.maxLength ?? 500;
|
|
1848
|
+
const redacted = options?.redacted ?? true;
|
|
1849
|
+
const out = {};
|
|
1850
|
+
for (const key of Object.keys(attrs).sort()) {
|
|
1851
|
+
if (redacted && shouldRedactKey(key)) {
|
|
1852
|
+
out[key] = "[REDACTED]";
|
|
1853
|
+
continue;
|
|
1854
|
+
}
|
|
1855
|
+
const v = attrs[key];
|
|
1856
|
+
out[key] = compactValue(v, maxLen, redacted);
|
|
1857
|
+
}
|
|
1858
|
+
return out;
|
|
1859
|
+
}
|
|
1860
|
+
function compactValue(value, maxLen, redacted) {
|
|
1861
|
+
if (value === null || typeof value !== "object") {
|
|
1862
|
+
return typeof value === "string" ? safeString2(value, maxLen) : value;
|
|
1863
|
+
}
|
|
1864
|
+
if (Array.isArray(value)) {
|
|
1865
|
+
const arr = value.slice(0, 20).map((x) => compactValue(x, maxLen, redacted));
|
|
1866
|
+
if (value.length > 20) arr.push(`\u2026(+${value.length - 20} more)`);
|
|
1867
|
+
return arr;
|
|
1868
|
+
}
|
|
1869
|
+
const o = value;
|
|
1870
|
+
const inner = {};
|
|
1871
|
+
for (const k of Object.keys(o)) {
|
|
1872
|
+
if (redacted && shouldRedactKey(k)) inner[k] = "[REDACTED]";
|
|
1873
|
+
else inner[k] = compactValue(o[k], maxLen, redacted);
|
|
1874
|
+
}
|
|
1875
|
+
return inner;
|
|
1876
|
+
}
|
|
1877
|
+
function flattenTree(tree) {
|
|
1878
|
+
const out = [];
|
|
1879
|
+
function walk(nodes) {
|
|
1880
|
+
for (const n of nodes) {
|
|
1881
|
+
out.push(n);
|
|
1882
|
+
if (n.children.length > 0) walk(n.children);
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
walk(tree.children);
|
|
1886
|
+
return out;
|
|
1887
|
+
}
|
|
1888
|
+
function zeroKinds() {
|
|
1889
|
+
return {
|
|
1890
|
+
RUN: 0,
|
|
1891
|
+
AGENT: 0,
|
|
1892
|
+
LLM: 0,
|
|
1893
|
+
TOOL: 0,
|
|
1894
|
+
CHAIN: 0,
|
|
1895
|
+
RETRIEVER: 0,
|
|
1896
|
+
DECISION: 0,
|
|
1897
|
+
RESULT: 0,
|
|
1898
|
+
ERROR: 0,
|
|
1899
|
+
LOGIC: 0,
|
|
1900
|
+
LOG: 0
|
|
1901
|
+
};
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
// packages/core/src/diff/engine.ts
|
|
1905
|
+
var DEFAULT_THRESHOLD_MS = 0;
|
|
1906
|
+
function pathSeg(step2, index) {
|
|
1907
|
+
return { index, name: step2.name, stepId: step2.id };
|
|
1908
|
+
}
|
|
1909
|
+
function buildPath(segments) {
|
|
1910
|
+
return { path: [...segments] };
|
|
1911
|
+
}
|
|
1912
|
+
function pairSteps(left, right) {
|
|
1913
|
+
const usedRight = /* @__PURE__ */ new Set();
|
|
1914
|
+
const pairs = [];
|
|
1915
|
+
for (let i = 0; i < left.length; i++) {
|
|
1916
|
+
const L = left[i];
|
|
1917
|
+
let R = right.find((r) => !usedRight.has(r.id) && r.id === L.id);
|
|
1918
|
+
if (R === void 0 && i < right.length && !usedRight.has(right[i].id)) {
|
|
1919
|
+
const cand = right[i];
|
|
1920
|
+
if (cand.name === L.name && (cand.type ?? "") === (L.type ?? "")) {
|
|
1921
|
+
R = cand;
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
if (R === void 0) {
|
|
1925
|
+
R = right.find(
|
|
1926
|
+
(r) => !usedRight.has(r.id) && r.name === L.name && (r.type ?? "") === (L.type ?? "")
|
|
1927
|
+
);
|
|
1928
|
+
}
|
|
1929
|
+
if (R !== void 0) {
|
|
1930
|
+
usedRight.add(R.id);
|
|
1931
|
+
pairs.push([L, R]);
|
|
1932
|
+
} else {
|
|
1933
|
+
pairs.push([L, void 0]);
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
for (const R of right) {
|
|
1937
|
+
if (!usedRight.has(R.id)) {
|
|
1938
|
+
pairs.push([void 0, R]);
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
return pairs;
|
|
1942
|
+
}
|
|
1943
|
+
function compareLeafSteps(L, R, segments, opts, out) {
|
|
1944
|
+
const path7 = buildPath(segments);
|
|
1945
|
+
if (L.name !== R.name) {
|
|
1946
|
+
out.push({
|
|
1947
|
+
kind: "structure",
|
|
1948
|
+
severity: "warning",
|
|
1949
|
+
message: "Step name differs",
|
|
1950
|
+
path: path7,
|
|
1951
|
+
left: L.name,
|
|
1952
|
+
right: R.name
|
|
1953
|
+
});
|
|
1954
|
+
}
|
|
1955
|
+
if ((L.type ?? "") !== (R.type ?? "")) {
|
|
1956
|
+
out.push({
|
|
1957
|
+
kind: "step-type",
|
|
1958
|
+
severity: "warning",
|
|
1959
|
+
message: "Step type differs",
|
|
1960
|
+
path: path7,
|
|
1961
|
+
left: L.type,
|
|
1962
|
+
right: R.type
|
|
1963
|
+
});
|
|
1964
|
+
}
|
|
1965
|
+
if ((L.status ?? "") !== (R.status ?? "")) {
|
|
1966
|
+
out.push({
|
|
1967
|
+
kind: "step-status",
|
|
1968
|
+
severity: "warning",
|
|
1969
|
+
message: "Step status differs",
|
|
1970
|
+
path: path7,
|
|
1971
|
+
left: L.status,
|
|
1972
|
+
right: R.status
|
|
1973
|
+
});
|
|
1974
|
+
}
|
|
1975
|
+
const le = L.error ?? "";
|
|
1976
|
+
const re = R.error ?? "";
|
|
1977
|
+
if (le !== re) {
|
|
1978
|
+
out.push({
|
|
1979
|
+
kind: "error",
|
|
1980
|
+
severity: "error",
|
|
1981
|
+
message: "Step error message differs",
|
|
1982
|
+
path: path7,
|
|
1983
|
+
left: le || void 0,
|
|
1984
|
+
right: re || void 0
|
|
1985
|
+
});
|
|
1986
|
+
}
|
|
1987
|
+
if (!opts.ignoreDuration) {
|
|
1988
|
+
const ld = L.durationMs;
|
|
1989
|
+
const rd = R.durationMs;
|
|
1990
|
+
const th = opts.durationThresholdMs;
|
|
1991
|
+
let differs = false;
|
|
1992
|
+
if (ld === void 0 && rd === void 0) differs = false;
|
|
1993
|
+
else if (ld === void 0 || rd === void 0) differs = true;
|
|
1994
|
+
else differs = Math.abs(ld - rd) > th;
|
|
1995
|
+
if (differs) {
|
|
1996
|
+
out.push({
|
|
1997
|
+
kind: "duration",
|
|
1998
|
+
severity: "info",
|
|
1999
|
+
message: "Step duration differs",
|
|
2000
|
+
path: path7,
|
|
2001
|
+
left: ld,
|
|
2002
|
+
right: rd
|
|
2003
|
+
});
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
const lm = stableJson(L.metadata ?? {});
|
|
2007
|
+
const rm = stableJson(R.metadata ?? {});
|
|
2008
|
+
if (lm !== rm) {
|
|
2009
|
+
out.push({
|
|
2010
|
+
kind: "metadata",
|
|
2011
|
+
severity: "info",
|
|
2012
|
+
message: "Step metadata differs",
|
|
2013
|
+
path: path7,
|
|
2014
|
+
left: L.metadata,
|
|
2015
|
+
right: R.metadata
|
|
2016
|
+
});
|
|
2017
|
+
}
|
|
2018
|
+
const lo = stableJson(L.outputPreview ?? null);
|
|
2019
|
+
const ro = stableJson(R.outputPreview ?? null);
|
|
2020
|
+
if (lo !== ro) {
|
|
2021
|
+
out.push({
|
|
2022
|
+
kind: "output",
|
|
2023
|
+
severity: "info",
|
|
2024
|
+
message: "Output preview differs",
|
|
2025
|
+
path: path7,
|
|
2026
|
+
left: L.outputPreview,
|
|
2027
|
+
right: R.outputPreview
|
|
2028
|
+
});
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
function compareRecursive(L, R, segments, opts, out) {
|
|
2032
|
+
compareLeafSteps(L, R, segments, opts, out);
|
|
2033
|
+
const pairs = pairSteps(L.children, R.children);
|
|
2034
|
+
let ci = 0;
|
|
2035
|
+
for (const [lch, rch] of pairs) {
|
|
2036
|
+
if (lch !== void 0 && rch !== void 0) {
|
|
2037
|
+
compareRecursive(lch, rch, [...segments, pathSeg(lch, ci)], opts, out);
|
|
2038
|
+
} else if (lch !== void 0) {
|
|
2039
|
+
out.push({
|
|
2040
|
+
kind: "step-removed",
|
|
2041
|
+
severity: "warning",
|
|
2042
|
+
message: `Step only in left run: ${lch.name}`,
|
|
2043
|
+
path: buildPath([...segments, pathSeg(lch, ci)]),
|
|
2044
|
+
left: lch.id,
|
|
2045
|
+
right: void 0
|
|
2046
|
+
});
|
|
2047
|
+
} else if (rch !== void 0) {
|
|
2048
|
+
out.push({
|
|
2049
|
+
kind: "step-added",
|
|
2050
|
+
severity: "warning",
|
|
2051
|
+
message: `Step only in right run: ${rch.name}`,
|
|
2052
|
+
path: buildPath([...segments, pathSeg(rch, ci)]),
|
|
2053
|
+
left: void 0,
|
|
2054
|
+
right: rch.id
|
|
2055
|
+
});
|
|
2056
|
+
}
|
|
2057
|
+
ci += 1;
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
function mergeDiffDefaults(options) {
|
|
2061
|
+
return {
|
|
2062
|
+
ignoreDuration: options?.ignoreDuration ?? false,
|
|
2063
|
+
durationThresholdMs: options?.durationThresholdMs !== void 0 ? options.durationThresholdMs : DEFAULT_THRESHOLD_MS,
|
|
2064
|
+
focus: options?.focus ?? "all",
|
|
2065
|
+
check: options?.check ?? "all"
|
|
2066
|
+
};
|
|
2067
|
+
}
|
|
2068
|
+
function kindMatchesFilter(kind, merged) {
|
|
2069
|
+
const { focus, check } = merged;
|
|
2070
|
+
if (check !== "all") {
|
|
2071
|
+
if (check === "structure") {
|
|
2072
|
+
if (!["step-added", "step-removed", "structure", "step-type"].includes(kind)) return false;
|
|
2073
|
+
} else if (check === "outputs") {
|
|
2074
|
+
if (!["metadata", "output"].includes(kind)) return false;
|
|
2075
|
+
} else if (check === "errors") {
|
|
2076
|
+
if (!["run-status", "step-status", "error"].includes(kind)) return false;
|
|
2077
|
+
} else if (check === "timing") {
|
|
2078
|
+
if (kind !== "duration") return false;
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
if (focus !== "all") {
|
|
2082
|
+
if (focus === "errors") {
|
|
2083
|
+
if (!["run-status", "step-status", "error"].includes(kind)) return false;
|
|
2084
|
+
} else if (focus === "structure") {
|
|
2085
|
+
if (!["step-added", "step-removed", "structure", "step-type"].includes(kind)) return false;
|
|
2086
|
+
} else if (focus === "outputs") {
|
|
2087
|
+
if (!["metadata", "output"].includes(kind)) return false;
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
return true;
|
|
2091
|
+
}
|
|
2092
|
+
function diffRuns(left, right, options) {
|
|
2093
|
+
const merged = mergeDiffDefaults(options);
|
|
2094
|
+
const opts = {
|
|
2095
|
+
ignoreDuration: merged.ignoreDuration,
|
|
2096
|
+
durationThresholdMs: merged.durationThresholdMs
|
|
2097
|
+
};
|
|
2098
|
+
const raw = [];
|
|
2099
|
+
if ((left.status ?? "") !== (right.status ?? "")) {
|
|
2100
|
+
raw.push({
|
|
2101
|
+
kind: "run-status",
|
|
2102
|
+
severity: "warning",
|
|
2103
|
+
message: "Run completion status differs",
|
|
2104
|
+
left: left.status,
|
|
2105
|
+
right: right.status
|
|
2106
|
+
});
|
|
2107
|
+
}
|
|
2108
|
+
if (!merged.ignoreDuration) {
|
|
2109
|
+
const ld = left.durationMs;
|
|
2110
|
+
const rd = right.durationMs;
|
|
2111
|
+
const th = merged.durationThresholdMs;
|
|
2112
|
+
let differs = false;
|
|
2113
|
+
if (ld === void 0 && rd === void 0) differs = false;
|
|
2114
|
+
else if (ld === void 0 || rd === void 0) differs = true;
|
|
2115
|
+
else differs = Math.abs(ld - rd) > th;
|
|
2116
|
+
if (differs) {
|
|
2117
|
+
raw.push({
|
|
2118
|
+
kind: "duration",
|
|
2119
|
+
severity: "info",
|
|
2120
|
+
message: "Run duration differs",
|
|
2121
|
+
left: ld,
|
|
2122
|
+
right: rd
|
|
2123
|
+
});
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
const pairs = pairSteps(left.steps, right.steps);
|
|
2127
|
+
let idx = 0;
|
|
2128
|
+
for (const [ls, rs] of pairs) {
|
|
2129
|
+
if (ls !== void 0 && rs !== void 0) {
|
|
2130
|
+
compareRecursive(ls, rs, [pathSeg(ls, idx)], opts, raw);
|
|
2131
|
+
idx += 1;
|
|
2132
|
+
} else if (ls !== void 0) {
|
|
2133
|
+
raw.push({
|
|
2134
|
+
kind: "step-removed",
|
|
2135
|
+
severity: "warning",
|
|
2136
|
+
message: `Step only in left run: ${ls.name}`,
|
|
2137
|
+
path: buildPath([pathSeg(ls, idx)]),
|
|
2138
|
+
left: ls.id,
|
|
2139
|
+
right: void 0
|
|
2140
|
+
});
|
|
2141
|
+
idx += 1;
|
|
2142
|
+
} else if (rs !== void 0) {
|
|
2143
|
+
raw.push({
|
|
2144
|
+
kind: "step-added",
|
|
2145
|
+
severity: "warning",
|
|
2146
|
+
message: `Step only in right run: ${rs.name}`,
|
|
2147
|
+
path: buildPath([pathSeg(rs, idx)]),
|
|
2148
|
+
left: void 0,
|
|
2149
|
+
right: rs.id
|
|
2150
|
+
});
|
|
2151
|
+
idx += 1;
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
const differences = raw.filter((d) => kindMatchesFilter(d.kind, merged));
|
|
2155
|
+
let errors = 0;
|
|
2156
|
+
let warnings = 0;
|
|
2157
|
+
let info = 0;
|
|
2158
|
+
for (const d of differences) {
|
|
2159
|
+
if (d.severity === "error") errors += 1;
|
|
2160
|
+
else if (d.severity === "warning") warnings += 1;
|
|
2161
|
+
else info += 1;
|
|
2162
|
+
}
|
|
2163
|
+
const firstVisible = differences[0];
|
|
2164
|
+
const firstDivergence = firstVisible !== void 0 ? {
|
|
2165
|
+
kind: "first-divergence",
|
|
2166
|
+
severity: firstVisible.severity,
|
|
2167
|
+
message: `First divergence: ${firstVisible.message}`,
|
|
2168
|
+
path: firstVisible.path,
|
|
2169
|
+
left: firstVisible.left,
|
|
2170
|
+
right: firstVisible.right
|
|
2171
|
+
} : void 0;
|
|
2172
|
+
const summary = {
|
|
2173
|
+
leftRunId: left.runId,
|
|
2174
|
+
rightRunId: right.runId,
|
|
2175
|
+
totalDifferences: differences.length,
|
|
2176
|
+
errors,
|
|
2177
|
+
warnings,
|
|
2178
|
+
info,
|
|
2179
|
+
firstDivergence
|
|
2180
|
+
};
|
|
2181
|
+
return { summary, differences };
|
|
2182
|
+
}
|
|
2183
|
+
function formatPath(path7) {
|
|
2184
|
+
if (path7 === void 0 || path7.path.length === 0) {
|
|
2185
|
+
return "(run)";
|
|
396
2186
|
}
|
|
2187
|
+
return path7.path.map((s) => s.name).join(" > ");
|
|
397
2188
|
}
|
|
398
|
-
function
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
2189
|
+
function formatValue(v, verbose) {
|
|
2190
|
+
if (v === void 0) return "(undefined)";
|
|
2191
|
+
if (typeof v === "string") return v;
|
|
2192
|
+
if (typeof v === "number" || typeof v === "boolean") return String(v);
|
|
2193
|
+
const s = JSON.stringify(v);
|
|
2194
|
+
if (verbose || s.length <= 120) return s;
|
|
2195
|
+
return `${s.slice(0, 117)}...`;
|
|
2196
|
+
}
|
|
2197
|
+
function renderRunDiff(result, options) {
|
|
2198
|
+
const verbose = options?.verbose === true;
|
|
2199
|
+
const sev = (s, level) => {
|
|
2200
|
+
return s;
|
|
2201
|
+
};
|
|
2202
|
+
const lines = [];
|
|
2203
|
+
const { summary } = result;
|
|
2204
|
+
lines.push("Run diff");
|
|
2205
|
+
lines.push(`Left: ${summary.leftRunId}`);
|
|
2206
|
+
lines.push(`Right: ${summary.rightRunId}`);
|
|
2207
|
+
lines.push("");
|
|
2208
|
+
lines.push("Summary:");
|
|
2209
|
+
lines.push(` Differences: ${summary.totalDifferences}`);
|
|
2210
|
+
lines.push(` Errors: ${summary.errors}`);
|
|
2211
|
+
lines.push(` Warnings: ${summary.warnings}`);
|
|
2212
|
+
lines.push(` Info: ${summary.info}`);
|
|
2213
|
+
lines.push("");
|
|
2214
|
+
const fd = summary.firstDivergence;
|
|
2215
|
+
const firstKind = result.differences[0]?.kind;
|
|
2216
|
+
if (fd !== void 0) {
|
|
2217
|
+
lines.push("First divergence:");
|
|
2218
|
+
const where = formatPath(fd.path);
|
|
2219
|
+
const displayKind = firstKind ?? fd.kind;
|
|
2220
|
+
lines.push(` ${displayKind} at ${where}`);
|
|
2221
|
+
if (fd.left !== void 0 || fd.right !== void 0) {
|
|
2222
|
+
lines.push(` left: ${formatValue(fd.left, verbose)}`);
|
|
2223
|
+
lines.push(` right: ${formatValue(fd.right, verbose)}`);
|
|
2224
|
+
}
|
|
2225
|
+
lines.push("");
|
|
2226
|
+
}
|
|
2227
|
+
lines.push("Differences:");
|
|
2228
|
+
if (result.differences.length === 0) {
|
|
2229
|
+
lines.push(" (none)");
|
|
2230
|
+
return lines.join("\n");
|
|
2231
|
+
}
|
|
2232
|
+
const showSides = (kind) => verbose || [
|
|
2233
|
+
"run-status",
|
|
2234
|
+
"step-status",
|
|
2235
|
+
"error",
|
|
2236
|
+
"duration",
|
|
2237
|
+
"step-type",
|
|
2238
|
+
"structure",
|
|
2239
|
+
"step-added",
|
|
2240
|
+
"step-removed"
|
|
2241
|
+
].includes(kind);
|
|
2242
|
+
for (const d of result.differences) {
|
|
2243
|
+
const tag = sev(`[${d.severity}]`, d.severity);
|
|
2244
|
+
const pathStr = d.path !== void 0 ? ` ${formatPath(d.path)}` : "";
|
|
2245
|
+
lines.push(` ${tag} ${d.kind}${pathStr}`);
|
|
2246
|
+
lines.push(` ${d.message}`);
|
|
2247
|
+
if (d.left !== void 0 || d.right !== void 0) {
|
|
2248
|
+
if (showSides(d.kind)) {
|
|
2249
|
+
lines.push(` left: ${formatValue(d.left, verbose)}`);
|
|
2250
|
+
lines.push(` right: ${formatValue(d.right, verbose)}`);
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
406
2253
|
}
|
|
2254
|
+
return lines.join("\n");
|
|
2255
|
+
}
|
|
2256
|
+
|
|
2257
|
+
// packages/core/src/diff/index.ts
|
|
2258
|
+
function diffTraceEvents(leftEvents, rightEvents, options) {
|
|
2259
|
+
const left = manualTraceEventsToComparableRun(leftEvents);
|
|
2260
|
+
const right = manualTraceEventsToComparableRun(rightEvents);
|
|
2261
|
+
return diffRuns(left, right, options);
|
|
407
2262
|
}
|
|
408
2263
|
var TERMINAL_INDENT = " ";
|
|
409
2264
|
var MAX_TERMINAL_NAME_LENGTH = 80;
|
|
@@ -430,24 +2285,24 @@ function formatTerminalName(name) {
|
|
|
430
2285
|
return truncateName(name, MAX_TERMINAL_NAME_LENGTH);
|
|
431
2286
|
}
|
|
432
2287
|
function getStatusIcon(status) {
|
|
433
|
-
if (status === "success") return
|
|
434
|
-
if (status === "error") return
|
|
435
|
-
return
|
|
2288
|
+
if (status === "success") return chalk2__default.default.green("\u2714");
|
|
2289
|
+
if (status === "error") return chalk2__default.default.red("\u2716");
|
|
2290
|
+
return chalk2__default.default.yellow("\u23F3");
|
|
436
2291
|
}
|
|
437
2292
|
function renderStepLine(name, durationMs, status, depth) {
|
|
438
2293
|
try {
|
|
439
2294
|
const nm = formatTerminalName(name);
|
|
440
2295
|
const ind = getIndent(depth ?? 0);
|
|
441
2296
|
if (status === "running" && durationMs === void 0) {
|
|
442
|
-
return `${ind}${
|
|
2297
|
+
return `${ind}${chalk2__default.default.yellow("\u23F3")} ${nm}`;
|
|
443
2298
|
}
|
|
444
2299
|
const hasDur = durationMs !== void 0 && Number.isFinite(durationMs);
|
|
445
|
-
const dur = hasDur ?
|
|
2300
|
+
const dur = hasDur ? formatDuration2(durationMs) : void 0;
|
|
446
2301
|
if (status === "running") {
|
|
447
|
-
return dur !== void 0 ? `${ind}${
|
|
2302
|
+
return dur !== void 0 ? `${ind}${chalk2__default.default.yellow("\u23F3")} ${nm} (${dur})` : `${ind}${chalk2__default.default.yellow("\u23F3")} ${nm}`;
|
|
448
2303
|
}
|
|
449
2304
|
if (!hasDur || dur === void 0) {
|
|
450
|
-
return `${ind}${
|
|
2305
|
+
return `${ind}${chalk2__default.default.yellow("\u23F3")} ${nm}`;
|
|
451
2306
|
}
|
|
452
2307
|
if (status === "success") {
|
|
453
2308
|
return `${ind}${getStatusIcon("success")} ${nm} (${dur})`;
|
|
@@ -616,6 +2471,773 @@ Object.assign(stepImpl, {
|
|
|
616
2471
|
tool: stepTool
|
|
617
2472
|
});
|
|
618
2473
|
|
|
2474
|
+
// packages/core/src/exporters/types.ts
|
|
2475
|
+
var EXPORT_PAYLOAD_VERSION = "0.1.2";
|
|
2476
|
+
|
|
2477
|
+
// packages/core/src/exporters/html-exporter.ts
|
|
2478
|
+
function renderTreeHtml(nodes, ulClass = "tree") {
|
|
2479
|
+
if (nodes.length === 0) return "";
|
|
2480
|
+
const parts = [`<ul class="${ulClass}">`];
|
|
2481
|
+
for (const n of nodes) {
|
|
2482
|
+
const ev = n.event;
|
|
2483
|
+
const status = ev.status ?? "?";
|
|
2484
|
+
const dur = ev.durationMs !== void 0 && Number.isFinite(ev.durationMs) ? `${ev.durationMs}ms` : "-";
|
|
2485
|
+
parts.push("<li>");
|
|
2486
|
+
parts.push(
|
|
2487
|
+
`<span class="nm">${escapeHtml(ev.name)}</span> <span class="meta">[${escapeHtml(ev.kind)}] ${escapeHtml(status)} (${escapeHtml(dur)})</span>`
|
|
2488
|
+
);
|
|
2489
|
+
if (n.children.length > 0) {
|
|
2490
|
+
parts.push(renderTreeHtml(n.children, "tree nested"));
|
|
2491
|
+
}
|
|
2492
|
+
parts.push("</li>");
|
|
2493
|
+
}
|
|
2494
|
+
parts.push("</ul>");
|
|
2495
|
+
return parts.join("");
|
|
2496
|
+
}
|
|
2497
|
+
function exportHtml(tree, options) {
|
|
2498
|
+
const warnings = [];
|
|
2499
|
+
const includeMetadata = options?.includeMetadata ?? true;
|
|
2500
|
+
const includeAttributes = options?.includeAttributes ?? false;
|
|
2501
|
+
const includeErrors = options?.includeErrors ?? true;
|
|
2502
|
+
const maxLen = options?.maxAttributeLength;
|
|
2503
|
+
const redacted = options?.redacted;
|
|
2504
|
+
const titleName = escapeHtml(tree.name ?? tree.runId);
|
|
2505
|
+
const summaryRows = [];
|
|
2506
|
+
summaryRows.push(
|
|
2507
|
+
`<tr><th scope="row">runId</th><td><code>${escapeHtml(tree.runId)}</code></td></tr>`
|
|
2508
|
+
);
|
|
2509
|
+
if (tree.name !== void 0) {
|
|
2510
|
+
summaryRows.push(`<tr><th scope="row">name</th><td>${escapeHtml(tree.name)}</td></tr>`);
|
|
2511
|
+
}
|
|
2512
|
+
summaryRows.push(
|
|
2513
|
+
`<tr><th scope="row">status</th><td>${escapeHtml(String(tree.status ?? "unknown"))}</td></tr>`
|
|
2514
|
+
);
|
|
2515
|
+
summaryRows.push(
|
|
2516
|
+
`<tr><th scope="row">durationMs</th><td>${tree.durationMs !== void 0 ? escapeHtml(String(tree.durationMs)) : "\u2014"}</td></tr>`
|
|
2517
|
+
);
|
|
2518
|
+
summaryRows.push(
|
|
2519
|
+
`<tr><th scope="row">startedAt</th><td>${tree.startedAt !== void 0 ? escapeHtml(String(tree.startedAt)) : "\u2014"}</td></tr>`
|
|
2520
|
+
);
|
|
2521
|
+
summaryRows.push(
|
|
2522
|
+
`<tr><th scope="row">endedAt</th><td>${tree.endedAt !== void 0 ? escapeHtml(String(tree.endedAt)) : "\u2014"}</td></tr>`
|
|
2523
|
+
);
|
|
2524
|
+
summaryRows.push(
|
|
2525
|
+
`<tr><th scope="row">totalEvents</th><td>${escapeHtml(String(tree.metadata.totalEvents))}</td></tr>`
|
|
2526
|
+
);
|
|
2527
|
+
let confidenceHtml = "";
|
|
2528
|
+
if (includeMetadata) {
|
|
2529
|
+
const cb = tree.metadata.confidenceBreakdown;
|
|
2530
|
+
confidenceHtml += "<h3>Confidence breakdown</h3><table><thead><tr><th>bucket</th><th>count</th></tr></thead><tbody>";
|
|
2531
|
+
for (const k of Object.keys(cb).sort()) {
|
|
2532
|
+
const key = k;
|
|
2533
|
+
confidenceHtml += `<tr><td>${escapeHtml(key)}</td><td>${cb[key]}</td></tr>`;
|
|
2534
|
+
}
|
|
2535
|
+
confidenceHtml += "</tbody></table>";
|
|
2536
|
+
confidenceHtml += "<h3>Kind breakdown</h3><table><thead><tr><th>kind</th><th>count</th></tr></thead><tbody>";
|
|
2537
|
+
for (const k of Object.keys(tree.metadata.kinds).sort()) {
|
|
2538
|
+
const key = k;
|
|
2539
|
+
const c = tree.metadata.kinds[key];
|
|
2540
|
+
if (c > 0) confidenceHtml += `<tr><td>${escapeHtml(key)}</td><td>${c}</td></tr>`;
|
|
2541
|
+
}
|
|
2542
|
+
confidenceHtml += "</tbody></table>";
|
|
2543
|
+
}
|
|
2544
|
+
const flat = flattenTree(tree);
|
|
2545
|
+
const errors = flat.filter((n) => n.event.status === "error");
|
|
2546
|
+
let errorsHtml = "";
|
|
2547
|
+
if (includeErrors && errors.length > 0) {
|
|
2548
|
+
errorsHtml += "<h2>Errors</h2><ul>";
|
|
2549
|
+
for (const n of errors) {
|
|
2550
|
+
const msg = n.event.attributes && typeof n.event.attributes.error === "object" ? safeString2(
|
|
2551
|
+
n.event.attributes.error.message,
|
|
2552
|
+
maxLen
|
|
2553
|
+
) : "";
|
|
2554
|
+
errorsHtml += `<li><strong>${escapeHtml(n.event.name)}</strong> (${escapeHtml(n.event.eventId)}): ${escapeHtml(msg || "error")}</li>`;
|
|
2555
|
+
}
|
|
2556
|
+
errorsHtml += "</ul>";
|
|
2557
|
+
}
|
|
2558
|
+
let attrsHtml = "";
|
|
2559
|
+
if (includeAttributes) {
|
|
2560
|
+
attrsHtml += "<h2>Attributes (bounded)</h2>";
|
|
2561
|
+
for (const n of flat) {
|
|
2562
|
+
if (!n.event.attributes || Object.keys(n.event.attributes).length === 0) continue;
|
|
2563
|
+
const compact = compactAttributes(n.event.attributes, {
|
|
2564
|
+
maxLength: maxLen,
|
|
2565
|
+
redacted
|
|
2566
|
+
});
|
|
2567
|
+
attrsHtml += `<h3>${escapeHtml(n.event.name)}</h3><pre class="json">${escapeHtml(stableJson(compact, true))}</pre>`;
|
|
2568
|
+
}
|
|
2569
|
+
warnings.push(
|
|
2570
|
+
"Attributes may still contain sensitive data; review exports before sharing."
|
|
2571
|
+
);
|
|
2572
|
+
}
|
|
2573
|
+
const css = `
|
|
2574
|
+
body{font-family:system-ui,sans-serif;line-height:1.5;margin:1.5rem;max-width:960px;color:#111}
|
|
2575
|
+
h1{font-size:1.35rem}
|
|
2576
|
+
h2{font-size:1.1rem;margin-top:1.5rem}
|
|
2577
|
+
table{border-collapse:collapse;margin:0.75rem 0}
|
|
2578
|
+
th,td{border:1px solid #ccc;padding:0.35rem 0.6rem;text-align:left}
|
|
2579
|
+
th{background:#f5f5f5}
|
|
2580
|
+
pre.json{background:#f8f8f8;padding:0.75rem;overflow:auto;font-size:0.85rem}
|
|
2581
|
+
ul.tree{list-style:none;padding-left:1rem}
|
|
2582
|
+
ul.tree.nested{padding-left:1.25rem;border-left:1px solid #ddd;margin:0.25rem 0}
|
|
2583
|
+
.nm{font-weight:600}
|
|
2584
|
+
.meta{color:#555;font-size:0.9rem}
|
|
2585
|
+
footer{margin-top:2rem;font-size:0.85rem;color:#555}
|
|
2586
|
+
`.trim();
|
|
2587
|
+
const html = `<!doctype html>
|
|
2588
|
+
<html lang="en">
|
|
2589
|
+
<head>
|
|
2590
|
+
<meta charset="utf-8"/>
|
|
2591
|
+
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
2592
|
+
<title>${titleName}</title>
|
|
2593
|
+
<style>${css}</style>
|
|
2594
|
+
</head>
|
|
2595
|
+
<body>
|
|
2596
|
+
<header><h1>AgentInspect Run: ${titleName}</h1></header>
|
|
2597
|
+
<p class="note">Generated locally by AgentInspect.</p>
|
|
2598
|
+
${includeMetadata ? `<section class="summary"><h2>Summary</h2><table>${summaryRows.join("")}</table>${confidenceHtml}</section>` : ""}
|
|
2599
|
+
<section class="tree"><h2>Execution tree</h2>${tree.children.length > 0 ? renderTreeHtml(tree.children) : "<p>No steps recorded.</p>"}</section>
|
|
2600
|
+
${errorsHtml}
|
|
2601
|
+
${attrsHtml}
|
|
2602
|
+
<footer>Generated locally by AgentInspect. Review for sensitive data before sharing.</footer>
|
|
2603
|
+
</body>
|
|
2604
|
+
</html>`;
|
|
2605
|
+
return {
|
|
2606
|
+
format: "html",
|
|
2607
|
+
content: html,
|
|
2608
|
+
contentType: "text/html",
|
|
2609
|
+
fileExtension: ".html",
|
|
2610
|
+
warnings
|
|
2611
|
+
};
|
|
2612
|
+
}
|
|
2613
|
+
|
|
2614
|
+
// packages/core/src/exporters/markdown-exporter.ts
|
|
2615
|
+
function renderTreeAscii(nodes, indent = "") {
|
|
2616
|
+
const lines = [];
|
|
2617
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
2618
|
+
const n = nodes[i];
|
|
2619
|
+
const last = i === nodes.length - 1;
|
|
2620
|
+
const branch = last ? "\u2514\u2500 " : "\u251C\u2500 ";
|
|
2621
|
+
const ev = n.event;
|
|
2622
|
+
const status = ev.status ?? "?";
|
|
2623
|
+
const dur = ev.durationMs !== void 0 && Number.isFinite(ev.durationMs) ? `${ev.durationMs}ms` : "-";
|
|
2624
|
+
lines.push(`${indent}${branch}${escapeMarkdown(ev.name)} [${ev.kind}] ${status} (${dur})`);
|
|
2625
|
+
const nextIndent = indent + (last ? " " : "\u2502 ");
|
|
2626
|
+
if (n.children.length > 0) {
|
|
2627
|
+
const childStr = renderTreeAscii(n.children, nextIndent);
|
|
2628
|
+
if (childStr.length > 0) lines.push(childStr);
|
|
2629
|
+
}
|
|
2630
|
+
}
|
|
2631
|
+
return lines.join("\n");
|
|
2632
|
+
}
|
|
2633
|
+
function exportMarkdown(tree, options) {
|
|
2634
|
+
const warnings = [];
|
|
2635
|
+
const includeMetadata = options?.includeMetadata ?? true;
|
|
2636
|
+
const includeAttributes = options?.includeAttributes ?? false;
|
|
2637
|
+
const includeErrors = options?.includeErrors ?? true;
|
|
2638
|
+
const maxLen = options?.maxAttributeLength;
|
|
2639
|
+
const redacted = options?.redacted;
|
|
2640
|
+
const titleName = tree.name ?? tree.runId;
|
|
2641
|
+
const lines = [];
|
|
2642
|
+
lines.push(`# AgentInspect Run: ${escapeMarkdown(titleName)}`);
|
|
2643
|
+
lines.push("");
|
|
2644
|
+
lines.push("Generated locally by AgentInspect. Review for sensitive data before sharing.");
|
|
2645
|
+
lines.push("");
|
|
2646
|
+
if (includeMetadata) {
|
|
2647
|
+
lines.push("## Summary");
|
|
2648
|
+
lines.push("");
|
|
2649
|
+
lines.push(`- **runId**: ${escapeMarkdown(tree.runId)}`);
|
|
2650
|
+
if (tree.name !== void 0) lines.push(`- **name**: ${escapeMarkdown(tree.name)}`);
|
|
2651
|
+
lines.push(`- **status**: ${escapeMarkdown(String(tree.status ?? "unknown"))}`);
|
|
2652
|
+
lines.push(
|
|
2653
|
+
`- **durationMs**: ${tree.durationMs !== void 0 ? escapeMarkdown(String(tree.durationMs)) : "-"}`
|
|
2654
|
+
);
|
|
2655
|
+
lines.push(
|
|
2656
|
+
`- **startedAt**: ${tree.startedAt !== void 0 ? escapeMarkdown(String(tree.startedAt)) : "-"}`
|
|
2657
|
+
);
|
|
2658
|
+
lines.push(
|
|
2659
|
+
`- **endedAt**: ${tree.endedAt !== void 0 ? escapeMarkdown(String(tree.endedAt)) : "-"}`
|
|
2660
|
+
);
|
|
2661
|
+
lines.push(`- **totalEvents**: ${tree.metadata.totalEvents}`);
|
|
2662
|
+
lines.push("");
|
|
2663
|
+
lines.push("### Confidence breakdown");
|
|
2664
|
+
lines.push("");
|
|
2665
|
+
lines.push("| bucket | count |");
|
|
2666
|
+
lines.push("| --- | --- |");
|
|
2667
|
+
for (const k of Object.keys(tree.metadata.confidenceBreakdown).sort()) {
|
|
2668
|
+
const key = k;
|
|
2669
|
+
lines.push(
|
|
2670
|
+
`| ${escapeMarkdown(key)} | ${tree.metadata.confidenceBreakdown[key]} |`
|
|
2671
|
+
);
|
|
2672
|
+
}
|
|
2673
|
+
lines.push("");
|
|
2674
|
+
lines.push("### Kind breakdown");
|
|
2675
|
+
lines.push("");
|
|
2676
|
+
lines.push("| kind | count |");
|
|
2677
|
+
lines.push("| --- | --- |");
|
|
2678
|
+
for (const k of Object.keys(tree.metadata.kinds).sort()) {
|
|
2679
|
+
const key = k;
|
|
2680
|
+
const c = tree.metadata.kinds[key];
|
|
2681
|
+
if (c > 0) lines.push(`| ${escapeMarkdown(key)} | ${c} |`);
|
|
2682
|
+
}
|
|
2683
|
+
lines.push("");
|
|
2684
|
+
}
|
|
2685
|
+
lines.push("## Execution tree");
|
|
2686
|
+
lines.push("");
|
|
2687
|
+
lines.push("```text");
|
|
2688
|
+
lines.push(
|
|
2689
|
+
tree.children.length > 0 ? renderTreeAscii(tree.children) : "(no steps)"
|
|
2690
|
+
);
|
|
2691
|
+
lines.push("```");
|
|
2692
|
+
lines.push("");
|
|
2693
|
+
const flat = flattenTree(tree);
|
|
2694
|
+
const errors = flat.filter((n) => n.event.status === "error");
|
|
2695
|
+
if (includeErrors && errors.length > 0) {
|
|
2696
|
+
lines.push("## Errors");
|
|
2697
|
+
lines.push("");
|
|
2698
|
+
for (const n of errors) {
|
|
2699
|
+
const msg = n.event.attributes && typeof n.event.attributes.error === "object" ? safeString2(
|
|
2700
|
+
n.event.attributes.error.message,
|
|
2701
|
+
maxLen
|
|
2702
|
+
) : "";
|
|
2703
|
+
lines.push(
|
|
2704
|
+
`- **${escapeMarkdown(n.event.name)}** (${escapeMarkdown(n.event.eventId)}): ${escapeMarkdown(msg || "error")}`
|
|
2705
|
+
);
|
|
2706
|
+
}
|
|
2707
|
+
lines.push("");
|
|
2708
|
+
}
|
|
2709
|
+
if (includeAttributes) {
|
|
2710
|
+
lines.push("## Attributes (bounded)");
|
|
2711
|
+
lines.push("");
|
|
2712
|
+
for (const n of flat) {
|
|
2713
|
+
if (!n.event.attributes || Object.keys(n.event.attributes).length === 0) continue;
|
|
2714
|
+
const compact = compactAttributes(n.event.attributes, {
|
|
2715
|
+
maxLength: maxLen,
|
|
2716
|
+
redacted
|
|
2717
|
+
});
|
|
2718
|
+
lines.push(`### ${escapeMarkdown(n.event.name)}`);
|
|
2719
|
+
lines.push("");
|
|
2720
|
+
lines.push("```json");
|
|
2721
|
+
lines.push(stableJson(compact, true));
|
|
2722
|
+
lines.push("```");
|
|
2723
|
+
lines.push("");
|
|
2724
|
+
}
|
|
2725
|
+
warnings.push(
|
|
2726
|
+
"Attributes may still contain sensitive data; review exports before sharing."
|
|
2727
|
+
);
|
|
2728
|
+
}
|
|
2729
|
+
return {
|
|
2730
|
+
format: "markdown",
|
|
2731
|
+
content: lines.join("\n"),
|
|
2732
|
+
contentType: "text/markdown",
|
|
2733
|
+
fileExtension: ".md",
|
|
2734
|
+
warnings
|
|
2735
|
+
};
|
|
2736
|
+
}
|
|
2737
|
+
function hexFrom(seed, byteLen) {
|
|
2738
|
+
return crypto__default.default.createHash("sha256").update(seed, "utf8").digest("hex").slice(0, byteLen * 2);
|
|
2739
|
+
}
|
|
2740
|
+
function mapInspectKindToOI(kind, warnings) {
|
|
2741
|
+
switch (kind) {
|
|
2742
|
+
case "LLM":
|
|
2743
|
+
return { openInferenceKind: "LLM" };
|
|
2744
|
+
case "TOOL":
|
|
2745
|
+
return { openInferenceKind: "TOOL" };
|
|
2746
|
+
case "CHAIN":
|
|
2747
|
+
return { openInferenceKind: "CHAIN" };
|
|
2748
|
+
case "RETRIEVER":
|
|
2749
|
+
return { openInferenceKind: "RETRIEVER" };
|
|
2750
|
+
case "AGENT":
|
|
2751
|
+
return { openInferenceKind: "AGENT" };
|
|
2752
|
+
case "DECISION":
|
|
2753
|
+
warnings.push(
|
|
2754
|
+
`Ambiguous kind DECISION mapped to CHAIN for span compatibility (${EXPORT_PAYLOAD_VERSION}).`
|
|
2755
|
+
);
|
|
2756
|
+
return { openInferenceKind: "CHAIN" };
|
|
2757
|
+
case "RESULT":
|
|
2758
|
+
warnings.push(
|
|
2759
|
+
`Ambiguous kind RESULT mapped to UNKNOWN for span compatibility (${EXPORT_PAYLOAD_VERSION}).`
|
|
2760
|
+
);
|
|
2761
|
+
return { openInferenceKind: "UNKNOWN" };
|
|
2762
|
+
case "ERROR":
|
|
2763
|
+
warnings.push(`ERROR kind mapped to CHAIN for span compatibility.`);
|
|
2764
|
+
return { openInferenceKind: "CHAIN" };
|
|
2765
|
+
case "LOG":
|
|
2766
|
+
case "LOGIC":
|
|
2767
|
+
case "RUN":
|
|
2768
|
+
warnings.push(`${kind} mapped to CHAIN for span compatibility.`);
|
|
2769
|
+
return { openInferenceKind: "CHAIN" };
|
|
2770
|
+
default:
|
|
2771
|
+
warnings.push(`Unhandled InspectKind ${kind} mapped to UNKNOWN.`);
|
|
2772
|
+
return { openInferenceKind: "UNKNOWN" };
|
|
2773
|
+
}
|
|
2774
|
+
}
|
|
2775
|
+
function exportOpenInference(tree, options) {
|
|
2776
|
+
const warnings = [
|
|
2777
|
+
"OpenInference-compatible JSON export is experimental until verified against specific backends.",
|
|
2778
|
+
"This file was generated locally and not sent anywhere."
|
|
2779
|
+
];
|
|
2780
|
+
const traceId = hexFrom(`trace:${tree.runId}`, 16);
|
|
2781
|
+
const includeAttributes = options?.includeAttributes ?? false;
|
|
2782
|
+
const maxLen = options?.maxAttributeLength;
|
|
2783
|
+
const spans = [];
|
|
2784
|
+
for (const n of flattenTree(tree)) {
|
|
2785
|
+
const ev = n.event;
|
|
2786
|
+
const spanId = hexFrom(`${tree.runId}:${ev.eventId}`, 8);
|
|
2787
|
+
const parentSpanHex = ev.parentId ? hexFrom(`${tree.runId}:${ev.parentId}`, 8) : void 0;
|
|
2788
|
+
const startNs = Math.round(ev.timestamp * 1e6);
|
|
2789
|
+
let endNs;
|
|
2790
|
+
if (ev.durationMs !== void 0 && Number.isFinite(ev.durationMs)) {
|
|
2791
|
+
endNs = startNs + Math.round(ev.durationMs * 1e6);
|
|
2792
|
+
}
|
|
2793
|
+
const { openInferenceKind } = mapInspectKindToOI(ev.kind, warnings);
|
|
2794
|
+
const attrs = {
|
|
2795
|
+
"openinference.span.kind": openInferenceKind,
|
|
2796
|
+
"agent_inspect.kind": ev.kind,
|
|
2797
|
+
"agent_inspect.confidence": ev.confidence,
|
|
2798
|
+
"agent_inspect.source.type": ev.source.type,
|
|
2799
|
+
"agent_inspect.run_id": tree.runId,
|
|
2800
|
+
"agent_inspect.event_id": ev.eventId,
|
|
2801
|
+
"agent_inspect.status": ev.status ?? "unset"
|
|
2802
|
+
};
|
|
2803
|
+
if (ev.durationMs !== void 0) {
|
|
2804
|
+
attrs["agent_inspect.duration_ms"] = ev.durationMs;
|
|
2805
|
+
}
|
|
2806
|
+
const meta = ev.attributes;
|
|
2807
|
+
if (meta?.model !== void 0 && typeof meta.model === "string") {
|
|
2808
|
+
attrs["llm.model_name"] = meta.model;
|
|
2809
|
+
}
|
|
2810
|
+
const tokens = meta?.tokens;
|
|
2811
|
+
if (tokens && typeof tokens === "object" && tokens !== null) {
|
|
2812
|
+
const inp = tokens.input;
|
|
2813
|
+
const outp = tokens.output;
|
|
2814
|
+
if (typeof inp === "number") attrs["llm.token_count.prompt"] = inp;
|
|
2815
|
+
if (typeof outp === "number") attrs["llm.token_count.completion"] = outp;
|
|
2816
|
+
}
|
|
2817
|
+
if (includeAttributes && meta && typeof meta === "object") {
|
|
2818
|
+
for (const [k, v] of Object.entries(meta)) {
|
|
2819
|
+
if (k === "tokens" || k === "model") continue;
|
|
2820
|
+
if (v !== void 0 && v !== null && typeof v !== "object") {
|
|
2821
|
+
attrs[`agent_inspect.preview.${k}`] = typeof v === "string" ? v.slice(0, maxLen) : v;
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2824
|
+
}
|
|
2825
|
+
let status;
|
|
2826
|
+
if (ev.status === "error") {
|
|
2827
|
+
const msg = meta && typeof meta.error === "object" && meta.error !== null ? String(meta.error.message ?? "error") : "error";
|
|
2828
|
+
status = { code: "ERROR", message: msg.slice(0, maxLen) };
|
|
2829
|
+
} else if (ev.status === "ok") {
|
|
2830
|
+
status = { code: "OK" };
|
|
2831
|
+
} else {
|
|
2832
|
+
status = { code: "UNSET" };
|
|
2833
|
+
}
|
|
2834
|
+
spans.push({
|
|
2835
|
+
trace_id: traceId,
|
|
2836
|
+
span_id: spanId,
|
|
2837
|
+
parent_span_id: parentSpanHex,
|
|
2838
|
+
name: ev.name,
|
|
2839
|
+
start_time_unix_nano: startNs,
|
|
2840
|
+
end_time_unix_nano: endNs,
|
|
2841
|
+
attributes: attrs,
|
|
2842
|
+
status
|
|
2843
|
+
});
|
|
2844
|
+
}
|
|
2845
|
+
const payload = {
|
|
2846
|
+
exporter: "agent-inspect",
|
|
2847
|
+
format: "openinference",
|
|
2848
|
+
compatibility: "openinference-compatible",
|
|
2849
|
+
version: EXPORT_PAYLOAD_VERSION,
|
|
2850
|
+
trace_id: traceId,
|
|
2851
|
+
spans,
|
|
2852
|
+
warnings
|
|
2853
|
+
};
|
|
2854
|
+
return {
|
|
2855
|
+
format: "openinference",
|
|
2856
|
+
content: JSON.stringify(payload, null, 2 ),
|
|
2857
|
+
contentType: "application/json",
|
|
2858
|
+
fileExtension: ".openinference.json",
|
|
2859
|
+
warnings
|
|
2860
|
+
};
|
|
2861
|
+
}
|
|
2862
|
+
function hexFrom2(seed, byteLen) {
|
|
2863
|
+
return crypto__default.default.createHash("sha256").update(seed, "utf8").digest("hex").slice(0, byteLen * 2);
|
|
2864
|
+
}
|
|
2865
|
+
function stringAttr(key, value) {
|
|
2866
|
+
return { key, value: { stringValue: value } };
|
|
2867
|
+
}
|
|
2868
|
+
function intAttr(key, value) {
|
|
2869
|
+
return { key, value: { intValue: String(value) } };
|
|
2870
|
+
}
|
|
2871
|
+
function genAiOperationName(kind) {
|
|
2872
|
+
switch (kind) {
|
|
2873
|
+
case "LLM":
|
|
2874
|
+
return "generate_content";
|
|
2875
|
+
case "TOOL":
|
|
2876
|
+
return "execute_tool";
|
|
2877
|
+
case "AGENT":
|
|
2878
|
+
return "invoke_agent";
|
|
2879
|
+
default:
|
|
2880
|
+
return void 0;
|
|
2881
|
+
}
|
|
2882
|
+
}
|
|
2883
|
+
function exportOtlpJson(tree, options) {
|
|
2884
|
+
const warnings = [
|
|
2885
|
+
"OTLP JSON export uses OTel GenAI-aligned attributes where applicable; experimental until verified against specific collectors.",
|
|
2886
|
+
"Not OTLP gRPC/protobuf \u2014 JSON mapping only. Generated locally; no network upload."
|
|
2887
|
+
];
|
|
2888
|
+
const traceId = hexFrom2(`trace:${tree.runId}`, 16);
|
|
2889
|
+
const includeAttributes = options?.includeAttributes ?? false;
|
|
2890
|
+
const maxLen = options?.maxAttributeLength;
|
|
2891
|
+
const flat = flattenTree(tree);
|
|
2892
|
+
const spans = [];
|
|
2893
|
+
for (const n of flat) {
|
|
2894
|
+
const ev = n.event;
|
|
2895
|
+
const spanId = hexFrom2(`${tree.runId}:${ev.eventId}`, 8);
|
|
2896
|
+
const parentSpanId = ev.parentId ? hexFrom2(`${tree.runId}:${ev.parentId}`, 8) : void 0;
|
|
2897
|
+
const startNs = String(Math.round(ev.timestamp * 1e6));
|
|
2898
|
+
let endNs;
|
|
2899
|
+
if (ev.durationMs !== void 0 && Number.isFinite(ev.durationMs)) {
|
|
2900
|
+
endNs = String(Math.round(ev.timestamp * 1e6 + ev.durationMs * 1e6));
|
|
2901
|
+
}
|
|
2902
|
+
const attrs = [
|
|
2903
|
+
stringAttr("agent_inspect.kind", ev.kind),
|
|
2904
|
+
stringAttr("agent_inspect.confidence", ev.confidence),
|
|
2905
|
+
stringAttr("agent_inspect.source.type", ev.source.type),
|
|
2906
|
+
stringAttr("agent_inspect.run_id", tree.runId),
|
|
2907
|
+
stringAttr("agent_inspect.event_id", ev.eventId),
|
|
2908
|
+
stringAttr("agent_inspect.status", ev.status ?? "unset")
|
|
2909
|
+
];
|
|
2910
|
+
if (ev.durationMs !== void 0) {
|
|
2911
|
+
attrs.push(intAttr("agent_inspect.duration_ms", ev.durationMs));
|
|
2912
|
+
}
|
|
2913
|
+
const op = genAiOperationName(ev.kind);
|
|
2914
|
+
if (op !== void 0) {
|
|
2915
|
+
attrs.push(stringAttr("gen_ai.operation.name", op));
|
|
2916
|
+
}
|
|
2917
|
+
const meta = ev.attributes;
|
|
2918
|
+
if (meta?.model !== void 0 && typeof meta.model === "string") {
|
|
2919
|
+
attrs.push(stringAttr("gen_ai.request.model", meta.model.slice(0, maxLen)));
|
|
2920
|
+
}
|
|
2921
|
+
const tokens = meta?.tokens;
|
|
2922
|
+
if (tokens && typeof tokens === "object" && tokens !== null) {
|
|
2923
|
+
const inp = tokens.input;
|
|
2924
|
+
const outp = tokens.output;
|
|
2925
|
+
if (typeof inp === "number") attrs.push(intAttr("gen_ai.usage.input_tokens", inp));
|
|
2926
|
+
if (typeof outp === "number") attrs.push(intAttr("gen_ai.usage.output_tokens", outp));
|
|
2927
|
+
}
|
|
2928
|
+
if (includeAttributes && meta && typeof meta === "object") {
|
|
2929
|
+
for (const [k, v] of Object.entries(meta)) {
|
|
2930
|
+
if (k === "tokens" || k === "model") continue;
|
|
2931
|
+
if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
|
|
2932
|
+
attrs.push(
|
|
2933
|
+
stringAttr(
|
|
2934
|
+
`agent_inspect.preview.${k}`,
|
|
2935
|
+
typeof v === "string" ? v.slice(0, maxLen) : String(v)
|
|
2936
|
+
)
|
|
2937
|
+
);
|
|
2938
|
+
}
|
|
2939
|
+
}
|
|
2940
|
+
}
|
|
2941
|
+
let statusCode = "STATUS_CODE_UNSET";
|
|
2942
|
+
let statusMessage;
|
|
2943
|
+
if (ev.status === "error") {
|
|
2944
|
+
statusCode = "STATUS_CODE_ERROR";
|
|
2945
|
+
statusMessage = meta && typeof meta.error === "object" && meta.error !== null ? String(meta.error.message ?? "error").slice(0, maxLen) : "error";
|
|
2946
|
+
} else if (ev.status === "ok") {
|
|
2947
|
+
statusCode = "STATUS_CODE_OK";
|
|
2948
|
+
}
|
|
2949
|
+
const spanJson = {
|
|
2950
|
+
traceId,
|
|
2951
|
+
spanId,
|
|
2952
|
+
name: ev.name,
|
|
2953
|
+
kind: "SPAN_KIND_INTERNAL",
|
|
2954
|
+
startTimeUnixNano: startNs,
|
|
2955
|
+
attributes: attrs,
|
|
2956
|
+
status: {
|
|
2957
|
+
code: statusCode,
|
|
2958
|
+
...statusMessage !== void 0 ? { message: statusMessage } : {}
|
|
2959
|
+
}
|
|
2960
|
+
};
|
|
2961
|
+
if (parentSpanId !== void 0) {
|
|
2962
|
+
spanJson.parentSpanId = parentSpanId;
|
|
2963
|
+
}
|
|
2964
|
+
if (endNs !== void 0) {
|
|
2965
|
+
spanJson.endTimeUnixNano = endNs;
|
|
2966
|
+
}
|
|
2967
|
+
spans.push(spanJson);
|
|
2968
|
+
}
|
|
2969
|
+
const payload = {
|
|
2970
|
+
resourceSpans: [
|
|
2971
|
+
{
|
|
2972
|
+
resource: {
|
|
2973
|
+
attributes: [stringAttr("service.name", "agent-inspect")]
|
|
2974
|
+
},
|
|
2975
|
+
scopeSpans: [
|
|
2976
|
+
{
|
|
2977
|
+
scope: { name: "agent-inspect" },
|
|
2978
|
+
spans
|
|
2979
|
+
}
|
|
2980
|
+
]
|
|
2981
|
+
}
|
|
2982
|
+
]
|
|
2983
|
+
};
|
|
2984
|
+
return {
|
|
2985
|
+
format: "otlp-json",
|
|
2986
|
+
content: JSON.stringify(payload, null, 2 ),
|
|
2987
|
+
contentType: "application/json",
|
|
2988
|
+
fileExtension: ".otlp.json",
|
|
2989
|
+
warnings
|
|
2990
|
+
};
|
|
2991
|
+
}
|
|
2992
|
+
|
|
2993
|
+
// packages/core/src/exporters/validation.ts
|
|
2994
|
+
var EXPERIMENTAL = "Experimental compatibility export \u2014 verify against your target tooling before relying on it.";
|
|
2995
|
+
function validateExportContent(format, content) {
|
|
2996
|
+
const errors = [];
|
|
2997
|
+
const warnings = [EXPERIMENTAL];
|
|
2998
|
+
if (format === "markdown") {
|
|
2999
|
+
if (!content.startsWith("# AgentInspect Run")) {
|
|
3000
|
+
errors.push('Markdown export must start with "# AgentInspect Run"');
|
|
3001
|
+
}
|
|
3002
|
+
return { ok: errors.length === 0, format, errors, warnings };
|
|
3003
|
+
}
|
|
3004
|
+
if (format === "html") {
|
|
3005
|
+
const lower = content.toLowerCase();
|
|
3006
|
+
if (!lower.includes("<!doctype html")) {
|
|
3007
|
+
errors.push("HTML export must include <!doctype html>");
|
|
3008
|
+
}
|
|
3009
|
+
if (/<\s*script\b/i.test(content)) {
|
|
3010
|
+
errors.push("HTML export must not contain script tags");
|
|
3011
|
+
}
|
|
3012
|
+
if (/<\s*link\b[^>]*href\s*=/i.test(content)) {
|
|
3013
|
+
warnings.push("HTML export contains link tags \u2014 ensure no external stylesheets.");
|
|
3014
|
+
}
|
|
3015
|
+
return { ok: errors.length === 0, format, errors, warnings };
|
|
3016
|
+
}
|
|
3017
|
+
if (format === "openinference") {
|
|
3018
|
+
let parsed;
|
|
3019
|
+
try {
|
|
3020
|
+
parsed = JSON.parse(content);
|
|
3021
|
+
} catch {
|
|
3022
|
+
errors.push("OpenInference export is not valid JSON");
|
|
3023
|
+
return { ok: false, format, errors, warnings };
|
|
3024
|
+
}
|
|
3025
|
+
if (!parsed || typeof parsed !== "object") {
|
|
3026
|
+
errors.push("OpenInference export JSON must be an object");
|
|
3027
|
+
return { ok: false, format, errors, warnings };
|
|
3028
|
+
}
|
|
3029
|
+
const o = parsed;
|
|
3030
|
+
if (o.format !== "openinference") {
|
|
3031
|
+
errors.push('OpenInference export must include format: "openinference"');
|
|
3032
|
+
}
|
|
3033
|
+
if (!Array.isArray(o.spans)) {
|
|
3034
|
+
errors.push("OpenInference export must include a spans array");
|
|
3035
|
+
}
|
|
3036
|
+
warnings.push("OpenInference-compatible JSON is not guaranteed for every backend.");
|
|
3037
|
+
return { ok: errors.length === 0, format, errors, warnings };
|
|
3038
|
+
}
|
|
3039
|
+
if (format === "otlp-json") {
|
|
3040
|
+
let parsed;
|
|
3041
|
+
try {
|
|
3042
|
+
parsed = JSON.parse(content);
|
|
3043
|
+
} catch {
|
|
3044
|
+
errors.push("OTLP JSON export is not valid JSON");
|
|
3045
|
+
return { ok: false, format, errors, warnings };
|
|
3046
|
+
}
|
|
3047
|
+
if (!parsed || typeof parsed !== "object") {
|
|
3048
|
+
errors.push("OTLP JSON export must be an object");
|
|
3049
|
+
return { ok: false, format, errors, warnings };
|
|
3050
|
+
}
|
|
3051
|
+
const o = parsed;
|
|
3052
|
+
if (!Array.isArray(o.resourceSpans)) {
|
|
3053
|
+
errors.push("OTLP JSON export must include resourceSpans array");
|
|
3054
|
+
}
|
|
3055
|
+
warnings.push(
|
|
3056
|
+
"OTLP JSON mapping uses OTel GenAI-aligned attributes where applicable; collectors may require transformation."
|
|
3057
|
+
);
|
|
3058
|
+
return { ok: errors.length === 0, format, errors, warnings };
|
|
3059
|
+
}
|
|
3060
|
+
errors.push(`Unsupported export format`);
|
|
3061
|
+
return { ok: false, format, errors, warnings };
|
|
3062
|
+
}
|
|
3063
|
+
|
|
3064
|
+
// packages/core/src/exporters/manual-trace-adapter.ts
|
|
3065
|
+
function stepTypeToInspectKind(t) {
|
|
3066
|
+
switch (t) {
|
|
3067
|
+
case "llm":
|
|
3068
|
+
return "LLM";
|
|
3069
|
+
case "tool":
|
|
3070
|
+
return "TOOL";
|
|
3071
|
+
case "decision":
|
|
3072
|
+
return "DECISION";
|
|
3073
|
+
case "run":
|
|
3074
|
+
return "CHAIN";
|
|
3075
|
+
default:
|
|
3076
|
+
return "LOGIC";
|
|
3077
|
+
}
|
|
3078
|
+
}
|
|
3079
|
+
function mapStepStatus2(s) {
|
|
3080
|
+
if (s === void 0) return "running";
|
|
3081
|
+
if (s === "success") return "ok";
|
|
3082
|
+
return "error";
|
|
3083
|
+
}
|
|
3084
|
+
function manualTraceEventsToRunTree(events) {
|
|
3085
|
+
const started = events.find((e) => e.event === "run_started");
|
|
3086
|
+
if (!started || started.event !== "run_started") {
|
|
3087
|
+
throw new Error("Invalid trace: missing run_started");
|
|
3088
|
+
}
|
|
3089
|
+
const runId = started.runId;
|
|
3090
|
+
const runName = started.name;
|
|
3091
|
+
const completedAll = events.filter((e) => e.event === "run_completed");
|
|
3092
|
+
const lastCompleted = completedAll[completedAll.length - 1];
|
|
3093
|
+
let runStatus;
|
|
3094
|
+
if (lastCompleted === void 0) {
|
|
3095
|
+
runStatus = "running";
|
|
3096
|
+
} else if (lastCompleted.status === "success") {
|
|
3097
|
+
runStatus = "ok";
|
|
3098
|
+
} else {
|
|
3099
|
+
runStatus = "error";
|
|
3100
|
+
}
|
|
3101
|
+
const startedAt = started.startTime;
|
|
3102
|
+
const endedAt = lastCompleted !== void 0 && runStatus !== "running" ? lastCompleted.endTime : void 0;
|
|
3103
|
+
const durationMs = lastCompleted !== void 0 && Number.isFinite(lastCompleted.durationMs) ? lastCompleted.durationMs : void 0;
|
|
3104
|
+
const steps = /* @__PURE__ */ new Map();
|
|
3105
|
+
for (const e of events) {
|
|
3106
|
+
if (e.event !== "step_started") continue;
|
|
3107
|
+
const s = e;
|
|
3108
|
+
steps.set(s.stepId, {
|
|
3109
|
+
id: s.stepId,
|
|
3110
|
+
parentId: s.parentId,
|
|
3111
|
+
name: s.name,
|
|
3112
|
+
type: s.type,
|
|
3113
|
+
startTime: s.startTime,
|
|
3114
|
+
timestamp: s.timestamp,
|
|
3115
|
+
metadata: s.metadata
|
|
3116
|
+
});
|
|
3117
|
+
}
|
|
3118
|
+
for (const e of events) {
|
|
3119
|
+
if (e.event !== "step_completed") continue;
|
|
3120
|
+
const acc = steps.get(e.stepId);
|
|
3121
|
+
if (!acc) continue;
|
|
3122
|
+
acc.status = e.status;
|
|
3123
|
+
acc.endTime = e.endTime;
|
|
3124
|
+
acc.durationMs = e.durationMs;
|
|
3125
|
+
if (e.error?.message) {
|
|
3126
|
+
acc.error = e.error;
|
|
3127
|
+
}
|
|
3128
|
+
}
|
|
3129
|
+
const inspectNodes = /* @__PURE__ */ new Map();
|
|
3130
|
+
for (const acc of steps.values()) {
|
|
3131
|
+
const kind = stepTypeToInspectKind(acc.type);
|
|
3132
|
+
const status = mapStepStatus2(acc.status);
|
|
3133
|
+
const attrs = { ...acc.metadata ?? {} };
|
|
3134
|
+
if (acc.error?.message) {
|
|
3135
|
+
attrs.error = acc.error;
|
|
3136
|
+
}
|
|
3137
|
+
const evt = {
|
|
3138
|
+
eventId: acc.id,
|
|
3139
|
+
runId,
|
|
3140
|
+
parentId: acc.parentId,
|
|
3141
|
+
name: acc.name,
|
|
3142
|
+
kind,
|
|
3143
|
+
timestamp: acc.timestamp,
|
|
3144
|
+
status,
|
|
3145
|
+
durationMs: acc.durationMs,
|
|
3146
|
+
attributes: Object.keys(attrs).length > 0 ? attrs : void 0,
|
|
3147
|
+
confidence: "explicit",
|
|
3148
|
+
source: { type: "manual" }
|
|
3149
|
+
};
|
|
3150
|
+
inspectNodes.set(acc.id, { event: evt, children: [], depth: 0 });
|
|
3151
|
+
}
|
|
3152
|
+
const roots = [];
|
|
3153
|
+
const sortByStart = (a, b) => a.event.timestamp - b.event.timestamp;
|
|
3154
|
+
for (const node of inspectNodes.values()) {
|
|
3155
|
+
const pid = node.event.parentId;
|
|
3156
|
+
if (pid !== void 0 && inspectNodes.has(pid)) {
|
|
3157
|
+
inspectNodes.get(pid).children.push(node);
|
|
3158
|
+
} else {
|
|
3159
|
+
roots.push(node);
|
|
3160
|
+
}
|
|
3161
|
+
}
|
|
3162
|
+
roots.sort(sortByStart);
|
|
3163
|
+
for (const n of inspectNodes.values()) {
|
|
3164
|
+
n.children.sort(sortByStart);
|
|
3165
|
+
}
|
|
3166
|
+
const assignDepth = (n, depth) => {
|
|
3167
|
+
n.depth = depth;
|
|
3168
|
+
for (const c of n.children) assignDepth(c, depth + 1);
|
|
3169
|
+
};
|
|
3170
|
+
for (const r of roots) assignDepth(r, 0);
|
|
3171
|
+
const confidenceBreakdown = {
|
|
3172
|
+
explicit: 0,
|
|
3173
|
+
correlated: 0,
|
|
3174
|
+
heuristic: 0,
|
|
3175
|
+
unknown: 0
|
|
3176
|
+
};
|
|
3177
|
+
const kinds = zeroKinds();
|
|
3178
|
+
function countWalk(nodes) {
|
|
3179
|
+
for (const n of nodes) {
|
|
3180
|
+
confidenceBreakdown[n.event.confidence] += 1;
|
|
3181
|
+
kinds[n.event.kind] += 1;
|
|
3182
|
+
if (n.children.length > 0) countWalk(n.children);
|
|
3183
|
+
}
|
|
3184
|
+
}
|
|
3185
|
+
countWalk(roots);
|
|
3186
|
+
return {
|
|
3187
|
+
runId,
|
|
3188
|
+
name: runName,
|
|
3189
|
+
status: runStatus,
|
|
3190
|
+
startedAt,
|
|
3191
|
+
endedAt,
|
|
3192
|
+
durationMs,
|
|
3193
|
+
children: roots,
|
|
3194
|
+
metadata: {
|
|
3195
|
+
totalEvents: inspectNodes.size,
|
|
3196
|
+
confidenceBreakdown,
|
|
3197
|
+
kinds
|
|
3198
|
+
}
|
|
3199
|
+
};
|
|
3200
|
+
}
|
|
3201
|
+
|
|
3202
|
+
// packages/core/src/exporters/index.ts
|
|
3203
|
+
function mergeExportDefaults(options) {
|
|
3204
|
+
return {
|
|
3205
|
+
format: options.format,
|
|
3206
|
+
includeMetadata: options.includeMetadata ?? true,
|
|
3207
|
+
includeAttributes: options.includeAttributes ?? false,
|
|
3208
|
+
includeErrors: options.includeErrors ?? true,
|
|
3209
|
+
pretty: options.pretty,
|
|
3210
|
+
redacted: options.redacted,
|
|
3211
|
+
maxAttributeLength: options.maxAttributeLength
|
|
3212
|
+
};
|
|
3213
|
+
}
|
|
3214
|
+
function exportRunTree(tree, options) {
|
|
3215
|
+
const opts = mergeExportDefaults(options);
|
|
3216
|
+
switch (opts.format) {
|
|
3217
|
+
case "markdown":
|
|
3218
|
+
return exportMarkdown(tree, opts);
|
|
3219
|
+
case "html":
|
|
3220
|
+
return exportHtml(tree, opts);
|
|
3221
|
+
case "openinference":
|
|
3222
|
+
return exportOpenInference(tree, opts);
|
|
3223
|
+
case "otlp-json":
|
|
3224
|
+
return exportOtlpJson(tree, opts);
|
|
3225
|
+
default: {
|
|
3226
|
+
const _x = opts.format;
|
|
3227
|
+
throw new Error(`Unsupported export format: ${String(_x)}`);
|
|
3228
|
+
}
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
3231
|
+
function validateExport(result) {
|
|
3232
|
+
const base = validateExportContent(result.format, result.content);
|
|
3233
|
+
return {
|
|
3234
|
+
ok: base.ok,
|
|
3235
|
+
format: base.format,
|
|
3236
|
+
errors: base.errors,
|
|
3237
|
+
warnings: [...result.warnings, ...base.warnings]
|
|
3238
|
+
};
|
|
3239
|
+
}
|
|
3240
|
+
|
|
619
3241
|
// packages/cli/src/list.ts
|
|
620
3242
|
function parseLimit(raw) {
|
|
621
3243
|
const fallback = 20;
|
|
@@ -624,95 +3246,247 @@ function parseLimit(raw) {
|
|
|
624
3246
|
if (!Number.isFinite(n) || n <= 0) return fallback;
|
|
625
3247
|
return Math.min(n, 100);
|
|
626
3248
|
}
|
|
627
|
-
function isStatusFilter(value) {
|
|
628
|
-
return value === "running" || value === "success" || value === "error";
|
|
629
|
-
}
|
|
630
|
-
function buildRunSummary(runId, events) {
|
|
631
|
-
const started = events.find(
|
|
632
|
-
(e) => e.event === "run_started"
|
|
633
|
-
);
|
|
634
|
-
if (!started) return void 0;
|
|
635
|
-
const completed = events.filter(
|
|
636
|
-
(e) => e.event === "run_completed"
|
|
637
|
-
);
|
|
638
|
-
const last = completed[completed.length - 1];
|
|
639
|
-
const status = last ? last.status : "running";
|
|
640
|
-
const startTime = Number.isFinite(started.startTime) ? started.startTime : Number.isFinite(started.timestamp) ? started.timestamp : 0;
|
|
641
|
-
const name = typeof started.name === "string" && started.name.trim() !== "" ? started.name.trim() : "unknown-run";
|
|
642
|
-
return {
|
|
643
|
-
runId,
|
|
644
|
-
name,
|
|
645
|
-
status,
|
|
646
|
-
durationMs: last !== void 0 ? last.durationMs : void 0,
|
|
647
|
-
startTime
|
|
648
|
-
};
|
|
649
|
-
}
|
|
650
3249
|
function statusIcon(status) {
|
|
651
3250
|
if (status === "success") return "\u2713";
|
|
652
3251
|
if (status === "error") return "\u2717";
|
|
653
|
-
return "\u23F3";
|
|
3252
|
+
if (status === "running") return "\u23F3";
|
|
3253
|
+
return "?";
|
|
654
3254
|
}
|
|
655
3255
|
function durationCell(status, durationMs) {
|
|
656
|
-
if (status === "running") return "-";
|
|
3256
|
+
if (status === "running" || status === "unknown") return "-";
|
|
657
3257
|
if (durationMs !== void 0 && Number.isFinite(durationMs)) {
|
|
658
|
-
return
|
|
3258
|
+
return formatDuration2(durationMs);
|
|
659
3259
|
}
|
|
660
3260
|
return "-";
|
|
661
3261
|
}
|
|
662
|
-
function timestampCell(
|
|
663
|
-
|
|
664
|
-
const s = formatTimestamp(
|
|
3262
|
+
function timestampCell(startedAt, createdAt) {
|
|
3263
|
+
const t = typeof startedAt === "number" && Number.isFinite(startedAt) && startedAt > 0 ? startedAt : createdAt instanceof Date ? createdAt.getTime() : NaN;
|
|
3264
|
+
const s = formatTimestamp(t);
|
|
665
3265
|
return s === "Invalid date" ? "Invalid date" : s;
|
|
666
3266
|
}
|
|
667
3267
|
async function list(options = {}) {
|
|
668
3268
|
try {
|
|
669
|
-
const traceDir =
|
|
670
|
-
const
|
|
3269
|
+
const traceDir = resolveTraceDir({ dir: options.dir });
|
|
3270
|
+
const td = new TraceDirectory({ dir: traceDir });
|
|
3271
|
+
if (typeof options.since === "string" && options.since.trim() !== "") {
|
|
3272
|
+
parseDuration(options.since.trim());
|
|
3273
|
+
}
|
|
3274
|
+
const files = await td.list();
|
|
3275
|
+
if (files.length === 0) {
|
|
3276
|
+
if (options.json) {
|
|
3277
|
+
console.log("[]");
|
|
3278
|
+
} else {
|
|
3279
|
+
console.log("No AgentInspect runs found");
|
|
3280
|
+
console.log(`Trace directory: ${traceDir}`);
|
|
3281
|
+
}
|
|
3282
|
+
return;
|
|
3283
|
+
}
|
|
3284
|
+
const metas = [];
|
|
3285
|
+
for (const fileName of files) {
|
|
3286
|
+
try {
|
|
3287
|
+
const filePath = td.getPath(fileName);
|
|
3288
|
+
const meta = await extractMetadata(filePath);
|
|
3289
|
+
metas.push(meta);
|
|
3290
|
+
} catch {
|
|
3291
|
+
}
|
|
3292
|
+
}
|
|
3293
|
+
if (metas.length === 0) {
|
|
3294
|
+
if (options.json) {
|
|
3295
|
+
console.log("[]");
|
|
3296
|
+
} else {
|
|
3297
|
+
console.log("No AgentInspect runs found");
|
|
3298
|
+
console.log(`Trace directory: ${traceDir}`);
|
|
3299
|
+
}
|
|
3300
|
+
return;
|
|
3301
|
+
}
|
|
3302
|
+
const limit = parseLimit(options.limit);
|
|
3303
|
+
const filtered = filterTraces(metas, {
|
|
3304
|
+
status: options.status,
|
|
3305
|
+
name: options.name,
|
|
3306
|
+
since: options.since,
|
|
3307
|
+
limit
|
|
3308
|
+
});
|
|
3309
|
+
const shown = filtered.slice(0, limit);
|
|
3310
|
+
if (options.json) {
|
|
3311
|
+
console.log(JSON.stringify(shown, null, 2));
|
|
3312
|
+
return;
|
|
3313
|
+
}
|
|
3314
|
+
console.log("Recent AgentInspect Runs");
|
|
3315
|
+
for (const s of shown) {
|
|
3316
|
+
const icon = statusIcon(s.status);
|
|
3317
|
+
const dur = durationCell(s.status, s.durationMs);
|
|
3318
|
+
const ts = timestampCell(s.startedAt, s.createdAt);
|
|
3319
|
+
const nm = truncateName(s.name ?? "unnamed", 80);
|
|
3320
|
+
console.log(`${icon} ${s.runId} | ${nm} | ${dur} | ${ts}`);
|
|
3321
|
+
}
|
|
3322
|
+
console.log("");
|
|
3323
|
+
console.log(`Showing ${shown.length} of ${filtered.length} runs`);
|
|
3324
|
+
console.log(`Trace directory: ${traceDir}`);
|
|
3325
|
+
} catch (e) {
|
|
3326
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
3327
|
+
console.error(`[AgentInspect] list failed: ${msg}`);
|
|
3328
|
+
process.exitCode = 1;
|
|
3329
|
+
}
|
|
3330
|
+
}
|
|
3331
|
+
function parseKeep(raw) {
|
|
3332
|
+
const trimmed = typeof raw === "string" ? raw.trim() : "";
|
|
3333
|
+
if (trimmed === "") {
|
|
3334
|
+
throw new Error(`Invalid --keep value: ${raw}. Provide a positive integer.`);
|
|
3335
|
+
}
|
|
3336
|
+
const n = Number.parseInt(trimmed, 10);
|
|
3337
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
3338
|
+
throw new Error(`Invalid --keep value: ${raw}. Provide a positive integer.`);
|
|
3339
|
+
}
|
|
3340
|
+
return n;
|
|
3341
|
+
}
|
|
3342
|
+
function basisTimeMs(meta) {
|
|
3343
|
+
const started = typeof meta.startedAt === "number" ? meta.startedAt : void 0;
|
|
3344
|
+
const t = started ?? meta.createdAt.getTime();
|
|
3345
|
+
return Number.isFinite(t) ? t : 0;
|
|
3346
|
+
}
|
|
3347
|
+
function stableSortNewestFirst(a, b) {
|
|
3348
|
+
const dt = basisTimeMs(b) - basisTimeMs(a);
|
|
3349
|
+
if (dt !== 0) return dt;
|
|
3350
|
+
return a.runId.localeCompare(b.runId);
|
|
3351
|
+
}
|
|
3352
|
+
async function confirmDeletion(count) {
|
|
3353
|
+
const { createInterface: createInterface2 } = await import('readline/promises');
|
|
3354
|
+
const rl = createInterface2({ input: process$1.stdin, output: process$1.stdout });
|
|
3355
|
+
try {
|
|
3356
|
+
const answer = await rl.question(
|
|
3357
|
+
`Delete ${count} AgentInspect trace file(s)? Type "yes" to continue: `
|
|
3358
|
+
);
|
|
3359
|
+
return answer.trim() === "yes";
|
|
3360
|
+
} finally {
|
|
3361
|
+
rl.close();
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
3364
|
+
async function clean(options = {}) {
|
|
3365
|
+
try {
|
|
3366
|
+
const hasOlder = typeof options.olderThan === "string" && options.olderThan.trim() !== "";
|
|
3367
|
+
const hasKeep = typeof options.keep === "string" && options.keep.trim() !== "";
|
|
3368
|
+
if (!hasOlder && !hasKeep) {
|
|
3369
|
+
console.error("clean requires either --older-than <duration> or --keep <count>");
|
|
3370
|
+
process.exitCode = 1;
|
|
3371
|
+
return;
|
|
3372
|
+
}
|
|
3373
|
+
if (hasOlder && hasKeep) {
|
|
3374
|
+
console.error("Use either --older-than or --keep (not both).");
|
|
3375
|
+
process.exitCode = 1;
|
|
3376
|
+
return;
|
|
3377
|
+
}
|
|
3378
|
+
if (hasOlder) {
|
|
3379
|
+
parseDuration(options.olderThan.trim());
|
|
3380
|
+
}
|
|
3381
|
+
if (hasKeep) {
|
|
3382
|
+
parseKeep(options.keep);
|
|
3383
|
+
}
|
|
3384
|
+
const traceDir = resolveTraceDir({ dir: options.dir });
|
|
3385
|
+
const td = new TraceDirectory({ dir: traceDir });
|
|
3386
|
+
const files = await td.list();
|
|
671
3387
|
if (files.length === 0) {
|
|
672
|
-
console.log("No
|
|
3388
|
+
console.log("No runs to clean.");
|
|
3389
|
+
console.log(`Trace directory: ${traceDir}`);
|
|
3390
|
+
return;
|
|
3391
|
+
}
|
|
3392
|
+
const verified = [];
|
|
3393
|
+
const skipped = [];
|
|
3394
|
+
for (const fileName of files) {
|
|
3395
|
+
const filePath = td.getPath(fileName);
|
|
3396
|
+
const ok = await isAgentInspectTrace(filePath);
|
|
3397
|
+
if (!ok) {
|
|
3398
|
+
skipped.push(fileName);
|
|
3399
|
+
continue;
|
|
3400
|
+
}
|
|
3401
|
+
try {
|
|
3402
|
+
verified.push(await extractMetadata(filePath));
|
|
3403
|
+
} catch {
|
|
3404
|
+
skipped.push(fileName);
|
|
3405
|
+
}
|
|
3406
|
+
}
|
|
3407
|
+
if (verified.length === 0) {
|
|
3408
|
+
console.log("No runs to clean.");
|
|
3409
|
+
console.log(`Trace directory: ${traceDir}`);
|
|
3410
|
+
if (skipped.length > 0) {
|
|
3411
|
+
console.log("");
|
|
3412
|
+
console.log(`Skipped ${skipped.length} non-AgentInspect file(s).`);
|
|
3413
|
+
}
|
|
3414
|
+
return;
|
|
3415
|
+
}
|
|
3416
|
+
let toDelete = [];
|
|
3417
|
+
if (hasOlder) {
|
|
3418
|
+
const windowMs = parseDuration(options.olderThan.trim());
|
|
3419
|
+
const cutoff = Date.now() - windowMs;
|
|
3420
|
+
toDelete = verified.filter((m) => basisTimeMs(m) < cutoff).sort(stableSortNewestFirst);
|
|
3421
|
+
} else {
|
|
3422
|
+
const keepN = parseKeep(options.keep);
|
|
3423
|
+
const sorted = [...verified].sort(stableSortNewestFirst);
|
|
3424
|
+
toDelete = sorted.slice(keepN);
|
|
3425
|
+
}
|
|
3426
|
+
if (toDelete.length === 0) {
|
|
3427
|
+
console.log("No runs to clean.");
|
|
673
3428
|
console.log(`Trace directory: ${traceDir}`);
|
|
3429
|
+
if (skipped.length > 0) {
|
|
3430
|
+
console.log("");
|
|
3431
|
+
console.log(`Skipped ${skipped.length} non-AgentInspect file(s).`);
|
|
3432
|
+
}
|
|
3433
|
+
return;
|
|
3434
|
+
}
|
|
3435
|
+
if (options.dryRun) {
|
|
3436
|
+
console.log(`Would delete ${toDelete.length} run(s):`);
|
|
3437
|
+
for (const m of toDelete) {
|
|
3438
|
+
console.log(`- ${m.filePath}`);
|
|
3439
|
+
}
|
|
3440
|
+
if (skipped.length > 0) {
|
|
3441
|
+
console.log("");
|
|
3442
|
+
console.log(`Skipped ${skipped.length} non-AgentInspect file(s).`);
|
|
3443
|
+
}
|
|
674
3444
|
return;
|
|
675
3445
|
}
|
|
676
|
-
|
|
677
|
-
|
|
3446
|
+
if (options.yes !== true) {
|
|
3447
|
+
if (process$1.stdin.isTTY !== true) {
|
|
3448
|
+
console.error(
|
|
3449
|
+
"Refusing to delete without --yes in a non-interactive terminal."
|
|
3450
|
+
);
|
|
3451
|
+
process.exitCode = 1;
|
|
3452
|
+
return;
|
|
3453
|
+
}
|
|
3454
|
+
const ok = await confirmDeletion(toDelete.length);
|
|
3455
|
+
if (!ok) {
|
|
3456
|
+
console.log("Cancelled.");
|
|
3457
|
+
return;
|
|
3458
|
+
}
|
|
3459
|
+
}
|
|
3460
|
+
let deleted = 0;
|
|
3461
|
+
for (const m of toDelete) {
|
|
3462
|
+
const ok = await isAgentInspectTrace(m.filePath);
|
|
3463
|
+
if (!ok) {
|
|
3464
|
+
skipped.push(m.filePath);
|
|
3465
|
+
continue;
|
|
3466
|
+
}
|
|
678
3467
|
try {
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
const events = await readTraceEvents(runId, traceDir);
|
|
682
|
-
const row = buildRunSummary(runId, events);
|
|
683
|
-
if (row !== void 0) summaries.push(row);
|
|
3468
|
+
await promises.unlink(m.filePath);
|
|
3469
|
+
deleted += 1;
|
|
684
3470
|
} catch {
|
|
685
3471
|
}
|
|
686
3472
|
}
|
|
687
|
-
|
|
688
|
-
console.log("No AgentInspect runs found");
|
|
689
|
-
console.log(`Trace directory: ${traceDir}`);
|
|
690
|
-
return;
|
|
691
|
-
}
|
|
692
|
-
summaries.sort((a, b) => b.startTime - a.startTime);
|
|
693
|
-
const statusFilter = isStatusFilter(options.status) ? options.status : void 0;
|
|
694
|
-
const filtered = statusFilter === void 0 ? summaries : summaries.filter((s) => s.status === statusFilter);
|
|
695
|
-
const limit = parseLimit(options.limit);
|
|
696
|
-
const shown = filtered.slice(0, limit);
|
|
697
|
-
console.log("Recent AgentInspect Runs");
|
|
698
|
-
for (const s of shown) {
|
|
699
|
-
const icon = statusIcon(s.status);
|
|
700
|
-
const dur = durationCell(s.status, s.durationMs);
|
|
701
|
-
const ts = timestampCell(s.startTime);
|
|
702
|
-
const nm = truncateName(s.name, 80);
|
|
703
|
-
console.log(`${icon} ${s.runId} | ${nm} | ${dur} | ${ts}`);
|
|
704
|
-
}
|
|
705
|
-
console.log("");
|
|
706
|
-
console.log(`Showing ${shown.length} of ${filtered.length} runs`);
|
|
3473
|
+
console.log(`Deleted ${deleted} run(s).`);
|
|
707
3474
|
console.log(`Trace directory: ${traceDir}`);
|
|
3475
|
+
if (skipped.length > 0) {
|
|
3476
|
+
console.log("");
|
|
3477
|
+
console.log(`Skipped ${skipped.length} non-AgentInspect file(s).`);
|
|
3478
|
+
}
|
|
708
3479
|
} catch (e) {
|
|
709
3480
|
const msg = e instanceof Error ? e.message : String(e);
|
|
710
|
-
console.error(`[AgentInspect]
|
|
3481
|
+
console.error(`[AgentInspect] clean failed: ${msg}`);
|
|
711
3482
|
process.exitCode = 1;
|
|
712
3483
|
}
|
|
713
3484
|
}
|
|
714
3485
|
|
|
715
3486
|
// packages/cli/src/view.ts
|
|
3487
|
+
function isModuleNotFound(e) {
|
|
3488
|
+
return e !== null && typeof e === "object" && "code" in e && e.code === "ERR_MODULE_NOT_FOUND";
|
|
3489
|
+
}
|
|
716
3490
|
function buildStepTree(events) {
|
|
717
3491
|
const nodes = /* @__PURE__ */ new Map();
|
|
718
3492
|
for (const e of events) {
|
|
@@ -785,6 +3559,60 @@ function printStepTree(nodes, depth, verbose) {
|
|
|
785
3559
|
printStepTree(node.children, depth + 1, verbose);
|
|
786
3560
|
}
|
|
787
3561
|
}
|
|
3562
|
+
function pickMode(options) {
|
|
3563
|
+
if (options.summary) return "summary";
|
|
3564
|
+
if (options.metadata) return "metadata";
|
|
3565
|
+
if (options.errorsOnly) return "errors-only";
|
|
3566
|
+
return "tree";
|
|
3567
|
+
}
|
|
3568
|
+
function printSummary(summary) {
|
|
3569
|
+
console.log("Run Summary");
|
|
3570
|
+
console.log(`ID: ${summary.runId}`);
|
|
3571
|
+
console.log(`Name: ${summary.name ?? "unnamed"}`);
|
|
3572
|
+
console.log(`Status: ${summary.status}`);
|
|
3573
|
+
console.log(
|
|
3574
|
+
`Duration: ${summary.durationMs !== void 0 ? formatDuration2(summary.durationMs) : "-"}`
|
|
3575
|
+
);
|
|
3576
|
+
console.log(`Total steps: ${summary.totalSteps}`);
|
|
3577
|
+
console.log(`LLM steps: ${summary.llmSteps}`);
|
|
3578
|
+
console.log(`Tool steps: ${summary.toolSteps}`);
|
|
3579
|
+
console.log(`Logic steps: ${summary.logicSteps}`);
|
|
3580
|
+
console.log(`Error steps: ${summary.errorSteps}`);
|
|
3581
|
+
console.log(`Max depth: ${summary.maxDepth}`);
|
|
3582
|
+
if (summary.longestStep) {
|
|
3583
|
+
console.log(
|
|
3584
|
+
`Longest step: ${summary.longestStep.name} (${formatDuration2(
|
|
3585
|
+
summary.longestStep.durationMs
|
|
3586
|
+
)}, ${summary.longestStep.type})`
|
|
3587
|
+
);
|
|
3588
|
+
}
|
|
3589
|
+
}
|
|
3590
|
+
function printMetadata(meta) {
|
|
3591
|
+
console.log("Trace Metadata");
|
|
3592
|
+
console.log(`ID: ${meta.runId}`);
|
|
3593
|
+
console.log(`Name: ${meta.name ?? "unnamed"}`);
|
|
3594
|
+
console.log(`Status: ${meta.status}`);
|
|
3595
|
+
console.log(
|
|
3596
|
+
`Started: ${meta.startedAt !== void 0 ? formatTimestamp(meta.startedAt) : "-"}`
|
|
3597
|
+
);
|
|
3598
|
+
console.log(
|
|
3599
|
+
`Ended: ${meta.endedAt !== void 0 ? formatTimestamp(meta.endedAt) : "-"}`
|
|
3600
|
+
);
|
|
3601
|
+
console.log(
|
|
3602
|
+
`Duration: ${meta.durationMs !== void 0 ? formatDuration2(meta.durationMs) : "-"}`
|
|
3603
|
+
);
|
|
3604
|
+
console.log(`Event count: ${meta.eventCount}`);
|
|
3605
|
+
console.log(`File path: ${meta.filePath}`);
|
|
3606
|
+
console.log(`File size: ${meta.fileSize}`);
|
|
3607
|
+
console.log(`Created at: ${meta.createdAt.toISOString()}`);
|
|
3608
|
+
}
|
|
3609
|
+
function filterErrorEvents(events) {
|
|
3610
|
+
return events.filter((e) => {
|
|
3611
|
+
if (e.event === "run_completed") return e.status === "error";
|
|
3612
|
+
if (e.event === "step_completed") return e.status === "error";
|
|
3613
|
+
return false;
|
|
3614
|
+
});
|
|
3615
|
+
}
|
|
788
3616
|
async function view(runId, options = {}) {
|
|
789
3617
|
try {
|
|
790
3618
|
const id = typeof runId === "string" && runId.trim() !== "" ? runId.trim() : "";
|
|
@@ -793,7 +3621,38 @@ async function view(runId, options = {}) {
|
|
|
793
3621
|
process.exitCode = 1;
|
|
794
3622
|
return;
|
|
795
3623
|
}
|
|
796
|
-
|
|
3624
|
+
if (options.tui) {
|
|
3625
|
+
const conflict = options.json || options.summary || options.metadata || options.errorsOnly;
|
|
3626
|
+
if (conflict) {
|
|
3627
|
+
console.error(
|
|
3628
|
+
"--tui cannot be combined with --json, --summary, --metadata, or --errors-only"
|
|
3629
|
+
);
|
|
3630
|
+
process.exitCode = 1;
|
|
3631
|
+
return;
|
|
3632
|
+
}
|
|
3633
|
+
try {
|
|
3634
|
+
const mod = await import('@agent-inspect/tui');
|
|
3635
|
+
await mod.runTraceViewer({ runId: id, dir: options.dir });
|
|
3636
|
+
} catch (e) {
|
|
3637
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
3638
|
+
if (msg.includes("interactive terminal")) {
|
|
3639
|
+
console.error(msg);
|
|
3640
|
+
process.exitCode = 1;
|
|
3641
|
+
return;
|
|
3642
|
+
}
|
|
3643
|
+
if (isModuleNotFound(e)) {
|
|
3644
|
+
console.error(
|
|
3645
|
+
"TUI support is optional. Install @agent-inspect/tui to use --tui."
|
|
3646
|
+
);
|
|
3647
|
+
process.exitCode = 1;
|
|
3648
|
+
return;
|
|
3649
|
+
}
|
|
3650
|
+
console.error(`[AgentInspect] TUI failed: ${msg}`);
|
|
3651
|
+
process.exitCode = 1;
|
|
3652
|
+
}
|
|
3653
|
+
return;
|
|
3654
|
+
}
|
|
3655
|
+
const traceDir = resolveTraceDir({ dir: options.dir });
|
|
797
3656
|
const events = await readTraceEvents(id, traceDir);
|
|
798
3657
|
if (events.length === 0) {
|
|
799
3658
|
console.log(`Run not found: ${id}`);
|
|
@@ -801,6 +3660,38 @@ async function view(runId, options = {}) {
|
|
|
801
3660
|
process.exitCode = 1;
|
|
802
3661
|
return;
|
|
803
3662
|
}
|
|
3663
|
+
const mode = pickMode(options);
|
|
3664
|
+
const filePath = getTraceFilePath(id, traceDir);
|
|
3665
|
+
if (mode === "summary") {
|
|
3666
|
+
const summary = buildRunSummary(events);
|
|
3667
|
+
if (options.json) {
|
|
3668
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
3669
|
+
} else {
|
|
3670
|
+
printSummary(summary);
|
|
3671
|
+
}
|
|
3672
|
+
return;
|
|
3673
|
+
}
|
|
3674
|
+
if (mode === "metadata") {
|
|
3675
|
+
const meta = await extractMetadata(filePath);
|
|
3676
|
+
if (options.json) {
|
|
3677
|
+
console.log(JSON.stringify(meta, null, 2));
|
|
3678
|
+
} else {
|
|
3679
|
+
printMetadata(meta);
|
|
3680
|
+
}
|
|
3681
|
+
return;
|
|
3682
|
+
}
|
|
3683
|
+
if (mode === "errors-only") {
|
|
3684
|
+
const errEvents = filterErrorEvents(events);
|
|
3685
|
+
if (options.json) {
|
|
3686
|
+
console.log(JSON.stringify(errEvents, null, 2));
|
|
3687
|
+
} else if (errEvents.length === 0) {
|
|
3688
|
+
console.log("No errors found in trace");
|
|
3689
|
+
} else {
|
|
3690
|
+
console.log("Error events");
|
|
3691
|
+
console.log(JSON.stringify(errEvents, null, 2));
|
|
3692
|
+
}
|
|
3693
|
+
return;
|
|
3694
|
+
}
|
|
804
3695
|
if (options.json) {
|
|
805
3696
|
console.log(JSON.stringify(events, null, 2));
|
|
806
3697
|
return;
|
|
@@ -818,7 +3709,7 @@ async function view(runId, options = {}) {
|
|
|
818
3709
|
);
|
|
819
3710
|
const last = completed[completed.length - 1];
|
|
820
3711
|
const status = last ? last.status : "running";
|
|
821
|
-
const durationLine = last !== void 0 && Number.isFinite(last.durationMs) ?
|
|
3712
|
+
const durationLine = last !== void 0 && Number.isFinite(last.durationMs) ? formatDuration2(last.durationMs) : "-";
|
|
822
3713
|
const startedTs = Number.isFinite(started.startTime) ? started.startTime : started.timestamp;
|
|
823
3714
|
const startedLabel = formatTimestamp(startedTs);
|
|
824
3715
|
console.log(`AgentInspect Run: ${started.name}`);
|
|
@@ -835,7 +3726,7 @@ async function view(runId, options = {}) {
|
|
|
835
3726
|
printStepTree(tree, 0, options.verbose === true);
|
|
836
3727
|
}
|
|
837
3728
|
console.log("");
|
|
838
|
-
console.log(`Trace file: ${
|
|
3729
|
+
console.log(`Trace file: ${filePath}`);
|
|
839
3730
|
} catch (e) {
|
|
840
3731
|
const msg = e instanceof Error ? e.message : String(e);
|
|
841
3732
|
console.error(`[AgentInspect] view failed: ${msg}`);
|
|
@@ -843,6 +3734,535 @@ async function view(runId, options = {}) {
|
|
|
843
3734
|
}
|
|
844
3735
|
}
|
|
845
3736
|
|
|
3737
|
+
// packages/cli/src/logs.ts
|
|
3738
|
+
function parseRunIdKeys(raw) {
|
|
3739
|
+
if (typeof raw !== "string") return void 0;
|
|
3740
|
+
const parts = raw.split(",").map((s) => s.trim()).filter((s) => s !== "");
|
|
3741
|
+
return parts.length > 0 ? parts : void 0;
|
|
3742
|
+
}
|
|
3743
|
+
function summarizeWarnings(warnings) {
|
|
3744
|
+
const out = {};
|
|
3745
|
+
for (const w of warnings) {
|
|
3746
|
+
out[w.code] = (out[w.code] ?? 0) + 1;
|
|
3747
|
+
}
|
|
3748
|
+
return out;
|
|
3749
|
+
}
|
|
3750
|
+
function formatWarningLine(w) {
|
|
3751
|
+
const loc = w.line !== void 0 ? `line ${w.line}` : w.file ? "file" : "unknown";
|
|
3752
|
+
return `- ${loc} ${w.code}: ${w.message}`;
|
|
3753
|
+
}
|
|
3754
|
+
async function logs(filePath, options = {}) {
|
|
3755
|
+
try {
|
|
3756
|
+
const fp = typeof filePath === "string" ? filePath.trim() : "";
|
|
3757
|
+
if (fp === "") {
|
|
3758
|
+
console.error("Log file path is required");
|
|
3759
|
+
process.exitCode = 1;
|
|
3760
|
+
return;
|
|
3761
|
+
}
|
|
3762
|
+
const warningsMode = options.warnings ?? "summary";
|
|
3763
|
+
if (warningsMode !== "none" && warningsMode !== "summary" && warningsMode !== "all") {
|
|
3764
|
+
console.error(`Invalid --warnings value: ${String(options.warnings)}`);
|
|
3765
|
+
process.exitCode = 1;
|
|
3766
|
+
return;
|
|
3767
|
+
}
|
|
3768
|
+
const res = await parseLogsToTrees(fp, {
|
|
3769
|
+
format: options.format ?? "auto",
|
|
3770
|
+
configPath: options.config,
|
|
3771
|
+
runIdKeys: parseRunIdKeys(options.runIdKey),
|
|
3772
|
+
eventKey: options.eventKey,
|
|
3773
|
+
timestampKey: options.timestampKey,
|
|
3774
|
+
messageKey: options.messageKey,
|
|
3775
|
+
levelKey: options.levelKey,
|
|
3776
|
+
parentIdKey: options.parentIdKey,
|
|
3777
|
+
durationKey: options.durationKey,
|
|
3778
|
+
statusKey: options.statusKey,
|
|
3779
|
+
warnings: warningsMode
|
|
3780
|
+
});
|
|
3781
|
+
const summary = {
|
|
3782
|
+
runs: res.trees.length,
|
|
3783
|
+
events: res.events.length,
|
|
3784
|
+
warnings: res.warnings.length
|
|
3785
|
+
};
|
|
3786
|
+
const hasOutput = res.events.length > 0 && res.trees.length > 0;
|
|
3787
|
+
if (options.json) {
|
|
3788
|
+
const payload = warningsMode === "none" ? { events: res.events, trees: res.trees, warnings: [], summary } : { ...res, summary };
|
|
3789
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
3790
|
+
if (!hasOutput) process.exitCode = 1;
|
|
3791
|
+
return;
|
|
3792
|
+
}
|
|
3793
|
+
if (!hasOutput) {
|
|
3794
|
+
console.error("No valid events found.");
|
|
3795
|
+
process.exitCode = 1;
|
|
3796
|
+
} else {
|
|
3797
|
+
const treeText = renderRunTrees(res.trees, {
|
|
3798
|
+
summary: options.summary ?? true,
|
|
3799
|
+
showConfidence: "always"
|
|
3800
|
+
});
|
|
3801
|
+
console.log(treeText);
|
|
3802
|
+
}
|
|
3803
|
+
if (warningsMode === "none") return;
|
|
3804
|
+
const warnings = res.warnings;
|
|
3805
|
+
const counts = summarizeWarnings(warnings);
|
|
3806
|
+
console.log("");
|
|
3807
|
+
console.log("Warnings:");
|
|
3808
|
+
console.log(` Total: ${warnings.length}`);
|
|
3809
|
+
if (warningsMode === "summary") {
|
|
3810
|
+
for (const [code, count] of Object.entries(counts)) {
|
|
3811
|
+
console.log(` ${code}: ${count}`);
|
|
3812
|
+
}
|
|
3813
|
+
return;
|
|
3814
|
+
}
|
|
3815
|
+
for (const w of warnings) {
|
|
3816
|
+
console.log(formatWarningLine(w));
|
|
3817
|
+
}
|
|
3818
|
+
} catch (e) {
|
|
3819
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
3820
|
+
console.error(`[AgentInspect] logs failed: ${msg}`);
|
|
3821
|
+
process.exitCode = 1;
|
|
3822
|
+
}
|
|
3823
|
+
}
|
|
3824
|
+
function parseRunIdKeys2(raw) {
|
|
3825
|
+
if (typeof raw !== "string") return void 0;
|
|
3826
|
+
const parts = raw.split(",").map((s) => s.trim()).filter((s) => s !== "");
|
|
3827
|
+
return parts.length > 0 ? parts : void 0;
|
|
3828
|
+
}
|
|
3829
|
+
function parseRefreshMs(raw) {
|
|
3830
|
+
const fallback = 250;
|
|
3831
|
+
if (raw === void 0 || raw.trim() === "") return fallback;
|
|
3832
|
+
const n = Number.parseInt(raw.trim(), 10);
|
|
3833
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
3834
|
+
throw new Error(`Invalid --refresh value: ${raw}. Provide a positive integer (ms).`);
|
|
3835
|
+
}
|
|
3836
|
+
return n;
|
|
3837
|
+
}
|
|
3838
|
+
function summarizeWarnings2(warnings) {
|
|
3839
|
+
const out = {};
|
|
3840
|
+
for (const w of warnings) {
|
|
3841
|
+
out[w.code] = (out[w.code] ?? 0) + 1;
|
|
3842
|
+
}
|
|
3843
|
+
return out;
|
|
3844
|
+
}
|
|
3845
|
+
function formatWarningLine2(w) {
|
|
3846
|
+
const loc = w.line !== void 0 ? `line ${w.line}` : w.file ? "file" : "unknown";
|
|
3847
|
+
return `- ${loc} ${w.code}: ${w.message}`;
|
|
3848
|
+
}
|
|
3849
|
+
function clearScreen() {
|
|
3850
|
+
process.stdout.write("\x1B[2J\x1B[0f");
|
|
3851
|
+
}
|
|
3852
|
+
function sleep(ms) {
|
|
3853
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3854
|
+
}
|
|
3855
|
+
async function* readStdinLines() {
|
|
3856
|
+
const { createInterface: createInterface2 } = await import('readline');
|
|
3857
|
+
const rl = createInterface2({ input: process$1.stdin, crlfDelay: Infinity });
|
|
3858
|
+
try {
|
|
3859
|
+
for await (const line of rl) {
|
|
3860
|
+
yield line;
|
|
3861
|
+
}
|
|
3862
|
+
} finally {
|
|
3863
|
+
rl.close();
|
|
3864
|
+
}
|
|
3865
|
+
}
|
|
3866
|
+
async function readFileOnce(filePath, onLine) {
|
|
3867
|
+
const fh = await promises.open(filePath, "r");
|
|
3868
|
+
try {
|
|
3869
|
+
const text = await fh.readFile({ encoding: "utf-8" });
|
|
3870
|
+
const lines = text.split(/\r?\n/);
|
|
3871
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3872
|
+
const line = lines[i] ?? "";
|
|
3873
|
+
onLine(line, i + 1);
|
|
3874
|
+
}
|
|
3875
|
+
} finally {
|
|
3876
|
+
await fh.close();
|
|
3877
|
+
}
|
|
3878
|
+
}
|
|
3879
|
+
async function followFile(filePath, options, onLine, shouldStop) {
|
|
3880
|
+
let pos = 0;
|
|
3881
|
+
let lineNumber = 0;
|
|
3882
|
+
let carry = "";
|
|
3883
|
+
const st = await promises.stat(filePath);
|
|
3884
|
+
if (options.once) {
|
|
3885
|
+
await readFileOnce(filePath, onLine);
|
|
3886
|
+
return;
|
|
3887
|
+
}
|
|
3888
|
+
pos = st.size;
|
|
3889
|
+
while (!shouldStop()) {
|
|
3890
|
+
let next;
|
|
3891
|
+
try {
|
|
3892
|
+
next = await promises.stat(filePath);
|
|
3893
|
+
} catch (e) {
|
|
3894
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
3895
|
+
throw new Error(`Failed to stat file: ${filePath} (${msg})`);
|
|
3896
|
+
}
|
|
3897
|
+
if (next.size > pos) {
|
|
3898
|
+
const fh = await promises.open(filePath, "r");
|
|
3899
|
+
try {
|
|
3900
|
+
const len = next.size - pos;
|
|
3901
|
+
const buf = Buffer.allocUnsafe(Number(len));
|
|
3902
|
+
const { bytesRead } = await fh.read(buf, 0, buf.length, pos);
|
|
3903
|
+
pos += bytesRead;
|
|
3904
|
+
const chunk = carry + buf.toString("utf-8", 0, bytesRead);
|
|
3905
|
+
const parts = chunk.split(/\r?\n/);
|
|
3906
|
+
carry = parts.pop() ?? "";
|
|
3907
|
+
for (const p of parts) {
|
|
3908
|
+
lineNumber += 1;
|
|
3909
|
+
onLine(p, lineNumber);
|
|
3910
|
+
}
|
|
3911
|
+
} finally {
|
|
3912
|
+
await fh.close();
|
|
3913
|
+
}
|
|
3914
|
+
}
|
|
3915
|
+
await sleep(options.refreshMs);
|
|
3916
|
+
}
|
|
3917
|
+
}
|
|
3918
|
+
async function tail(options = {}) {
|
|
3919
|
+
try {
|
|
3920
|
+
const warningsMode = options.warnings ?? "summary";
|
|
3921
|
+
if (warningsMode !== "none" && warningsMode !== "summary" && warningsMode !== "all") {
|
|
3922
|
+
console.error(`Invalid --warnings value: ${String(options.warnings)}`);
|
|
3923
|
+
process.exitCode = 1;
|
|
3924
|
+
return;
|
|
3925
|
+
}
|
|
3926
|
+
const refreshMs = parseRefreshMs(options.refresh);
|
|
3927
|
+
const cfgBase = await loadLogIngestConfig(options.config);
|
|
3928
|
+
const override = {};
|
|
3929
|
+
const runIdKeys = parseRunIdKeys2(options.runIdKey);
|
|
3930
|
+
if (runIdKeys !== void 0) override.runIdKeys = runIdKeys;
|
|
3931
|
+
for (const k of [
|
|
3932
|
+
"eventKey",
|
|
3933
|
+
"timestampKey",
|
|
3934
|
+
"messageKey",
|
|
3935
|
+
"levelKey",
|
|
3936
|
+
"parentIdKey",
|
|
3937
|
+
"durationKey",
|
|
3938
|
+
"statusKey"
|
|
3939
|
+
]) {
|
|
3940
|
+
const v = options[k];
|
|
3941
|
+
if (v !== void 0) override[k] = v;
|
|
3942
|
+
}
|
|
3943
|
+
const config = mergeLogIngestConfig(cfgBase, override);
|
|
3944
|
+
const format = options.format ?? "auto";
|
|
3945
|
+
const filePath = typeof options.file === "string" && options.file.trim() !== "" ? options.file.trim() : void 0;
|
|
3946
|
+
const acc = new LiveLogAccumulator({
|
|
3947
|
+
config,
|
|
3948
|
+
format,
|
|
3949
|
+
file: filePath ?? "stdin"
|
|
3950
|
+
});
|
|
3951
|
+
const isTty = Boolean(process.stdout.isTTY);
|
|
3952
|
+
const doClear = isTty && options.noClear !== true;
|
|
3953
|
+
let stop = false;
|
|
3954
|
+
const shouldStop = () => stop;
|
|
3955
|
+
const onSigInt = () => {
|
|
3956
|
+
stop = true;
|
|
3957
|
+
};
|
|
3958
|
+
process.once("SIGINT", onSigInt);
|
|
3959
|
+
let dirty = false;
|
|
3960
|
+
let lastRenderedKey = "";
|
|
3961
|
+
let waitingShown = false;
|
|
3962
|
+
const renderNow = () => {
|
|
3963
|
+
const events = acc.getEvents();
|
|
3964
|
+
const trees = acc.getTrees();
|
|
3965
|
+
const warnings = acc.getWarnings();
|
|
3966
|
+
const summary = {
|
|
3967
|
+
runs: trees.length,
|
|
3968
|
+
events: events.length,
|
|
3969
|
+
warnings: warnings.length
|
|
3970
|
+
};
|
|
3971
|
+
if (options.json) {
|
|
3972
|
+
const payload = warningsMode === "none" ? { events, trees, warnings: [], summary } : { events, trees, warnings, summary };
|
|
3973
|
+
process.stdout.write(JSON.stringify(payload) + "\n");
|
|
3974
|
+
return;
|
|
3975
|
+
}
|
|
3976
|
+
if (doClear) clearScreen();
|
|
3977
|
+
if (!waitingShown && events.length === 0 && isTty) {
|
|
3978
|
+
console.log("Waiting for logs...");
|
|
3979
|
+
waitingShown = true;
|
|
3980
|
+
}
|
|
3981
|
+
if (trees.length > 0) {
|
|
3982
|
+
const text = renderRunTrees(trees, { summary: true, showConfidence: "always" });
|
|
3983
|
+
console.log(text);
|
|
3984
|
+
}
|
|
3985
|
+
if (warningsMode === "none") return;
|
|
3986
|
+
console.log("");
|
|
3987
|
+
console.log("Warnings:");
|
|
3988
|
+
console.log(` Total: ${warnings.length}`);
|
|
3989
|
+
if (warningsMode === "summary") {
|
|
3990
|
+
const counts = summarizeWarnings2(warnings);
|
|
3991
|
+
for (const [code, count] of Object.entries(counts)) {
|
|
3992
|
+
console.log(` ${code}: ${count}`);
|
|
3993
|
+
}
|
|
3994
|
+
} else {
|
|
3995
|
+
for (const w of warnings) console.log(formatWarningLine2(w));
|
|
3996
|
+
}
|
|
3997
|
+
};
|
|
3998
|
+
const renderLoop = async () => {
|
|
3999
|
+
while (!shouldStop()) {
|
|
4000
|
+
if (dirty) {
|
|
4001
|
+
const key = `${acc.getEvents().length}:${acc.getWarnings().length}:${acc.getTrees().length}`;
|
|
4002
|
+
if (key !== lastRenderedKey) {
|
|
4003
|
+
lastRenderedKey = key;
|
|
4004
|
+
renderNow();
|
|
4005
|
+
}
|
|
4006
|
+
dirty = false;
|
|
4007
|
+
}
|
|
4008
|
+
await sleep(refreshMs);
|
|
4009
|
+
}
|
|
4010
|
+
};
|
|
4011
|
+
const renderTask = renderLoop();
|
|
4012
|
+
const onLine = (line, lineNumber) => {
|
|
4013
|
+
acc.pushLine(line, lineNumber);
|
|
4014
|
+
dirty = true;
|
|
4015
|
+
};
|
|
4016
|
+
let endedNaturally = false;
|
|
4017
|
+
if (filePath) {
|
|
4018
|
+
try {
|
|
4019
|
+
await promises.stat(filePath);
|
|
4020
|
+
} catch (e) {
|
|
4021
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
4022
|
+
console.error(`Log file does not exist: ${filePath} (${msg})`);
|
|
4023
|
+
process.exitCode = 1;
|
|
4024
|
+
stop = true;
|
|
4025
|
+
return;
|
|
4026
|
+
}
|
|
4027
|
+
await followFile(
|
|
4028
|
+
filePath,
|
|
4029
|
+
{ refreshMs, once: options.once === true },
|
|
4030
|
+
onLine,
|
|
4031
|
+
shouldStop
|
|
4032
|
+
);
|
|
4033
|
+
endedNaturally = options.once === true;
|
|
4034
|
+
} else {
|
|
4035
|
+
let lineNumber = 0;
|
|
4036
|
+
for await (const line of readStdinLines()) {
|
|
4037
|
+
lineNumber += 1;
|
|
4038
|
+
onLine(line, lineNumber);
|
|
4039
|
+
if (shouldStop()) break;
|
|
4040
|
+
}
|
|
4041
|
+
endedNaturally = !shouldStop();
|
|
4042
|
+
stop = true;
|
|
4043
|
+
}
|
|
4044
|
+
dirty = true;
|
|
4045
|
+
renderNow();
|
|
4046
|
+
if (endedNaturally && acc.getEvents().length === 0) {
|
|
4047
|
+
if (!options.json) {
|
|
4048
|
+
console.error("No valid events found.");
|
|
4049
|
+
}
|
|
4050
|
+
process.exitCode = 1;
|
|
4051
|
+
}
|
|
4052
|
+
stop = true;
|
|
4053
|
+
await renderTask;
|
|
4054
|
+
} catch (e) {
|
|
4055
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
4056
|
+
console.error(`[AgentInspect] tail failed: ${msg}`);
|
|
4057
|
+
process.exitCode = 1;
|
|
4058
|
+
}
|
|
4059
|
+
}
|
|
4060
|
+
function parseExportFormat(s) {
|
|
4061
|
+
const v = (s ?? "markdown").trim().toLowerCase();
|
|
4062
|
+
if (v === "markdown" || v === "html" || v === "openinference" || v === "otlp-json") {
|
|
4063
|
+
return v;
|
|
4064
|
+
}
|
|
4065
|
+
throw new Error(
|
|
4066
|
+
`Unsupported --format "${s ?? ""}". Use markdown, html, openinference, or otlp-json.`
|
|
4067
|
+
);
|
|
4068
|
+
}
|
|
4069
|
+
async function exportCommand(runId, options = {}) {
|
|
4070
|
+
const id = typeof runId === "string" && runId.trim() !== "" ? runId.trim() : "";
|
|
4071
|
+
if (id === "") {
|
|
4072
|
+
console.error("Run id is required");
|
|
4073
|
+
process.exitCode = 1;
|
|
4074
|
+
return;
|
|
4075
|
+
}
|
|
4076
|
+
let format;
|
|
4077
|
+
try {
|
|
4078
|
+
format = parseExportFormat(options.format);
|
|
4079
|
+
} catch (e) {
|
|
4080
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
4081
|
+
console.error(msg);
|
|
4082
|
+
process.exitCode = 1;
|
|
4083
|
+
return;
|
|
4084
|
+
}
|
|
4085
|
+
const traceDir = resolveTraceDir({ dir: options.dir });
|
|
4086
|
+
let events;
|
|
4087
|
+
try {
|
|
4088
|
+
events = await readTraceEvents(id, traceDir);
|
|
4089
|
+
} catch (e) {
|
|
4090
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
4091
|
+
console.error(`[AgentInspect] export failed: ${msg}`);
|
|
4092
|
+
process.exitCode = 1;
|
|
4093
|
+
return;
|
|
4094
|
+
}
|
|
4095
|
+
if (events.length === 0) {
|
|
4096
|
+
console.error(`Run not found or trace is empty: ${id}
|
|
4097
|
+
Trace directory: ${traceDir}`);
|
|
4098
|
+
process.exitCode = 1;
|
|
4099
|
+
return;
|
|
4100
|
+
}
|
|
4101
|
+
let tree;
|
|
4102
|
+
try {
|
|
4103
|
+
tree = manualTraceEventsToRunTree(events);
|
|
4104
|
+
} catch (e) {
|
|
4105
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
4106
|
+
console.error(`[AgentInspect] export failed: ${msg}`);
|
|
4107
|
+
process.exitCode = 1;
|
|
4108
|
+
return;
|
|
4109
|
+
}
|
|
4110
|
+
const exportOpts = {
|
|
4111
|
+
format,
|
|
4112
|
+
includeMetadata: options.noMetadata === true ? false : true,
|
|
4113
|
+
includeAttributes: options.includeAttributes === true,
|
|
4114
|
+
includeErrors: options.noErrors === true ? false : true,
|
|
4115
|
+
pretty: true,
|
|
4116
|
+
redacted: true,
|
|
4117
|
+
maxAttributeLength: 500
|
|
4118
|
+
};
|
|
4119
|
+
const result = exportRunTree(tree, exportOpts);
|
|
4120
|
+
const validation = options.validate === true ? validateExport(result) : void 0;
|
|
4121
|
+
if (validation !== void 0 && !validation.ok) {
|
|
4122
|
+
process.exitCode = 1;
|
|
4123
|
+
}
|
|
4124
|
+
const outPath = options.output !== void 0 && options.output.trim() !== "" ? path__default.default.resolve(options.output.trim()) : void 0;
|
|
4125
|
+
if (outPath !== void 0) {
|
|
4126
|
+
await promises.mkdir(path__default.default.dirname(outPath), { recursive: true });
|
|
4127
|
+
await promises.writeFile(outPath, result.content, "utf-8");
|
|
4128
|
+
const vlabel = validation !== void 0 ? validation.ok ? "ok" : "failed" : "skipped";
|
|
4129
|
+
console.log(`Wrote ${result.fileExtension} export to ${outPath} (validation: ${vlabel})`);
|
|
4130
|
+
if (validation !== void 0 && !validation.ok) {
|
|
4131
|
+
console.error("Validation errors:", validation.errors.join("; "));
|
|
4132
|
+
}
|
|
4133
|
+
}
|
|
4134
|
+
if (options.json === true) {
|
|
4135
|
+
const payload = {
|
|
4136
|
+
format: result.format,
|
|
4137
|
+
contentType: result.contentType,
|
|
4138
|
+
fileExtension: result.fileExtension,
|
|
4139
|
+
warnings: [...result.warnings, ...validation?.warnings ?? []],
|
|
4140
|
+
validation
|
|
4141
|
+
};
|
|
4142
|
+
if (outPath === void 0) {
|
|
4143
|
+
payload.content = result.content;
|
|
4144
|
+
}
|
|
4145
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
4146
|
+
if (validation !== void 0 && !validation.ok) {
|
|
4147
|
+
console.error("Validation errors:", validation.errors.join("; "));
|
|
4148
|
+
}
|
|
4149
|
+
} else if (outPath === void 0) {
|
|
4150
|
+
console.log(result.content);
|
|
4151
|
+
if (options.validate === true && validation !== void 0) {
|
|
4152
|
+
if (validation.ok) {
|
|
4153
|
+
console.error(`Validation: ok (${validation.warnings.length} warning(s))`);
|
|
4154
|
+
} else {
|
|
4155
|
+
console.error("Validation failed:", validation.errors.join("; "));
|
|
4156
|
+
}
|
|
4157
|
+
if (validation.warnings.length > 0) {
|
|
4158
|
+
console.error("Warnings:", validation.warnings.join("; "));
|
|
4159
|
+
}
|
|
4160
|
+
}
|
|
4161
|
+
}
|
|
4162
|
+
}
|
|
4163
|
+
|
|
4164
|
+
// packages/cli/src/diff.ts
|
|
4165
|
+
function parseFocus(s) {
|
|
4166
|
+
const v = (s ?? "all").trim().toLowerCase();
|
|
4167
|
+
if (v === "all" || v === "errors" || v === "structure" || v === "outputs") {
|
|
4168
|
+
return v;
|
|
4169
|
+
}
|
|
4170
|
+
throw new Error(
|
|
4171
|
+
`Invalid --focus "${s ?? ""}". Use all, errors, structure, or outputs.`
|
|
4172
|
+
);
|
|
4173
|
+
}
|
|
4174
|
+
function parseCheck(s) {
|
|
4175
|
+
const v = (s ?? "all").trim().toLowerCase();
|
|
4176
|
+
if (v === "all" || v === "structure" || v === "outputs" || v === "errors" || v === "timing") {
|
|
4177
|
+
return v;
|
|
4178
|
+
}
|
|
4179
|
+
throw new Error(
|
|
4180
|
+
`Invalid --check "${s ?? ""}". Use all, structure, outputs, errors, or timing.`
|
|
4181
|
+
);
|
|
4182
|
+
}
|
|
4183
|
+
async function diffCommand(leftRunId, rightRunId, options = {}) {
|
|
4184
|
+
const leftId = typeof leftRunId === "string" && leftRunId.trim() !== "" ? leftRunId.trim() : "";
|
|
4185
|
+
const rightId = typeof rightRunId === "string" && rightRunId.trim() !== "" ? rightRunId.trim() : "";
|
|
4186
|
+
if (leftId === "" || rightId === "") {
|
|
4187
|
+
console.error("Both left and right run ids are required");
|
|
4188
|
+
process.exitCode = 1;
|
|
4189
|
+
return;
|
|
4190
|
+
}
|
|
4191
|
+
let focus;
|
|
4192
|
+
let check;
|
|
4193
|
+
try {
|
|
4194
|
+
focus = parseFocus(options.focus);
|
|
4195
|
+
check = parseCheck(options.check);
|
|
4196
|
+
} catch (e) {
|
|
4197
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
4198
|
+
console.error(msg);
|
|
4199
|
+
process.exitCode = 1;
|
|
4200
|
+
return;
|
|
4201
|
+
}
|
|
4202
|
+
let durationThresholdMs;
|
|
4203
|
+
if (options.durationThreshold !== void 0 && options.durationThreshold.trim() !== "") {
|
|
4204
|
+
try {
|
|
4205
|
+
durationThresholdMs = parseDuration(options.durationThreshold.trim());
|
|
4206
|
+
} catch (e) {
|
|
4207
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
4208
|
+
console.error(`Invalid --duration-threshold: ${msg}`);
|
|
4209
|
+
process.exitCode = 1;
|
|
4210
|
+
return;
|
|
4211
|
+
}
|
|
4212
|
+
}
|
|
4213
|
+
const traceDir = resolveTraceDir({ dir: options.dir });
|
|
4214
|
+
let leftEvents;
|
|
4215
|
+
let rightEvents;
|
|
4216
|
+
try {
|
|
4217
|
+
leftEvents = await readTraceEvents(leftId, traceDir);
|
|
4218
|
+
rightEvents = await readTraceEvents(rightId, traceDir);
|
|
4219
|
+
} catch (e) {
|
|
4220
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
4221
|
+
console.error(`[AgentInspect] diff failed: ${msg}`);
|
|
4222
|
+
process.exitCode = 1;
|
|
4223
|
+
return;
|
|
4224
|
+
}
|
|
4225
|
+
if (leftEvents.length === 0) {
|
|
4226
|
+
console.error(
|
|
4227
|
+
`Run not found or trace is empty: ${leftId}
|
|
4228
|
+
Trace directory: ${traceDir}`
|
|
4229
|
+
);
|
|
4230
|
+
process.exitCode = 1;
|
|
4231
|
+
return;
|
|
4232
|
+
}
|
|
4233
|
+
if (rightEvents.length === 0) {
|
|
4234
|
+
console.error(
|
|
4235
|
+
`Run not found or trace is empty: ${rightId}
|
|
4236
|
+
Trace directory: ${traceDir}`
|
|
4237
|
+
);
|
|
4238
|
+
process.exitCode = 1;
|
|
4239
|
+
return;
|
|
4240
|
+
}
|
|
4241
|
+
const diffOpts = {
|
|
4242
|
+
ignoreDuration: options.ignoreDuration === true,
|
|
4243
|
+
durationThresholdMs,
|
|
4244
|
+
focus,
|
|
4245
|
+
check
|
|
4246
|
+
};
|
|
4247
|
+
let result;
|
|
4248
|
+
try {
|
|
4249
|
+
result = diffTraceEvents(leftEvents, rightEvents, diffOpts);
|
|
4250
|
+
} catch (e) {
|
|
4251
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
4252
|
+
console.error(`[AgentInspect] diff failed: ${msg}`);
|
|
4253
|
+
process.exitCode = 1;
|
|
4254
|
+
return;
|
|
4255
|
+
}
|
|
4256
|
+
if (options.json === true) {
|
|
4257
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4258
|
+
return;
|
|
4259
|
+
}
|
|
4260
|
+
console.log(
|
|
4261
|
+
renderRunDiff(result, {
|
|
4262
|
+
verbose: options.verbose === true})
|
|
4263
|
+
);
|
|
4264
|
+
}
|
|
4265
|
+
|
|
846
4266
|
// packages/cli/src/index.ts
|
|
847
4267
|
function runCommand(action) {
|
|
848
4268
|
void action().catch((error) => {
|
|
@@ -857,18 +4277,100 @@ function createCliProgram() {
|
|
|
857
4277
|
new commander.Option("--status <status>", "filter by run status").choices([
|
|
858
4278
|
"running",
|
|
859
4279
|
"success",
|
|
860
|
-
"error"
|
|
4280
|
+
"error",
|
|
4281
|
+
"unknown"
|
|
861
4282
|
])
|
|
862
|
-
).
|
|
4283
|
+
).option("--name <query>", "filter by run name or id (substring match)").option(
|
|
4284
|
+
"--since <duration>",
|
|
4285
|
+
"only include runs since a duration (e.g. 30s, 5m, 2h, 7d)"
|
|
4286
|
+
).option("--json", "print runs as JSON").action(
|
|
863
4287
|
(opts) => {
|
|
864
4288
|
runCommand(() => list(opts));
|
|
865
4289
|
}
|
|
866
4290
|
);
|
|
867
|
-
program.command("view").description("View a single run trace").argument("<run-id>", "run id (e.g. from list output)").option("--dir <path>", "trace directory").option("--verbose", "show extra detail (types, metadata, error stacks)").option("--json", "print raw trace events as JSON").
|
|
4291
|
+
program.command("view").description("View a single run trace").argument("<run-id>", "run id (e.g. from list output)").option("--dir <path>", "trace directory").option("--summary", "print a run summary (counts, duration, max depth)").option("--metadata", "print trace metadata (file path/size, timestamps)").option("--errors-only", "show only error events / failed steps").option("--verbose", "show extra detail (types, metadata, error stacks)").option("--json", "print raw trace events as JSON").option(
|
|
4292
|
+
"--tui",
|
|
4293
|
+
"open optional interactive TUI viewer (requires @agent-inspect/tui)"
|
|
4294
|
+
).action(
|
|
868
4295
|
(runId, opts) => {
|
|
869
4296
|
runCommand(() => view(runId, opts));
|
|
870
4297
|
}
|
|
871
4298
|
);
|
|
4299
|
+
program.command("clean").description("Safely delete old AgentInspect run traces").option("--dir <path>", "trace directory").option(
|
|
4300
|
+
"--older-than <duration>",
|
|
4301
|
+
"delete runs older than a duration (e.g. 30s, 5m, 2h, 7d)"
|
|
4302
|
+
).option("--keep <count>", "keep N most recent runs (delete the rest)").option("--dry-run", "print what would be deleted (no changes)").option("--yes", "skip confirmation prompt").action(
|
|
4303
|
+
(opts) => {
|
|
4304
|
+
runCommand(() => clean(opts));
|
|
4305
|
+
}
|
|
4306
|
+
);
|
|
4307
|
+
program.command("logs").description("Parse structured logs into execution trees").argument("<file>", "path to log file").addOption(
|
|
4308
|
+
new commander.Option("--format <format>", "log format").choices([
|
|
4309
|
+
"auto",
|
|
4310
|
+
"json",
|
|
4311
|
+
"log4js"
|
|
4312
|
+
])
|
|
4313
|
+
).option("--config <path>", "path to log ingest config (JSON)").option(
|
|
4314
|
+
"--run-id-key <keys>",
|
|
4315
|
+
"override run id keys (comma-separated, e.g. decisionId,requestId,jobId)"
|
|
4316
|
+
).option("--event-key <key>", "override event key").option("--timestamp-key <key>", "override timestamp key").option("--message-key <key>", "override message key").option("--level-key <key>", "override level key").option("--parent-id-key <key>", "override parent id key").option("--duration-key <key>", "override duration key").option("--status-key <key>", "override status key").option("--json", "print result as JSON").option("--summary", "include summary section in human output").addOption(
|
|
4317
|
+
new commander.Option("--warnings <mode>", "warning output mode").choices([
|
|
4318
|
+
"summary",
|
|
4319
|
+
"all",
|
|
4320
|
+
"none"
|
|
4321
|
+
])
|
|
4322
|
+
).option("--verbose", "show more detail (reserved for future)").option("--no-color", "disable color output").action((file, opts) => {
|
|
4323
|
+
runCommand(() => logs(file, opts));
|
|
4324
|
+
});
|
|
4325
|
+
program.command("tail").description("Live tail structured logs into execution trees").option("--file <path>", "tail a log file (default: read from stdin)").addOption(
|
|
4326
|
+
new commander.Option("--format <format>", "log format").choices([
|
|
4327
|
+
"auto",
|
|
4328
|
+
"json",
|
|
4329
|
+
"log4js"
|
|
4330
|
+
])
|
|
4331
|
+
).option("--config <path>", "path to log ingest config (JSON)").option(
|
|
4332
|
+
"--run-id-key <keys>",
|
|
4333
|
+
"override run id keys (comma-separated, e.g. decisionId,requestId,jobId)"
|
|
4334
|
+
).option("--event-key <key>", "override event key").option("--timestamp-key <key>", "override timestamp key").option("--message-key <key>", "override message key").option("--level-key <key>", "override level key").option("--parent-id-key <key>", "override parent id key").option("--duration-key <key>", "override duration key").option("--status-key <key>", "override status key").addOption(
|
|
4335
|
+
new commander.Option("--warnings <mode>", "warning output mode").choices([
|
|
4336
|
+
"summary",
|
|
4337
|
+
"all",
|
|
4338
|
+
"none"
|
|
4339
|
+
])
|
|
4340
|
+
).option("--refresh <ms>", "minimum time between renders (ms)").option("--once", "read once and exit (for --file)").option("--json", "print newline-delimited JSON updates").option("--no-clear", "do not clear screen between renders").option("--verbose", "show more detail (reserved for future)").option("--no-color", "disable color output").action((opts) => {
|
|
4341
|
+
runCommand(() => tail(opts));
|
|
4342
|
+
});
|
|
4343
|
+
program.command("export").description("Export a manual trace run (Markdown, HTML, OpenInference-compatible JSON, OTLP JSON)").argument("<run-id>", "run id (e.g. from list output)").option("--dir <path>", "trace directory").addOption(
|
|
4344
|
+
new commander.Option("--format <format>", "export format (default: markdown)").choices([
|
|
4345
|
+
"markdown",
|
|
4346
|
+
"html",
|
|
4347
|
+
"openinference",
|
|
4348
|
+
"otlp-json"
|
|
4349
|
+
])
|
|
4350
|
+
).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").action((runId, opts) => {
|
|
4351
|
+
runCommand(() => exportCommand(runId, opts));
|
|
4352
|
+
});
|
|
4353
|
+
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(
|
|
4354
|
+
"--duration-threshold <duration>",
|
|
4355
|
+
"ignore duration deltas at or below this (e.g. 500ms, 2s, 1m)"
|
|
4356
|
+
).addOption(
|
|
4357
|
+
new commander.Option("--focus <scope>", "limit categories shown").choices([
|
|
4358
|
+
"all",
|
|
4359
|
+
"errors",
|
|
4360
|
+
"structure",
|
|
4361
|
+
"outputs"
|
|
4362
|
+
])
|
|
4363
|
+
).addOption(
|
|
4364
|
+
new commander.Option("--check <scope>", "limit categories compared").choices([
|
|
4365
|
+
"all",
|
|
4366
|
+
"structure",
|
|
4367
|
+
"outputs",
|
|
4368
|
+
"errors",
|
|
4369
|
+
"timing"
|
|
4370
|
+
])
|
|
4371
|
+
).option("--verbose", "show more left/right detail").action((leftRunId, rightRunId, opts) => {
|
|
4372
|
+
runCommand(() => diffCommand(leftRunId, rightRunId, opts));
|
|
4373
|
+
});
|
|
872
4374
|
return program;
|
|
873
4375
|
}
|
|
874
4376
|
function isPrimaryModule() {
|