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
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var promises = require('fs/promises');
|
|
4
|
+
var crypto = require('crypto');
|
|
5
|
+
var nanoid = require('nanoid');
|
|
4
6
|
var os = require('os');
|
|
5
7
|
var path = require('path');
|
|
6
|
-
var nanoid = require('nanoid');
|
|
7
8
|
var async_hooks = require('async_hooks');
|
|
8
|
-
var
|
|
9
|
+
var fs = require('fs');
|
|
10
|
+
var readline = require('readline');
|
|
11
|
+
var chalk2 = require('chalk');
|
|
9
12
|
|
|
10
13
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
14
|
|
|
15
|
+
var crypto__default = /*#__PURE__*/_interopDefault(crypto);
|
|
12
16
|
var os__default = /*#__PURE__*/_interopDefault(os);
|
|
13
17
|
var path__default = /*#__PURE__*/_interopDefault(path);
|
|
14
|
-
var
|
|
18
|
+
var chalk2__default = /*#__PURE__*/_interopDefault(chalk2);
|
|
15
19
|
|
|
16
20
|
// packages/core/src/types.ts
|
|
17
21
|
var STEP_TYPES = [
|
|
@@ -59,6 +63,1002 @@ function isTraceEvent(value) {
|
|
|
59
63
|
return false;
|
|
60
64
|
}
|
|
61
65
|
}
|
|
66
|
+
function isRecord2(v) {
|
|
67
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
68
|
+
}
|
|
69
|
+
function isNonEmptyStringArray(v) {
|
|
70
|
+
return Array.isArray(v) && v.length > 0 && v.every((x) => typeof x === "string" && x.trim() !== "");
|
|
71
|
+
}
|
|
72
|
+
function validateRedact(redact) {
|
|
73
|
+
if (!Array.isArray(redact)) {
|
|
74
|
+
throw new Error("Invalid config: redact must be an array");
|
|
75
|
+
}
|
|
76
|
+
for (const r of redact) {
|
|
77
|
+
if (typeof r === "string") continue;
|
|
78
|
+
if (!isRecord2(r)) {
|
|
79
|
+
throw new Error("Invalid config: redact entries must be strings or objects");
|
|
80
|
+
}
|
|
81
|
+
if (typeof r.key !== "string" || r.key.trim() === "") {
|
|
82
|
+
throw new Error("Invalid config: redact.key must be a non-empty string");
|
|
83
|
+
}
|
|
84
|
+
if (r.strategy !== "full" && r.strategy !== "prefix" && r.strategy !== "hash") {
|
|
85
|
+
throw new Error(
|
|
86
|
+
`Invalid config: redact.strategy must be one of full, prefix, hash (got ${String(
|
|
87
|
+
r.strategy
|
|
88
|
+
)})`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
if (r.keep !== void 0 && (typeof r.keep !== "number" || !Number.isFinite(r.keep) || r.keep < 0)) {
|
|
92
|
+
throw new Error("Invalid config: redact.keep must be a non-negative number when provided");
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function validateMappings(mappings) {
|
|
97
|
+
if (!isRecord2(mappings)) {
|
|
98
|
+
throw new Error("Invalid config: mappings must be an object");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
var DEFAULT_LOG_INGEST_CONFIG = {
|
|
102
|
+
runIdKeys: ["runId", "traceId", "requestId", "decisionId", "jobId"],
|
|
103
|
+
eventKey: "event",
|
|
104
|
+
timestampKey: "timestamp",
|
|
105
|
+
messageKey: "message",
|
|
106
|
+
levelKey: "level",
|
|
107
|
+
heuristicWindowMs: 2e3,
|
|
108
|
+
mappings: {
|
|
109
|
+
"*.error": { kind: "ERROR", status: "error" },
|
|
110
|
+
"*.failed": { kind: "ERROR", status: "error" },
|
|
111
|
+
"*.llm.*": { kind: "LLM" },
|
|
112
|
+
"*.tool.*": { kind: "TOOL" },
|
|
113
|
+
"*.agent.*": { kind: "AGENT" },
|
|
114
|
+
"*.retriever.*": { kind: "RETRIEVER" },
|
|
115
|
+
"*.result.*": { kind: "RESULT" }
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
function mergeLogIngestConfig(base, override) {
|
|
119
|
+
const merged = {
|
|
120
|
+
...base,
|
|
121
|
+
...override,
|
|
122
|
+
mappings: {
|
|
123
|
+
...base.mappings ?? {},
|
|
124
|
+
...override.mappings ?? {}
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
return merged;
|
|
128
|
+
}
|
|
129
|
+
async function loadLogIngestConfig(configPath) {
|
|
130
|
+
if (configPath === void 0 || configPath.trim() === "") {
|
|
131
|
+
return DEFAULT_LOG_INGEST_CONFIG;
|
|
132
|
+
}
|
|
133
|
+
let rawText;
|
|
134
|
+
try {
|
|
135
|
+
rawText = await promises.readFile(configPath, "utf-8");
|
|
136
|
+
} catch (e) {
|
|
137
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
138
|
+
throw new Error(`Failed to read config file: ${configPath} (${msg})`);
|
|
139
|
+
}
|
|
140
|
+
let parsed;
|
|
141
|
+
try {
|
|
142
|
+
parsed = JSON.parse(rawText);
|
|
143
|
+
} catch (e) {
|
|
144
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
145
|
+
throw new Error(`Invalid JSON in config file: ${configPath} (${msg})`);
|
|
146
|
+
}
|
|
147
|
+
if (!isRecord2(parsed)) {
|
|
148
|
+
throw new Error("Invalid config: expected a JSON object at top-level");
|
|
149
|
+
}
|
|
150
|
+
const user = parsed;
|
|
151
|
+
if (user.runIdKeys !== void 0 && !isNonEmptyStringArray(user.runIdKeys)) {
|
|
152
|
+
throw new Error("Invalid config: runIdKeys must be a non-empty array of strings");
|
|
153
|
+
}
|
|
154
|
+
if (user.eventKey !== void 0 && (typeof user.eventKey !== "string" || user.eventKey.trim() === "")) {
|
|
155
|
+
throw new Error("Invalid config: eventKey must be a non-empty string");
|
|
156
|
+
}
|
|
157
|
+
for (const k of [
|
|
158
|
+
"timestampKey",
|
|
159
|
+
"messageKey",
|
|
160
|
+
"levelKey",
|
|
161
|
+
"parentIdKey",
|
|
162
|
+
"durationKey",
|
|
163
|
+
"statusKey"
|
|
164
|
+
]) {
|
|
165
|
+
const v = user[k];
|
|
166
|
+
if (v !== void 0 && (typeof v !== "string" || v.trim() === "")) {
|
|
167
|
+
throw new Error(`Invalid config: ${k} must be a non-empty string when provided`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (user.mappings !== void 0) {
|
|
171
|
+
validateMappings(user.mappings);
|
|
172
|
+
}
|
|
173
|
+
if (user.redact !== void 0) {
|
|
174
|
+
validateRedact(user.redact);
|
|
175
|
+
}
|
|
176
|
+
if (user.heuristicWindowMs !== void 0 && (typeof user.heuristicWindowMs !== "number" || !Number.isFinite(user.heuristicWindowMs) || user.heuristicWindowMs < 0)) {
|
|
177
|
+
throw new Error("Invalid config: heuristicWindowMs must be a non-negative number when provided");
|
|
178
|
+
}
|
|
179
|
+
if (user.redact && JSON.stringify(user.redact).includes("=>")) {
|
|
180
|
+
throw new Error("Invalid config: function strings are not supported in redact rules");
|
|
181
|
+
}
|
|
182
|
+
return mergeLogIngestConfig(DEFAULT_LOG_INGEST_CONFIG, user);
|
|
183
|
+
}
|
|
184
|
+
function isRecord3(v) {
|
|
185
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
186
|
+
}
|
|
187
|
+
var JsonLogParser = class {
|
|
188
|
+
parseLines(lines, filePath) {
|
|
189
|
+
const records = [];
|
|
190
|
+
const warnings = [];
|
|
191
|
+
for (let i = 0; i < lines.length; i++) {
|
|
192
|
+
const lineNumber = i + 1;
|
|
193
|
+
const raw = lines[i] ?? "";
|
|
194
|
+
const trimmed = raw.trim();
|
|
195
|
+
if (trimmed === "") continue;
|
|
196
|
+
let parsed;
|
|
197
|
+
try {
|
|
198
|
+
parsed = JSON.parse(trimmed);
|
|
199
|
+
} catch {
|
|
200
|
+
warnings.push({
|
|
201
|
+
code: "MALFORMED_JSON",
|
|
202
|
+
message: "Malformed JSON log line",
|
|
203
|
+
file: filePath,
|
|
204
|
+
line: lineNumber,
|
|
205
|
+
raw: trimmed.slice(0, 500)
|
|
206
|
+
});
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (!isRecord3(parsed)) {
|
|
210
|
+
warnings.push({
|
|
211
|
+
code: "MALFORMED_JSON",
|
|
212
|
+
message: "JSON log line must be an object",
|
|
213
|
+
file: filePath,
|
|
214
|
+
line: lineNumber,
|
|
215
|
+
raw: trimmed.slice(0, 500)
|
|
216
|
+
});
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
records.push({
|
|
220
|
+
raw: parsed,
|
|
221
|
+
file: filePath,
|
|
222
|
+
line: lineNumber,
|
|
223
|
+
sourceType: "json-log"
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
return { records, warnings };
|
|
227
|
+
}
|
|
228
|
+
async parseFile(filePath) {
|
|
229
|
+
let text;
|
|
230
|
+
try {
|
|
231
|
+
text = await promises.readFile(filePath, "utf-8");
|
|
232
|
+
} catch (e) {
|
|
233
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
234
|
+
throw new Error(`Failed to read log file: ${filePath} (${msg})`);
|
|
235
|
+
}
|
|
236
|
+
const lines = text.split(/\r?\n/);
|
|
237
|
+
return this.parseLines(lines, filePath);
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
function isRecord4(v) {
|
|
241
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
242
|
+
}
|
|
243
|
+
function findLastJsonObjectSubstring(line) {
|
|
244
|
+
let last;
|
|
245
|
+
for (let i = 0; i < line.length; i++) {
|
|
246
|
+
if (line[i] !== "{") continue;
|
|
247
|
+
let depth = 0;
|
|
248
|
+
let inString = false;
|
|
249
|
+
let escape = false;
|
|
250
|
+
for (let j = i; j < line.length; j++) {
|
|
251
|
+
const ch = line[j];
|
|
252
|
+
if (inString) {
|
|
253
|
+
if (escape) {
|
|
254
|
+
escape = false;
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
if (ch === "\\") {
|
|
258
|
+
escape = true;
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
if (ch === '"') {
|
|
262
|
+
inString = false;
|
|
263
|
+
}
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
if (ch === '"') {
|
|
267
|
+
inString = true;
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
if (ch === "{") depth += 1;
|
|
271
|
+
if (ch === "}") depth -= 1;
|
|
272
|
+
if (depth === 0) {
|
|
273
|
+
last = { start: i, end: j + 1 };
|
|
274
|
+
i = j;
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
if (depth < 0) break;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
if (!last) return void 0;
|
|
281
|
+
return line.slice(last.start, last.end);
|
|
282
|
+
}
|
|
283
|
+
var Log4jsParser = class {
|
|
284
|
+
parseLines(lines, filePath) {
|
|
285
|
+
const records = [];
|
|
286
|
+
const warnings = [];
|
|
287
|
+
for (let i = 0; i < lines.length; i++) {
|
|
288
|
+
const lineNumber = i + 1;
|
|
289
|
+
const rawLine = lines[i] ?? "";
|
|
290
|
+
const trimmed = rawLine.trim();
|
|
291
|
+
if (trimmed === "") continue;
|
|
292
|
+
const jsonText = findLastJsonObjectSubstring(trimmed);
|
|
293
|
+
if (!jsonText) {
|
|
294
|
+
warnings.push({
|
|
295
|
+
code: "UNSUPPORTED_LOG4JS_PAYLOAD",
|
|
296
|
+
message: "No embedded JSON object found in log4js line",
|
|
297
|
+
file: filePath,
|
|
298
|
+
line: lineNumber,
|
|
299
|
+
raw: trimmed.slice(0, 500)
|
|
300
|
+
});
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
let parsed;
|
|
304
|
+
try {
|
|
305
|
+
parsed = JSON.parse(jsonText);
|
|
306
|
+
} catch {
|
|
307
|
+
warnings.push({
|
|
308
|
+
code: "MALFORMED_JSON",
|
|
309
|
+
message: "Malformed embedded JSON object in log4js line",
|
|
310
|
+
file: filePath,
|
|
311
|
+
line: lineNumber,
|
|
312
|
+
raw: jsonText.slice(0, 500)
|
|
313
|
+
});
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
if (!isRecord4(parsed)) {
|
|
317
|
+
warnings.push({
|
|
318
|
+
code: "UNSUPPORTED_LOG4JS_PAYLOAD",
|
|
319
|
+
message: "Embedded JSON payload must be an object",
|
|
320
|
+
file: filePath,
|
|
321
|
+
line: lineNumber,
|
|
322
|
+
raw: jsonText.slice(0, 500)
|
|
323
|
+
});
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
records.push({
|
|
327
|
+
raw: parsed,
|
|
328
|
+
file: filePath,
|
|
329
|
+
line: lineNumber,
|
|
330
|
+
sourceType: "log4js"
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
return { records, warnings };
|
|
334
|
+
}
|
|
335
|
+
async parseFile(filePath) {
|
|
336
|
+
let text;
|
|
337
|
+
try {
|
|
338
|
+
text = await promises.readFile(filePath, "utf-8");
|
|
339
|
+
} catch (e) {
|
|
340
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
341
|
+
throw new Error(`Failed to read log file: ${filePath} (${msg})`);
|
|
342
|
+
}
|
|
343
|
+
const lines = text.split(/\r?\n/);
|
|
344
|
+
return this.parseLines(lines, filePath);
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
// packages/core/src/logs/mapping.ts
|
|
349
|
+
function wildcardMatch(pattern, value) {
|
|
350
|
+
if (pattern === value) return true;
|
|
351
|
+
if (!pattern.includes("*")) return false;
|
|
352
|
+
const parts = pattern.split("*");
|
|
353
|
+
let idx = 0;
|
|
354
|
+
for (let i = 0; i < parts.length; i++) {
|
|
355
|
+
const part = parts[i];
|
|
356
|
+
if (part === "") continue;
|
|
357
|
+
const found = value.indexOf(part, idx);
|
|
358
|
+
if (found === -1) return false;
|
|
359
|
+
if (i === 0 && !pattern.startsWith("*") && found !== 0) return false;
|
|
360
|
+
idx = found + part.length;
|
|
361
|
+
}
|
|
362
|
+
if (!pattern.endsWith("*")) {
|
|
363
|
+
const last = parts[parts.length - 1];
|
|
364
|
+
if (last !== "" && !value.endsWith(last)) return false;
|
|
365
|
+
if (last === "" && !value.endsWith(parts[parts.length - 2] ?? "")) return false;
|
|
366
|
+
}
|
|
367
|
+
return true;
|
|
368
|
+
}
|
|
369
|
+
function matchMapping(eventName, mappings) {
|
|
370
|
+
if (!mappings) return void 0;
|
|
371
|
+
if (mappings[eventName]) return mappings[eventName];
|
|
372
|
+
let bestKey;
|
|
373
|
+
let bestScore = -1;
|
|
374
|
+
for (const key of Object.keys(mappings)) {
|
|
375
|
+
if (!key.includes("*")) continue;
|
|
376
|
+
if (!wildcardMatch(key, eventName)) continue;
|
|
377
|
+
const score = key.replaceAll("*", "").length;
|
|
378
|
+
if (score > bestScore) {
|
|
379
|
+
bestScore = score;
|
|
380
|
+
bestKey = key;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return bestKey ? mappings[bestKey] : void 0;
|
|
384
|
+
}
|
|
385
|
+
var DEFAULT_REDACT_KEYS = [
|
|
386
|
+
"authorization",
|
|
387
|
+
"cookie",
|
|
388
|
+
"token",
|
|
389
|
+
"apiKey",
|
|
390
|
+
"password",
|
|
391
|
+
"secret",
|
|
392
|
+
"email"
|
|
393
|
+
];
|
|
394
|
+
function isRecord5(v) {
|
|
395
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
396
|
+
}
|
|
397
|
+
function toKey(s) {
|
|
398
|
+
return s.toLowerCase();
|
|
399
|
+
}
|
|
400
|
+
function stableHash(value) {
|
|
401
|
+
const h = crypto__default.default.createHash("sha256").update(value, "utf8").digest("hex");
|
|
402
|
+
return h.slice(0, 8);
|
|
403
|
+
}
|
|
404
|
+
function compileRules(rules) {
|
|
405
|
+
const out = /* @__PURE__ */ new Map();
|
|
406
|
+
const set = (r) => {
|
|
407
|
+
const k = toKey(r.key);
|
|
408
|
+
out.set(k, { ...r, key: k });
|
|
409
|
+
};
|
|
410
|
+
for (const k of DEFAULT_REDACT_KEYS) {
|
|
411
|
+
set({ key: k, strategy: "full" });
|
|
412
|
+
}
|
|
413
|
+
for (const r of rules ?? []) {
|
|
414
|
+
if (typeof r === "string") {
|
|
415
|
+
set({ key: r, strategy: "full" });
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
const key = r.key;
|
|
419
|
+
if (r.strategy === "full") set({ key, strategy: "full" });
|
|
420
|
+
if (r.strategy === "hash") set({ key, strategy: "hash" });
|
|
421
|
+
if (r.strategy === "prefix") {
|
|
422
|
+
set({ key, strategy: "prefix", keep: typeof r.keep === "number" ? r.keep : 8 });
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return [...out.values()];
|
|
426
|
+
}
|
|
427
|
+
var Redactor = class {
|
|
428
|
+
#rules;
|
|
429
|
+
constructor(options) {
|
|
430
|
+
this.#rules = compileRules(options?.rules);
|
|
431
|
+
}
|
|
432
|
+
redactValue(key, value) {
|
|
433
|
+
const k = toKey(key);
|
|
434
|
+
const rule = this.#rules.find((r) => r.key === k);
|
|
435
|
+
if (!rule) {
|
|
436
|
+
return this.#redactNested(value);
|
|
437
|
+
}
|
|
438
|
+
if (rule.strategy === "full") return "[REDACTED]";
|
|
439
|
+
const asString = typeof value === "string" ? value : typeof value === "number" || typeof value === "boolean" || typeof value === "bigint" ? String(value) : void 0;
|
|
440
|
+
if (rule.strategy === "prefix") {
|
|
441
|
+
if (asString === void 0) return "[REDACTED]";
|
|
442
|
+
const keep = Math.max(0, Math.floor(rule.keep));
|
|
443
|
+
return asString.length <= keep ? `${asString}\u2026` : `${asString.slice(0, keep)}\u2026`;
|
|
444
|
+
}
|
|
445
|
+
if (rule.strategy === "hash") {
|
|
446
|
+
if (asString === void 0) return "[HASH:unknown]";
|
|
447
|
+
return `[HASH:${stableHash(asString)}]`;
|
|
448
|
+
}
|
|
449
|
+
return this.#redactNested(value);
|
|
450
|
+
}
|
|
451
|
+
redactRecord(record) {
|
|
452
|
+
const out = {};
|
|
453
|
+
for (const [k, v] of Object.entries(record)) {
|
|
454
|
+
out[k] = this.redactValue(k, v);
|
|
455
|
+
}
|
|
456
|
+
return out;
|
|
457
|
+
}
|
|
458
|
+
#redactNested(value) {
|
|
459
|
+
if (Array.isArray(value)) {
|
|
460
|
+
return value.map((v) => this.#redactNested(v));
|
|
461
|
+
}
|
|
462
|
+
if (isRecord5(value)) {
|
|
463
|
+
const out = {};
|
|
464
|
+
for (const [k, v] of Object.entries(value)) {
|
|
465
|
+
out[k] = this.redactValue(k, v);
|
|
466
|
+
}
|
|
467
|
+
return out;
|
|
468
|
+
}
|
|
469
|
+
return value;
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
function isFiniteNumber(v) {
|
|
473
|
+
return typeof v === "number" && Number.isFinite(v);
|
|
474
|
+
}
|
|
475
|
+
function safeString(v) {
|
|
476
|
+
if (typeof v !== "string") return void 0;
|
|
477
|
+
const t = v.trim();
|
|
478
|
+
return t === "" ? void 0 : t;
|
|
479
|
+
}
|
|
480
|
+
function parseTimestamp(v) {
|
|
481
|
+
if (isFiniteNumber(v)) return v;
|
|
482
|
+
if (typeof v === "string") {
|
|
483
|
+
const t = Date.parse(v);
|
|
484
|
+
if (Number.isFinite(t)) return t;
|
|
485
|
+
}
|
|
486
|
+
return void 0;
|
|
487
|
+
}
|
|
488
|
+
function hasToken(hay, token) {
|
|
489
|
+
return hay.includes(token);
|
|
490
|
+
}
|
|
491
|
+
function inferKind(eventName) {
|
|
492
|
+
if (hasToken(eventName, ".llm.")) return "LLM";
|
|
493
|
+
if (hasToken(eventName, ".tool.")) return "TOOL";
|
|
494
|
+
if (hasToken(eventName, ".agent.")) return "AGENT";
|
|
495
|
+
if (hasToken(eventName, ".retriever.")) return "RETRIEVER";
|
|
496
|
+
if (hasToken(eventName, ".result.")) return "RESULT";
|
|
497
|
+
if (eventName.endsWith(".error") || eventName.endsWith(".failed") || eventName.includes(".error") || eventName.includes(".failed")) {
|
|
498
|
+
return "ERROR";
|
|
499
|
+
}
|
|
500
|
+
return "LOG";
|
|
501
|
+
}
|
|
502
|
+
function deriveName(eventName, kind) {
|
|
503
|
+
const parts = eventName.split(".");
|
|
504
|
+
const last = parts[parts.length - 1] ?? eventName;
|
|
505
|
+
if (kind === "LLM") return `llm:${last}`;
|
|
506
|
+
if (kind === "TOOL") return `tool:${last}`;
|
|
507
|
+
if (kind === "AGENT") return `agent:${last}`;
|
|
508
|
+
if (kind === "RESULT") return `result:${last}`;
|
|
509
|
+
if (kind === "RUN") return `run:${last}`;
|
|
510
|
+
if (kind === "RETRIEVER") return `retriever:${last}`;
|
|
511
|
+
if (kind === "ERROR") return `error:${last}`;
|
|
512
|
+
return eventName;
|
|
513
|
+
}
|
|
514
|
+
var EventNormalizer = class {
|
|
515
|
+
#config;
|
|
516
|
+
constructor(options) {
|
|
517
|
+
this.#config = options.config;
|
|
518
|
+
}
|
|
519
|
+
#normalizeInternal(record) {
|
|
520
|
+
const raw = record.raw;
|
|
521
|
+
const cfg = this.#config;
|
|
522
|
+
let runId;
|
|
523
|
+
for (const k of cfg.runIdKeys) {
|
|
524
|
+
const v = raw[k];
|
|
525
|
+
const s = safeString(v);
|
|
526
|
+
if (s) {
|
|
527
|
+
runId = s;
|
|
528
|
+
break;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
if (!runId) {
|
|
532
|
+
return {
|
|
533
|
+
warning: {
|
|
534
|
+
code: "MISSING_RUN_ID",
|
|
535
|
+
message: "Missing run id (none of runIdKeys present)",
|
|
536
|
+
file: record.file,
|
|
537
|
+
line: record.line
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
const eventName = safeString(raw[cfg.eventKey]);
|
|
542
|
+
if (!eventName) {
|
|
543
|
+
return {
|
|
544
|
+
warning: {
|
|
545
|
+
code: "MISSING_EVENT",
|
|
546
|
+
message: `Missing event name (key: ${cfg.eventKey})`,
|
|
547
|
+
file: record.file,
|
|
548
|
+
line: record.line
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
const mapping = matchMapping(eventName, cfg.mappings);
|
|
553
|
+
const tsKey = cfg.timestampKey ?? "timestamp";
|
|
554
|
+
const tsRaw = raw[tsKey];
|
|
555
|
+
const parsedTs = parseTimestamp(tsRaw);
|
|
556
|
+
const timestamp = parsedTs ?? Date.now();
|
|
557
|
+
const timestampMissing = parsedTs === void 0;
|
|
558
|
+
const parentIdKey = cfg.parentIdKey;
|
|
559
|
+
const parentId = parentIdKey ? safeString(raw[parentIdKey]) : void 0;
|
|
560
|
+
let durationMs;
|
|
561
|
+
const durationKey = cfg.durationKey;
|
|
562
|
+
if (durationKey) {
|
|
563
|
+
const v = raw[durationKey];
|
|
564
|
+
if (isFiniteNumber(v)) durationMs = v;
|
|
565
|
+
} else if (isFiniteNumber(raw.durationMs)) {
|
|
566
|
+
durationMs = raw.durationMs;
|
|
567
|
+
}
|
|
568
|
+
let status;
|
|
569
|
+
const statusKey = cfg.statusKey;
|
|
570
|
+
const statusRaw = statusKey ? safeString(raw[statusKey]) : void 0;
|
|
571
|
+
if (statusRaw === "running" || statusRaw === "ok" || statusRaw === "error") {
|
|
572
|
+
status = statusRaw;
|
|
573
|
+
} else if (mapping?.status) {
|
|
574
|
+
status = mapping.status;
|
|
575
|
+
} else if (mapping?.kind === "ERROR") {
|
|
576
|
+
status = "error";
|
|
577
|
+
} else if (eventName.includes(".failed") || eventName.includes(".error")) {
|
|
578
|
+
status = "error";
|
|
579
|
+
} else if (mapping?.startsRun || mapping?.startsStep) {
|
|
580
|
+
status = "running";
|
|
581
|
+
} else if (mapping?.endsRun || mapping?.endsStep) {
|
|
582
|
+
status = "ok";
|
|
583
|
+
}
|
|
584
|
+
const kind = mapping?.kind ?? inferKind(eventName);
|
|
585
|
+
const name = mapping?.name ?? deriveName(eventName, kind);
|
|
586
|
+
let confidence = "correlated";
|
|
587
|
+
if (parentId || mapping?.startsRun) confidence = "explicit";
|
|
588
|
+
if (timestampMissing) confidence = "unknown";
|
|
589
|
+
const omit = new Set([
|
|
590
|
+
...cfg.runIdKeys,
|
|
591
|
+
cfg.eventKey,
|
|
592
|
+
tsKey,
|
|
593
|
+
cfg.messageKey ?? "message",
|
|
594
|
+
cfg.levelKey ?? "level",
|
|
595
|
+
cfg.parentIdKey ?? "",
|
|
596
|
+
cfg.durationKey ?? "",
|
|
597
|
+
cfg.statusKey ?? "",
|
|
598
|
+
"durationMs"
|
|
599
|
+
].filter((k) => k !== ""));
|
|
600
|
+
const attributes = {};
|
|
601
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
602
|
+
if (omit.has(k)) continue;
|
|
603
|
+
attributes[k] = v;
|
|
604
|
+
}
|
|
605
|
+
const event = {
|
|
606
|
+
eventId: nanoid.nanoid(10),
|
|
607
|
+
runId,
|
|
608
|
+
...parentId ? { parentId } : {},
|
|
609
|
+
name,
|
|
610
|
+
kind,
|
|
611
|
+
timestamp,
|
|
612
|
+
...status ? { status } : {},
|
|
613
|
+
...durationMs !== void 0 ? { durationMs } : {},
|
|
614
|
+
...Object.keys(attributes).length > 0 ? { attributes } : {},
|
|
615
|
+
confidence,
|
|
616
|
+
source: {
|
|
617
|
+
type: record.sourceType,
|
|
618
|
+
file: record.file,
|
|
619
|
+
line: record.line
|
|
620
|
+
}
|
|
621
|
+
};
|
|
622
|
+
if (timestampMissing) {
|
|
623
|
+
return {
|
|
624
|
+
event,
|
|
625
|
+
warning: {
|
|
626
|
+
code: "MISSING_TIMESTAMP",
|
|
627
|
+
message: `Missing or invalid timestamp (key: ${tsKey})`,
|
|
628
|
+
file: record.file,
|
|
629
|
+
line: record.line,
|
|
630
|
+
raw: JSON.stringify(raw).slice(0, 500)
|
|
631
|
+
}
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
return { event };
|
|
635
|
+
}
|
|
636
|
+
normalize(record) {
|
|
637
|
+
const r = this.#normalizeInternal(record);
|
|
638
|
+
return r.event ?? r.warning;
|
|
639
|
+
}
|
|
640
|
+
normalizeAll(records) {
|
|
641
|
+
const out = [];
|
|
642
|
+
const warnings = [];
|
|
643
|
+
for (const r of records) {
|
|
644
|
+
const normalized = this.#normalizeInternal(r);
|
|
645
|
+
if (normalized.event) out.push(normalized.event);
|
|
646
|
+
if (normalized.warning) warnings.push(normalized.warning);
|
|
647
|
+
}
|
|
648
|
+
return { records: out, warnings };
|
|
649
|
+
}
|
|
650
|
+
};
|
|
651
|
+
|
|
652
|
+
// packages/core/src/logs/tree-builder.ts
|
|
653
|
+
function inc(map, key) {
|
|
654
|
+
map[key] = (map[key] ?? 0) + 1;
|
|
655
|
+
}
|
|
656
|
+
function computeRunStatus(events) {
|
|
657
|
+
let hasRunning = false;
|
|
658
|
+
for (const e of events) {
|
|
659
|
+
if (e.status === "error") return "error";
|
|
660
|
+
if (e.status === "running") hasRunning = true;
|
|
661
|
+
}
|
|
662
|
+
if (hasRunning) return "running";
|
|
663
|
+
return "ok";
|
|
664
|
+
}
|
|
665
|
+
var TreeBuilder = class {
|
|
666
|
+
constructor(options) {
|
|
667
|
+
void options?.config;
|
|
668
|
+
}
|
|
669
|
+
build(events) {
|
|
670
|
+
const byRun = /* @__PURE__ */ new Map();
|
|
671
|
+
for (const e of events) {
|
|
672
|
+
if (!byRun.has(e.runId)) byRun.set(e.runId, []);
|
|
673
|
+
byRun.get(e.runId).push(e);
|
|
674
|
+
}
|
|
675
|
+
const out = [];
|
|
676
|
+
for (const [runId, runEvents] of byRun.entries()) {
|
|
677
|
+
const sorted = [...runEvents].sort((a, b) => a.timestamp - b.timestamp);
|
|
678
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
679
|
+
for (const e of sorted) {
|
|
680
|
+
nodes.set(e.eventId, { event: e, children: [], depth: 0 });
|
|
681
|
+
}
|
|
682
|
+
const roots = [];
|
|
683
|
+
for (const node of nodes.values()) {
|
|
684
|
+
const parentId = node.event.parentId;
|
|
685
|
+
if (parentId && nodes.has(parentId)) {
|
|
686
|
+
nodes.get(parentId).children.push(node);
|
|
687
|
+
} else {
|
|
688
|
+
roots.push(node);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
const assignDepth = (n, depth) => {
|
|
692
|
+
n.depth = depth;
|
|
693
|
+
for (const c of n.children) assignDepth(c, depth + 1);
|
|
694
|
+
};
|
|
695
|
+
for (const r of roots) assignDepth(r, 0);
|
|
696
|
+
const confidenceBreakdown = {
|
|
697
|
+
explicit: 0,
|
|
698
|
+
correlated: 0,
|
|
699
|
+
heuristic: 0,
|
|
700
|
+
unknown: 0
|
|
701
|
+
};
|
|
702
|
+
const kinds = {};
|
|
703
|
+
for (const e of sorted) {
|
|
704
|
+
inc(confidenceBreakdown, e.confidence);
|
|
705
|
+
kinds[e.kind] = (kinds[e.kind] ?? 0) + 1;
|
|
706
|
+
}
|
|
707
|
+
const startedAt = sorted.length > 0 ? sorted[0].timestamp : void 0;
|
|
708
|
+
const endedAt = sorted.length > 0 ? sorted[sorted.length - 1].timestamp : void 0;
|
|
709
|
+
const status = computeRunStatus(sorted);
|
|
710
|
+
const durationMs = startedAt !== void 0 && endedAt !== void 0 && Number.isFinite(startedAt) && Number.isFinite(endedAt) && endedAt >= startedAt && status !== "running" ? endedAt - startedAt : void 0;
|
|
711
|
+
const name = sorted.find((e) => e.kind === "RUN")?.name;
|
|
712
|
+
out.push({
|
|
713
|
+
runId,
|
|
714
|
+
name,
|
|
715
|
+
status,
|
|
716
|
+
startedAt,
|
|
717
|
+
endedAt: status === "running" ? void 0 : endedAt,
|
|
718
|
+
durationMs,
|
|
719
|
+
children: roots,
|
|
720
|
+
metadata: {
|
|
721
|
+
totalEvents: sorted.length,
|
|
722
|
+
confidenceBreakdown,
|
|
723
|
+
kinds
|
|
724
|
+
}
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
out.sort((a, b) => (b.startedAt ?? 0) - (a.startedAt ?? 0));
|
|
728
|
+
return out;
|
|
729
|
+
}
|
|
730
|
+
};
|
|
731
|
+
|
|
732
|
+
// packages/core/src/logs/tree-renderer.ts
|
|
733
|
+
function truncate(v, max) {
|
|
734
|
+
if (v.length <= max) return v;
|
|
735
|
+
return v.slice(0, Math.max(0, max - 1)) + "\u2026";
|
|
736
|
+
}
|
|
737
|
+
function fmtAttrValue(value, maxLen) {
|
|
738
|
+
if (value === null || value === void 0) return void 0;
|
|
739
|
+
if (typeof value === "string") return truncate(value, maxLen);
|
|
740
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
741
|
+
return String(value);
|
|
742
|
+
}
|
|
743
|
+
if (typeof value === "object") {
|
|
744
|
+
try {
|
|
745
|
+
return truncate(JSON.stringify(value), maxLen);
|
|
746
|
+
} catch {
|
|
747
|
+
return "[object]";
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
return String(value);
|
|
751
|
+
}
|
|
752
|
+
function compactAttrs(attrs, maxLen) {
|
|
753
|
+
if (!attrs) return "";
|
|
754
|
+
const entries = Object.entries(attrs);
|
|
755
|
+
if (entries.length === 0) return "";
|
|
756
|
+
const picks = /* @__PURE__ */ new Map();
|
|
757
|
+
const set = (k, v) => {
|
|
758
|
+
const s = fmtAttrValue(v, maxLen);
|
|
759
|
+
if (s !== void 0) picks.set(k, s);
|
|
760
|
+
};
|
|
761
|
+
set("job", attrs.jobId ?? attrs.job ?? attrs.jobUuid);
|
|
762
|
+
set("user", attrs.userUuid ?? attrs.userId ?? attrs.user);
|
|
763
|
+
set("trip", attrs.tripUuid ?? attrs.tripId ?? attrs.trip);
|
|
764
|
+
set("msgs", attrs.messageCount ?? attrs.msgs);
|
|
765
|
+
set("trips", attrs.trips);
|
|
766
|
+
set("model", attrs.model);
|
|
767
|
+
const tokens = attrs.tokens;
|
|
768
|
+
if (tokens && typeof tokens === "object" && tokens !== null) {
|
|
769
|
+
const input = tokens.input;
|
|
770
|
+
const output = tokens.output;
|
|
771
|
+
if (typeof input === "number" || typeof output === "number") {
|
|
772
|
+
picks.set("tokens", `${input ?? "?"}/${output ?? "?"}`);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
for (const k of ["shouldNotify", "variant"]) {
|
|
776
|
+
if (k in attrs) set(k, attrs[k]);
|
|
777
|
+
}
|
|
778
|
+
const rendered = [...picks.entries()].filter(([, v]) => v !== "").map(([k, v]) => `${k}=${v}`);
|
|
779
|
+
return rendered.length > 0 ? " " + rendered.join(" ") : "";
|
|
780
|
+
}
|
|
781
|
+
function statusMark(node) {
|
|
782
|
+
if (node.event.status === "error") return " \u2716";
|
|
783
|
+
if (node.event.status === "ok") return " \u2714";
|
|
784
|
+
return "";
|
|
785
|
+
}
|
|
786
|
+
function fmtDuration(ms) {
|
|
787
|
+
if (!Number.isFinite(ms) || ms < 0) return "";
|
|
788
|
+
if (ms < 1e3) return `${Math.round(ms)}ms`;
|
|
789
|
+
return `${(ms / 1e3).toFixed(2)}s`;
|
|
790
|
+
}
|
|
791
|
+
function renderNodeLines(node, prefix, isLast, options) {
|
|
792
|
+
const branch = prefix + (isLast ? "\u2514\u2500 " : "\u251C\u2500 ");
|
|
793
|
+
const nextPrefix = prefix + (isLast ? " " : "\u2502 ");
|
|
794
|
+
const attrs = compactAttrs(node.event.attributes, options.maxAttributeLength);
|
|
795
|
+
const dur = node.event.durationMs !== void 0 ? ` ${fmtDuration(node.event.durationMs)}` : "";
|
|
796
|
+
const line = `${branch}${node.event.name}${attrs}${statusMark(node)}${dur}`;
|
|
797
|
+
const lines = [line];
|
|
798
|
+
const showConf = options.showConfidence === "always" || options.showConfidence === "non-explicit" && node.event.confidence !== "explicit";
|
|
799
|
+
if (showConf) {
|
|
800
|
+
lines.push(`${nextPrefix}confidence: ${node.event.confidence}`);
|
|
801
|
+
}
|
|
802
|
+
const children = node.children;
|
|
803
|
+
for (let i = 0; i < children.length; i++) {
|
|
804
|
+
lines.push(...renderNodeLines(children[i], nextPrefix, i === children.length - 1, options));
|
|
805
|
+
}
|
|
806
|
+
return lines;
|
|
807
|
+
}
|
|
808
|
+
function renderRunTree(tree, options) {
|
|
809
|
+
const opts = {
|
|
810
|
+
verbose: options?.verbose ?? false,
|
|
811
|
+
showConfidence: options?.showConfidence ?? "always",
|
|
812
|
+
showMetadata: options?.showMetadata ?? false,
|
|
813
|
+
color: options?.color ?? false,
|
|
814
|
+
maxAttributeLength: options?.maxAttributeLength ?? 40,
|
|
815
|
+
summary: options?.summary ?? true
|
|
816
|
+
};
|
|
817
|
+
const header = `Run ${tree.runId}`;
|
|
818
|
+
const lines = [header];
|
|
819
|
+
const children = tree.children;
|
|
820
|
+
for (let i = 0; i < children.length; i++) {
|
|
821
|
+
lines.push(...renderNodeLines(children[i], "", i === children.length - 1, opts));
|
|
822
|
+
}
|
|
823
|
+
if (opts.summary) {
|
|
824
|
+
const cb = tree.metadata.confidenceBreakdown;
|
|
825
|
+
const tools = tree.metadata.kinds.TOOL ?? 0;
|
|
826
|
+
const llms = tree.metadata.kinds.LLM ?? 0;
|
|
827
|
+
lines.push("");
|
|
828
|
+
lines.push("Summary:");
|
|
829
|
+
lines.push(` Events: ${tree.metadata.totalEvents}`);
|
|
830
|
+
lines.push(` Tools: ${tools}`);
|
|
831
|
+
lines.push(` LLMs: ${llms}`);
|
|
832
|
+
lines.push(
|
|
833
|
+
` Confidence: ${cb.explicit} explicit, ${cb.correlated} correlated, ${cb.heuristic} heuristic, ${cb.unknown} unknown`
|
|
834
|
+
);
|
|
835
|
+
lines.push("");
|
|
836
|
+
lines.push("Note:");
|
|
837
|
+
lines.push(" Flat timeline by default. Nesting only with explicit parentId.");
|
|
838
|
+
}
|
|
839
|
+
return lines.join("\n");
|
|
840
|
+
}
|
|
841
|
+
function renderRunTrees(trees, options) {
|
|
842
|
+
return trees.map((t) => renderRunTree(t, options)).join("\n\n");
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// packages/core/src/logs/line-parser.ts
|
|
846
|
+
function shiftLineNumbers(res, options) {
|
|
847
|
+
const targetLine = typeof options.line === "number" && Number.isFinite(options.line) && options.line > 0 ? Math.floor(options.line) : void 0;
|
|
848
|
+
const file = typeof options.file === "string" && options.file.trim() !== "" ? options.file : void 0;
|
|
849
|
+
if (targetLine === void 0 && file === void 0) return res;
|
|
850
|
+
const mapRecord = (x) => ({
|
|
851
|
+
...x,
|
|
852
|
+
...targetLine !== void 0 ? { line: targetLine } : {},
|
|
853
|
+
...file !== void 0 ? { file } : {}
|
|
854
|
+
});
|
|
855
|
+
const mapWarning = (x) => ({
|
|
856
|
+
...x,
|
|
857
|
+
...targetLine !== void 0 ? { line: targetLine } : {},
|
|
858
|
+
...file !== void 0 ? { file } : {}
|
|
859
|
+
});
|
|
860
|
+
return {
|
|
861
|
+
records: res.records.map(mapRecord),
|
|
862
|
+
warnings: res.warnings.map(mapWarning)
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
function normalizeFormat(line, format) {
|
|
866
|
+
if (format && format !== "auto") return format;
|
|
867
|
+
const trimmed = line.trim();
|
|
868
|
+
if (trimmed.startsWith("{")) return "json";
|
|
869
|
+
return "log4js";
|
|
870
|
+
}
|
|
871
|
+
function parseLogLine(line, options = {}) {
|
|
872
|
+
const raw = typeof line === "string" ? line : "";
|
|
873
|
+
const trimmed = raw.trim();
|
|
874
|
+
if (trimmed === "") return { records: [], warnings: [] };
|
|
875
|
+
const format = normalizeFormat(raw, options.format);
|
|
876
|
+
const base = format === "json" ? new JsonLogParser().parseLines([raw], options.file) : new Log4jsParser().parseLines([raw], options.file);
|
|
877
|
+
return shiftLineNumbers(base, options);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// packages/core/src/logs/live-tree.ts
|
|
881
|
+
var LiveLogAccumulator = class {
|
|
882
|
+
#config;
|
|
883
|
+
#format;
|
|
884
|
+
#file;
|
|
885
|
+
#normalizer;
|
|
886
|
+
#redactor;
|
|
887
|
+
#treeBuilder;
|
|
888
|
+
#events = [];
|
|
889
|
+
#warnings = [];
|
|
890
|
+
#trees = [];
|
|
891
|
+
constructor(options) {
|
|
892
|
+
this.#config = mergeLogIngestConfig(options.config, {});
|
|
893
|
+
this.#format = options.format ?? "auto";
|
|
894
|
+
this.#file = options.file;
|
|
895
|
+
this.#normalizer = new EventNormalizer({ config: this.#config });
|
|
896
|
+
this.#redactor = new Redactor({ rules: this.#config.redact });
|
|
897
|
+
this.#treeBuilder = new TreeBuilder({ config: this.#config });
|
|
898
|
+
}
|
|
899
|
+
pushLine(line, lineNumber) {
|
|
900
|
+
try {
|
|
901
|
+
const parsed = parseLogLine(line, {
|
|
902
|
+
format: this.#format,
|
|
903
|
+
file: this.#file,
|
|
904
|
+
line: lineNumber
|
|
905
|
+
});
|
|
906
|
+
const normalized = this.#normalizer.normalizeAll(parsed.records);
|
|
907
|
+
const redactedEvents = normalized.records.map((e) => ({
|
|
908
|
+
...e,
|
|
909
|
+
attributes: e.attributes ? this.#redactor.redactRecord(e.attributes) : void 0
|
|
910
|
+
}));
|
|
911
|
+
this.#events = [...this.#events, ...redactedEvents];
|
|
912
|
+
this.#warnings = [...this.#warnings, ...parsed.warnings, ...normalized.warnings];
|
|
913
|
+
this.#trees = this.#treeBuilder.build(this.#events);
|
|
914
|
+
return {
|
|
915
|
+
events: this.#events,
|
|
916
|
+
trees: this.#trees,
|
|
917
|
+
warnings: this.#warnings
|
|
918
|
+
};
|
|
919
|
+
} catch (e) {
|
|
920
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
921
|
+
const warning = {
|
|
922
|
+
code: "UNKNOWN",
|
|
923
|
+
message: `LiveLogAccumulator failed to process line (${msg})`,
|
|
924
|
+
file: this.#file,
|
|
925
|
+
line: lineNumber,
|
|
926
|
+
raw: typeof line === "string" ? line.slice(0, 500) : void 0
|
|
927
|
+
};
|
|
928
|
+
this.#warnings = [...this.#warnings, warning];
|
|
929
|
+
return { events: this.#events, trees: this.#trees, warnings: this.#warnings };
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
getEvents() {
|
|
933
|
+
return this.#events;
|
|
934
|
+
}
|
|
935
|
+
getTrees() {
|
|
936
|
+
return this.#trees;
|
|
937
|
+
}
|
|
938
|
+
getWarnings() {
|
|
939
|
+
return this.#warnings;
|
|
940
|
+
}
|
|
941
|
+
reset() {
|
|
942
|
+
this.#events = [];
|
|
943
|
+
this.#trees = [];
|
|
944
|
+
this.#warnings = [];
|
|
945
|
+
}
|
|
946
|
+
};
|
|
947
|
+
|
|
948
|
+
// packages/core/src/logs/index.ts
|
|
949
|
+
function firstNonEmptyLine(text) {
|
|
950
|
+
for (const line of text.split(/\r?\n/)) {
|
|
951
|
+
const t = line.trim();
|
|
952
|
+
if (t !== "") return t;
|
|
953
|
+
}
|
|
954
|
+
return void 0;
|
|
955
|
+
}
|
|
956
|
+
async function detectFormat(filePath) {
|
|
957
|
+
const text = await promises.readFile(filePath, "utf-8");
|
|
958
|
+
const first = firstNonEmptyLine(text);
|
|
959
|
+
if (!first) return "log4js";
|
|
960
|
+
if (!first.startsWith("{")) return "log4js";
|
|
961
|
+
try {
|
|
962
|
+
const parsed = JSON.parse(first);
|
|
963
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return "json";
|
|
964
|
+
} catch {
|
|
965
|
+
}
|
|
966
|
+
return "log4js";
|
|
967
|
+
}
|
|
968
|
+
function applyOverrides(cfg, options) {
|
|
969
|
+
const override = {};
|
|
970
|
+
for (const k of [
|
|
971
|
+
"runIdKeys",
|
|
972
|
+
"eventKey",
|
|
973
|
+
"timestampKey",
|
|
974
|
+
"messageKey",
|
|
975
|
+
"levelKey",
|
|
976
|
+
"parentIdKey",
|
|
977
|
+
"durationKey",
|
|
978
|
+
"statusKey"
|
|
979
|
+
]) {
|
|
980
|
+
const v = options[k];
|
|
981
|
+
if (v !== void 0) override[k] = v;
|
|
982
|
+
}
|
|
983
|
+
return mergeLogIngestConfig(cfg, override);
|
|
984
|
+
}
|
|
985
|
+
async function parseLogsToTrees(filePath, options = {}) {
|
|
986
|
+
const base = options.config ?? await loadLogIngestConfig(options.configPath);
|
|
987
|
+
const config = applyOverrides(base, options);
|
|
988
|
+
const format = options.format === "auto" || options.format === void 0 ? await detectFormat(filePath) : options.format;
|
|
989
|
+
let parsed;
|
|
990
|
+
if (format === "json") {
|
|
991
|
+
parsed = await new JsonLogParser().parseFile(filePath);
|
|
992
|
+
} else {
|
|
993
|
+
parsed = await new Log4jsParser().parseFile(filePath);
|
|
994
|
+
}
|
|
995
|
+
const normalizer = new EventNormalizer({ config });
|
|
996
|
+
const normalized = normalizer.normalizeAll(parsed.records);
|
|
997
|
+
const redactor = new Redactor({ rules: config.redact });
|
|
998
|
+
const events = normalized.records.map((e) => ({
|
|
999
|
+
...e,
|
|
1000
|
+
attributes: e.attributes ? redactor.redactRecord(e.attributes) : void 0
|
|
1001
|
+
}));
|
|
1002
|
+
const trees = new TreeBuilder({ config }).build(events);
|
|
1003
|
+
return {
|
|
1004
|
+
events,
|
|
1005
|
+
trees,
|
|
1006
|
+
warnings: [...parsed.warnings, ...normalized.warnings]
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
// packages/core/src/utils/duration.ts
|
|
1011
|
+
function parseDuration(duration) {
|
|
1012
|
+
const raw = typeof duration === "string" ? duration.trim() : "";
|
|
1013
|
+
const match = raw.match(/^(\d+)(ms|[smhd])$/);
|
|
1014
|
+
if (!match) {
|
|
1015
|
+
throw new Error(
|
|
1016
|
+
`Invalid duration format: ${duration}. Use a positive integer followed by ms, s, m, h, or d (e.g. 500ms, 30s, 5m, 2h, 7d).`
|
|
1017
|
+
);
|
|
1018
|
+
}
|
|
1019
|
+
const amount = Number.parseInt(match[1], 10);
|
|
1020
|
+
const unit = match[2];
|
|
1021
|
+
if (!Number.isFinite(amount) || amount <= 0) {
|
|
1022
|
+
throw new Error(
|
|
1023
|
+
`Invalid duration amount: ${duration}. Amount must be a positive integer.`
|
|
1024
|
+
);
|
|
1025
|
+
}
|
|
1026
|
+
switch (unit) {
|
|
1027
|
+
case "ms":
|
|
1028
|
+
return amount;
|
|
1029
|
+
case "s":
|
|
1030
|
+
return amount * 1e3;
|
|
1031
|
+
case "m":
|
|
1032
|
+
return amount * 60 * 1e3;
|
|
1033
|
+
case "h":
|
|
1034
|
+
return amount * 60 * 60 * 1e3;
|
|
1035
|
+
case "d":
|
|
1036
|
+
return amount * 24 * 60 * 60 * 1e3;
|
|
1037
|
+
default: {
|
|
1038
|
+
throw new Error(`Unknown duration unit: ${unit}`);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
function formatDuration(ms) {
|
|
1043
|
+
if (!Number.isFinite(ms)) {
|
|
1044
|
+
return "0ms";
|
|
1045
|
+
}
|
|
1046
|
+
if (ms < 0) {
|
|
1047
|
+
throw new Error(`formatDuration: ms must be non-negative (got ${ms})`);
|
|
1048
|
+
}
|
|
1049
|
+
if (ms < 1e3) {
|
|
1050
|
+
return `${Math.floor(ms)}ms`;
|
|
1051
|
+
}
|
|
1052
|
+
if (ms < 6e4) {
|
|
1053
|
+
return `${(ms / 1e3).toFixed(2)}s`;
|
|
1054
|
+
}
|
|
1055
|
+
if (ms < 36e5) {
|
|
1056
|
+
return `${(ms / 6e4).toFixed(1)}m`;
|
|
1057
|
+
}
|
|
1058
|
+
return `${(ms / 36e5).toFixed(1)}h`;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
// packages/core/src/utils.ts
|
|
62
1062
|
var DEFAULT_TRACE_DIR_NAME = ".agent-inspect";
|
|
63
1063
|
var RUNS_DIR_NAME = "runs";
|
|
64
1064
|
var FALLBACK_TRACE_DIR = path__default.default.join(
|
|
@@ -73,15 +1073,8 @@ function createRunId() {
|
|
|
73
1073
|
function createStepId() {
|
|
74
1074
|
return `step_${nanoid.nanoid(10)}`;
|
|
75
1075
|
}
|
|
76
|
-
function
|
|
77
|
-
|
|
78
|
-
return "0ms";
|
|
79
|
-
}
|
|
80
|
-
if (ms < 1e3) {
|
|
81
|
-
return `${Math.floor(ms)}ms`;
|
|
82
|
-
}
|
|
83
|
-
const seconds = ms / 1e3;
|
|
84
|
-
return `${(Math.round(seconds * 10) / 10).toFixed(1)}s`;
|
|
1076
|
+
function formatDuration2(ms) {
|
|
1077
|
+
return formatDuration(ms);
|
|
85
1078
|
}
|
|
86
1079
|
function formatTimestamp(timestamp) {
|
|
87
1080
|
if (!Number.isFinite(timestamp)) {
|
|
@@ -100,6 +1093,10 @@ function formatTimestamp(timestamp) {
|
|
|
100
1093
|
return `${y}-${mo}-${day} ${h}:${min}:${s}`;
|
|
101
1094
|
}
|
|
102
1095
|
function getDefaultTraceDir() {
|
|
1096
|
+
const envDir = process.env.AGENT_INSPECT_TRACE_DIR;
|
|
1097
|
+
if (typeof envDir === "string" && envDir.trim() !== "") {
|
|
1098
|
+
return envDir.trim();
|
|
1099
|
+
}
|
|
103
1100
|
try {
|
|
104
1101
|
const home = os__default.default.homedir();
|
|
105
1102
|
if (typeof home !== "string" || home.trim() === "") {
|
|
@@ -315,7 +1312,7 @@ function runWithStepContext(stepId, fn) {
|
|
|
315
1312
|
});
|
|
316
1313
|
});
|
|
317
1314
|
}
|
|
318
|
-
function
|
|
1315
|
+
function isRecord6(value) {
|
|
319
1316
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
320
1317
|
}
|
|
321
1318
|
function nonEmptyString(value) {
|
|
@@ -326,7 +1323,7 @@ function finiteNumber(value) {
|
|
|
326
1323
|
}
|
|
327
1324
|
function optionalErrorInfo(value) {
|
|
328
1325
|
if (value === void 0) return true;
|
|
329
|
-
if (!
|
|
1326
|
+
if (!isRecord6(value)) return false;
|
|
330
1327
|
if (typeof value.message !== "string") return false;
|
|
331
1328
|
if ("stack" in value && value.stack !== void 0) {
|
|
332
1329
|
if (typeof value.stack !== "string") return false;
|
|
@@ -334,7 +1331,7 @@ function optionalErrorInfo(value) {
|
|
|
334
1331
|
return true;
|
|
335
1332
|
}
|
|
336
1333
|
function validateEvent(event) {
|
|
337
|
-
if (!
|
|
1334
|
+
if (!isRecord6(event)) return false;
|
|
338
1335
|
if (event.schemaVersion !== "0.1") return false;
|
|
339
1336
|
if (!finiteNumber(event.timestamp)) return false;
|
|
340
1337
|
if (typeof event.event !== "string") return false;
|
|
@@ -343,7 +1340,7 @@ function validateEvent(event) {
|
|
|
343
1340
|
if (!nonEmptyString(event.runId) || !nonEmptyString(event.name) || !finiteNumber(event.startTime)) {
|
|
344
1341
|
return false;
|
|
345
1342
|
}
|
|
346
|
-
if (event.metadata !== void 0 && !
|
|
1343
|
+
if (event.metadata !== void 0 && !isRecord6(event.metadata)) {
|
|
347
1344
|
return false;
|
|
348
1345
|
}
|
|
349
1346
|
return true;
|
|
@@ -358,7 +1355,7 @@ function validateEvent(event) {
|
|
|
358
1355
|
if (event.parentId !== void 0 && typeof event.parentId !== "string") {
|
|
359
1356
|
return false;
|
|
360
1357
|
}
|
|
361
|
-
if (event.metadata !== void 0 && !
|
|
1358
|
+
if (event.metadata !== void 0 && !isRecord6(event.metadata)) {
|
|
362
1359
|
return false;
|
|
363
1360
|
}
|
|
364
1361
|
return true;
|
|
@@ -502,6 +1499,898 @@ function getRunIdFromTraceFileName(fileName) {
|
|
|
502
1499
|
return void 0;
|
|
503
1500
|
}
|
|
504
1501
|
}
|
|
1502
|
+
function resolveTraceDir(options = {}) {
|
|
1503
|
+
if (typeof options.dir === "string" && options.dir.trim() !== "") {
|
|
1504
|
+
return options.dir.trim();
|
|
1505
|
+
}
|
|
1506
|
+
const envDir = process.env.AGENT_INSPECT_TRACE_DIR;
|
|
1507
|
+
if (typeof envDir === "string" && envDir.trim() !== "") {
|
|
1508
|
+
return envDir.trim();
|
|
1509
|
+
}
|
|
1510
|
+
return getDefaultTraceDir();
|
|
1511
|
+
}
|
|
1512
|
+
var TraceDirectory = class {
|
|
1513
|
+
#dir;
|
|
1514
|
+
constructor(options = {}) {
|
|
1515
|
+
this.#dir = resolveTraceDir(options);
|
|
1516
|
+
}
|
|
1517
|
+
getPath(filename) {
|
|
1518
|
+
return filename ? path__default.default.join(this.#dir, filename) : this.#dir;
|
|
1519
|
+
}
|
|
1520
|
+
async list() {
|
|
1521
|
+
try {
|
|
1522
|
+
const files = await promises.readdir(this.#dir);
|
|
1523
|
+
return files.filter((f) => f.endsWith(".jsonl"));
|
|
1524
|
+
} catch (e) {
|
|
1525
|
+
if (e && typeof e === "object" && "code" in e && e.code === "ENOENT") {
|
|
1526
|
+
return [];
|
|
1527
|
+
}
|
|
1528
|
+
throw e;
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
async getFileStats(filename) {
|
|
1532
|
+
return await promises.stat(this.getPath(filename));
|
|
1533
|
+
}
|
|
1534
|
+
};
|
|
1535
|
+
function isFiniteNumber2(v) {
|
|
1536
|
+
return typeof v === "number" && Number.isFinite(v);
|
|
1537
|
+
}
|
|
1538
|
+
function safeParseJson(line) {
|
|
1539
|
+
try {
|
|
1540
|
+
return JSON.parse(line);
|
|
1541
|
+
} catch {
|
|
1542
|
+
return void 0;
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
async function extractMetadata(filePath, _quickScan) {
|
|
1546
|
+
const stats = await promises.stat(filePath);
|
|
1547
|
+
let runIdFromFile = path__default.default.basename(filePath);
|
|
1548
|
+
if (runIdFromFile.endsWith(".jsonl")) {
|
|
1549
|
+
runIdFromFile = runIdFromFile.slice(0, -".jsonl".length);
|
|
1550
|
+
}
|
|
1551
|
+
const raw = await promises.readFile(filePath, "utf-8");
|
|
1552
|
+
const lines = raw.split(/\r?\n/);
|
|
1553
|
+
let eventCount = 0;
|
|
1554
|
+
let runId;
|
|
1555
|
+
let name;
|
|
1556
|
+
let startedAt;
|
|
1557
|
+
let endedAt;
|
|
1558
|
+
let explicitDurationMs;
|
|
1559
|
+
let hasRunStarted = false;
|
|
1560
|
+
let hasRunCompleted = false;
|
|
1561
|
+
let runCompletedStatus;
|
|
1562
|
+
let anyStepError = false;
|
|
1563
|
+
let anyKnownEvent = false;
|
|
1564
|
+
for (const line of lines) {
|
|
1565
|
+
const trimmed = line.trim();
|
|
1566
|
+
if (trimmed === "") continue;
|
|
1567
|
+
const parsed = safeParseJson(trimmed);
|
|
1568
|
+
if (!parsed) continue;
|
|
1569
|
+
if (!isTraceEvent(parsed)) continue;
|
|
1570
|
+
const e = parsed;
|
|
1571
|
+
anyKnownEvent = true;
|
|
1572
|
+
eventCount += 1;
|
|
1573
|
+
if (runId === void 0 && typeof e.runId === "string") {
|
|
1574
|
+
runId = e.runId;
|
|
1575
|
+
}
|
|
1576
|
+
if (e.event === "run_started") {
|
|
1577
|
+
hasRunStarted = true;
|
|
1578
|
+
const rs = e;
|
|
1579
|
+
if (typeof rs.name === "string" && rs.name.trim() !== "") {
|
|
1580
|
+
name = rs.name;
|
|
1581
|
+
}
|
|
1582
|
+
if (isFiniteNumber2(rs.startTime)) {
|
|
1583
|
+
startedAt = rs.startTime;
|
|
1584
|
+
} else if (isFiniteNumber2(rs.timestamp)) {
|
|
1585
|
+
startedAt = rs.timestamp;
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
if (e.event === "run_completed") {
|
|
1589
|
+
hasRunCompleted = true;
|
|
1590
|
+
const rc = e;
|
|
1591
|
+
runCompletedStatus = rc.status;
|
|
1592
|
+
if (isFiniteNumber2(rc.endTime)) endedAt = rc.endTime;
|
|
1593
|
+
else if (isFiniteNumber2(rc.timestamp)) endedAt = rc.timestamp;
|
|
1594
|
+
if (isFiniteNumber2(rc.durationMs)) explicitDurationMs = rc.durationMs;
|
|
1595
|
+
}
|
|
1596
|
+
if (e.event === "step_completed") {
|
|
1597
|
+
const sc = e;
|
|
1598
|
+
if (sc.status === "error") {
|
|
1599
|
+
anyStepError = true;
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
const resolvedRunId = runId ?? runIdFromFile;
|
|
1604
|
+
let status = "unknown";
|
|
1605
|
+
if (hasRunCompleted && (runCompletedStatus === "success" || runCompletedStatus === "error")) {
|
|
1606
|
+
status = runCompletedStatus;
|
|
1607
|
+
} else if (anyStepError) {
|
|
1608
|
+
status = "error";
|
|
1609
|
+
} else if (hasRunStarted && !hasRunCompleted) {
|
|
1610
|
+
status = "running";
|
|
1611
|
+
} else if (anyKnownEvent) {
|
|
1612
|
+
status = "unknown";
|
|
1613
|
+
} else {
|
|
1614
|
+
status = "unknown";
|
|
1615
|
+
}
|
|
1616
|
+
const durationMs = explicitDurationMs ?? (startedAt !== void 0 && endedAt !== void 0 && Number.isFinite(startedAt) && Number.isFinite(endedAt) && endedAt >= startedAt ? endedAt - startedAt : void 0);
|
|
1617
|
+
return {
|
|
1618
|
+
runId: resolvedRunId,
|
|
1619
|
+
name,
|
|
1620
|
+
status,
|
|
1621
|
+
startedAt,
|
|
1622
|
+
endedAt,
|
|
1623
|
+
durationMs,
|
|
1624
|
+
eventCount,
|
|
1625
|
+
filePath,
|
|
1626
|
+
fileSize: stats.size,
|
|
1627
|
+
createdAt: stats.birthtime
|
|
1628
|
+
};
|
|
1629
|
+
}
|
|
1630
|
+
function buildRunSummary(events) {
|
|
1631
|
+
const started = events.find(
|
|
1632
|
+
(e) => e.event === "run_started"
|
|
1633
|
+
);
|
|
1634
|
+
const completed = events.filter(
|
|
1635
|
+
(e) => e.event === "run_completed"
|
|
1636
|
+
);
|
|
1637
|
+
const lastCompleted = completed[completed.length - 1];
|
|
1638
|
+
const runId = started?.runId ?? events.find((e) => typeof e.runId === "string")?.runId ?? "unknown-run";
|
|
1639
|
+
const name = typeof started?.name === "string" && started.name.trim() !== "" ? started.name : void 0;
|
|
1640
|
+
const status = lastCompleted ? lastCompleted.status : started ? "running" : "unknown";
|
|
1641
|
+
const durationMs = lastCompleted && isFiniteNumber2(lastCompleted.durationMs) ? lastCompleted.durationMs : void 0;
|
|
1642
|
+
started && isFiniteNumber2(started.startTime) ? started.startTime : void 0;
|
|
1643
|
+
const steps = /* @__PURE__ */ new Map();
|
|
1644
|
+
for (const e of events) {
|
|
1645
|
+
if (e.event === "step_started") {
|
|
1646
|
+
const s = e;
|
|
1647
|
+
steps.set(s.stepId, {
|
|
1648
|
+
type: s.type,
|
|
1649
|
+
name: s.name,
|
|
1650
|
+
status: "running",
|
|
1651
|
+
parentId: s.parentId,
|
|
1652
|
+
tokensInput: typeof s.metadata?.tokens?.input === "number" ? s.metadata.tokens.input : void 0,
|
|
1653
|
+
tokensOutput: typeof s.metadata?.tokens?.output === "number" ? s.metadata.tokens.output : void 0
|
|
1654
|
+
});
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
for (const e of events) {
|
|
1658
|
+
if (e.event === "step_completed") {
|
|
1659
|
+
const c = e;
|
|
1660
|
+
const existing = steps.get(c.stepId);
|
|
1661
|
+
if (!existing) continue;
|
|
1662
|
+
existing.status = c.status;
|
|
1663
|
+
existing.durationMs = c.durationMs;
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
let totalSteps = 0;
|
|
1667
|
+
let llmSteps = 0;
|
|
1668
|
+
let toolSteps = 0;
|
|
1669
|
+
let logicSteps = 0;
|
|
1670
|
+
let errorSteps = 0;
|
|
1671
|
+
let maxDepth = 0;
|
|
1672
|
+
let longestStep;
|
|
1673
|
+
let totalTokensInput = 0;
|
|
1674
|
+
let totalTokensOutput = 0;
|
|
1675
|
+
let hasAnyTokens = false;
|
|
1676
|
+
const depthCache = /* @__PURE__ */ new Map();
|
|
1677
|
+
const computeDepth = (stepId) => {
|
|
1678
|
+
const cached = depthCache.get(stepId);
|
|
1679
|
+
if (cached !== void 0) return cached;
|
|
1680
|
+
const node = steps.get(stepId);
|
|
1681
|
+
if (!node) return 0;
|
|
1682
|
+
const parent = node.parentId;
|
|
1683
|
+
if (typeof parent !== "string" || parent.trim() === "" || !steps.has(parent)) {
|
|
1684
|
+
depthCache.set(stepId, 0);
|
|
1685
|
+
return 0;
|
|
1686
|
+
}
|
|
1687
|
+
const d = Math.min(1e3, computeDepth(parent) + 1);
|
|
1688
|
+
depthCache.set(stepId, d);
|
|
1689
|
+
return d;
|
|
1690
|
+
};
|
|
1691
|
+
for (const [id, s] of steps.entries()) {
|
|
1692
|
+
totalSteps += 1;
|
|
1693
|
+
if (s.type === "llm") llmSteps += 1;
|
|
1694
|
+
else if (s.type === "tool") toolSteps += 1;
|
|
1695
|
+
else logicSteps += 1;
|
|
1696
|
+
if (s.status === "error") errorSteps += 1;
|
|
1697
|
+
const depth = computeDepth(id);
|
|
1698
|
+
if (depth > maxDepth) maxDepth = depth;
|
|
1699
|
+
if (typeof s.durationMs === "number" && Number.isFinite(s.durationMs)) {
|
|
1700
|
+
if (!longestStep || s.durationMs > longestStep.durationMs) {
|
|
1701
|
+
longestStep = { name: s.name, durationMs: s.durationMs, type: s.type };
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
if (typeof s.tokensInput === "number" || typeof s.tokensOutput === "number") {
|
|
1705
|
+
hasAnyTokens = true;
|
|
1706
|
+
if (typeof s.tokensInput === "number") totalTokensInput += s.tokensInput;
|
|
1707
|
+
if (typeof s.tokensOutput === "number") totalTokensOutput += s.tokensOutput;
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
const summary = {
|
|
1711
|
+
runId,
|
|
1712
|
+
name,
|
|
1713
|
+
status,
|
|
1714
|
+
durationMs,
|
|
1715
|
+
totalSteps,
|
|
1716
|
+
llmSteps,
|
|
1717
|
+
toolSteps,
|
|
1718
|
+
logicSteps,
|
|
1719
|
+
errorSteps,
|
|
1720
|
+
maxDepth,
|
|
1721
|
+
...longestStep ? { longestStep } : {},
|
|
1722
|
+
...hasAnyTokens ? { totalTokens: { input: totalTokensInput, output: totalTokensOutput } } : {}
|
|
1723
|
+
};
|
|
1724
|
+
return summary;
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
// packages/core/src/trace-filter.ts
|
|
1728
|
+
function toLower(s) {
|
|
1729
|
+
return typeof s === "string" ? s.toLowerCase() : "";
|
|
1730
|
+
}
|
|
1731
|
+
function filterTraces(traces, options) {
|
|
1732
|
+
const input = [...traces];
|
|
1733
|
+
let out = input.filter((t) => {
|
|
1734
|
+
if (options.status && t.status !== options.status) return false;
|
|
1735
|
+
if (options.name) {
|
|
1736
|
+
const q = options.name.toLowerCase();
|
|
1737
|
+
const hay = `${toLower(t.name)} ${toLower(t.runId)}`;
|
|
1738
|
+
if (!hay.includes(q)) return false;
|
|
1739
|
+
}
|
|
1740
|
+
if (options.since) {
|
|
1741
|
+
const windowMs = parseDuration(options.since);
|
|
1742
|
+
const cutoff = Date.now() - windowMs;
|
|
1743
|
+
const started = typeof t.startedAt === "number" ? t.startedAt : void 0;
|
|
1744
|
+
const basis = started ?? t.createdAt.getTime();
|
|
1745
|
+
if (!Number.isFinite(basis) || basis < cutoff) return false;
|
|
1746
|
+
}
|
|
1747
|
+
return true;
|
|
1748
|
+
});
|
|
1749
|
+
out.sort((a, b) => {
|
|
1750
|
+
const aTime = (typeof a.startedAt === "number" ? a.startedAt : void 0) ?? a.createdAt.getTime();
|
|
1751
|
+
const bTime = (typeof b.startedAt === "number" ? b.startedAt : void 0) ?? b.createdAt.getTime();
|
|
1752
|
+
return bTime - aTime;
|
|
1753
|
+
});
|
|
1754
|
+
if (typeof options.limit === "number" && Number.isFinite(options.limit)) {
|
|
1755
|
+
const n = Math.max(0, Math.floor(options.limit));
|
|
1756
|
+
out = out.slice(0, n);
|
|
1757
|
+
}
|
|
1758
|
+
return out;
|
|
1759
|
+
}
|
|
1760
|
+
var KNOWN_EVENTS = /* @__PURE__ */ new Set([
|
|
1761
|
+
"run_started",
|
|
1762
|
+
"run_completed",
|
|
1763
|
+
"step_started",
|
|
1764
|
+
"step_completed"
|
|
1765
|
+
]);
|
|
1766
|
+
function isRecord7(value) {
|
|
1767
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1768
|
+
}
|
|
1769
|
+
function safeParse(line) {
|
|
1770
|
+
try {
|
|
1771
|
+
return JSON.parse(line);
|
|
1772
|
+
} catch {
|
|
1773
|
+
return void 0;
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
async function isAgentInspectTrace(filePath) {
|
|
1777
|
+
try {
|
|
1778
|
+
const rl = readline.createInterface({
|
|
1779
|
+
input: fs.createReadStream(filePath, { encoding: "utf8" }),
|
|
1780
|
+
crlfDelay: Infinity
|
|
1781
|
+
});
|
|
1782
|
+
let checked = 0;
|
|
1783
|
+
for await (const line of rl) {
|
|
1784
|
+
const trimmed = line.trim();
|
|
1785
|
+
if (trimmed === "") continue;
|
|
1786
|
+
const parsed = safeParse(trimmed);
|
|
1787
|
+
if (!parsed) continue;
|
|
1788
|
+
if (!isRecord7(parsed)) continue;
|
|
1789
|
+
checked += 1;
|
|
1790
|
+
if (isTraceEvent(parsed)) return true;
|
|
1791
|
+
const ev = parsed.event;
|
|
1792
|
+
const runId = parsed.runId;
|
|
1793
|
+
if (typeof ev === "string" && KNOWN_EVENTS.has(ev) && typeof runId === "string") {
|
|
1794
|
+
return true;
|
|
1795
|
+
}
|
|
1796
|
+
if (checked >= 20) break;
|
|
1797
|
+
}
|
|
1798
|
+
return false;
|
|
1799
|
+
} catch {
|
|
1800
|
+
return false;
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
// packages/core/src/diff/comparable.ts
|
|
1805
|
+
function extractOutputPreview(meta) {
|
|
1806
|
+
if (meta === void 0) return void 0;
|
|
1807
|
+
if ("outputPreview" in meta) return meta.outputPreview;
|
|
1808
|
+
if ("resultPreview" in meta) return meta.resultPreview;
|
|
1809
|
+
return void 0;
|
|
1810
|
+
}
|
|
1811
|
+
function mapStepStatus(s) {
|
|
1812
|
+
if (s === void 0) return "running";
|
|
1813
|
+
return s;
|
|
1814
|
+
}
|
|
1815
|
+
function manualTraceEventsToComparableRun(events) {
|
|
1816
|
+
const started = events.find((e) => e.event === "run_started");
|
|
1817
|
+
if (!started || started.event !== "run_started") {
|
|
1818
|
+
throw new Error("Invalid trace: missing run_started");
|
|
1819
|
+
}
|
|
1820
|
+
const rs = started;
|
|
1821
|
+
const runId = rs.runId;
|
|
1822
|
+
const completedAll = events.filter((e) => e.event === "run_completed");
|
|
1823
|
+
const lastCompleted = completedAll[completedAll.length - 1];
|
|
1824
|
+
let runStatus;
|
|
1825
|
+
if (lastCompleted === void 0) runStatus = "running";
|
|
1826
|
+
else runStatus = lastCompleted.status;
|
|
1827
|
+
const durationMs = lastCompleted !== void 0 && Number.isFinite(lastCompleted.durationMs) ? lastCompleted.durationMs : void 0;
|
|
1828
|
+
const steps = /* @__PURE__ */ new Map();
|
|
1829
|
+
let order = 0;
|
|
1830
|
+
for (const e of events) {
|
|
1831
|
+
if (e.event !== "step_started") continue;
|
|
1832
|
+
const s = e;
|
|
1833
|
+
const meta = s.metadata ? { ...s.metadata } : void 0;
|
|
1834
|
+
steps.set(s.stepId, {
|
|
1835
|
+
id: s.stepId,
|
|
1836
|
+
parentId: s.parentId,
|
|
1837
|
+
name: s.name,
|
|
1838
|
+
type: s.type,
|
|
1839
|
+
order: order++,
|
|
1840
|
+
timestamp: s.timestamp,
|
|
1841
|
+
metadata: meta
|
|
1842
|
+
});
|
|
1843
|
+
}
|
|
1844
|
+
for (const e of events) {
|
|
1845
|
+
if (e.event !== "step_completed") continue;
|
|
1846
|
+
const acc = steps.get(e.stepId);
|
|
1847
|
+
if (!acc) continue;
|
|
1848
|
+
acc.status = e.status;
|
|
1849
|
+
acc.durationMs = e.durationMs;
|
|
1850
|
+
if (e.error?.message) acc.errorMsg = e.error.message;
|
|
1851
|
+
const extra = e;
|
|
1852
|
+
if (extra.metadata !== void 0 && typeof extra.metadata === "object") {
|
|
1853
|
+
acc.metadata = { ...acc.metadata ?? {}, ...extra.metadata };
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
1857
|
+
for (const acc of steps.values()) {
|
|
1858
|
+
let meta = acc.metadata ? { ...acc.metadata } : void 0;
|
|
1859
|
+
if (acc.parentId !== void 0 && !steps.has(acc.parentId)) {
|
|
1860
|
+
meta = { ...meta ?? {}, agent_inspect_diff_parent_missing: true };
|
|
1861
|
+
}
|
|
1862
|
+
const outputPreview = extractOutputPreview(meta);
|
|
1863
|
+
const sc = {
|
|
1864
|
+
id: acc.id,
|
|
1865
|
+
name: acc.name,
|
|
1866
|
+
type: acc.type,
|
|
1867
|
+
status: mapStepStatus(acc.status),
|
|
1868
|
+
durationMs: acc.durationMs,
|
|
1869
|
+
error: acc.errorMsg,
|
|
1870
|
+
metadata: meta && Object.keys(meta).length > 0 ? meta : void 0,
|
|
1871
|
+
outputPreview,
|
|
1872
|
+
children: []
|
|
1873
|
+
};
|
|
1874
|
+
nodes.set(acc.id, sc);
|
|
1875
|
+
}
|
|
1876
|
+
const roots = [];
|
|
1877
|
+
const sortByOrder = (a, b) => {
|
|
1878
|
+
const oa = steps.get(a.id)?.order ?? 0;
|
|
1879
|
+
const ob = steps.get(b.id)?.order ?? 0;
|
|
1880
|
+
return oa - ob;
|
|
1881
|
+
};
|
|
1882
|
+
for (const acc of steps.values()) {
|
|
1883
|
+
const node = nodes.get(acc.id);
|
|
1884
|
+
if (acc.parentId !== void 0 && nodes.has(acc.parentId)) {
|
|
1885
|
+
nodes.get(acc.parentId).children.push(node);
|
|
1886
|
+
} else {
|
|
1887
|
+
roots.push(node);
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
roots.sort(sortByOrder);
|
|
1891
|
+
for (const n of nodes.values()) {
|
|
1892
|
+
n.children.sort(sortByOrder);
|
|
1893
|
+
}
|
|
1894
|
+
return {
|
|
1895
|
+
runId,
|
|
1896
|
+
name: rs.name,
|
|
1897
|
+
status: runStatus,
|
|
1898
|
+
durationMs,
|
|
1899
|
+
steps: roots
|
|
1900
|
+
};
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
// packages/core/src/exporters/helpers.ts
|
|
1904
|
+
var REDACT_SUBSTRINGS = [
|
|
1905
|
+
"authorization",
|
|
1906
|
+
"cookie",
|
|
1907
|
+
"token",
|
|
1908
|
+
"apikey",
|
|
1909
|
+
"password",
|
|
1910
|
+
"secret",
|
|
1911
|
+
"email"
|
|
1912
|
+
];
|
|
1913
|
+
function shouldRedactKey(key) {
|
|
1914
|
+
const k = key.toLowerCase();
|
|
1915
|
+
for (const s of REDACT_SUBSTRINGS) {
|
|
1916
|
+
if (k.includes(s)) return true;
|
|
1917
|
+
}
|
|
1918
|
+
return false;
|
|
1919
|
+
}
|
|
1920
|
+
function safeString2(value, maxLength) {
|
|
1921
|
+
if (value === null || value === void 0) return "";
|
|
1922
|
+
let s;
|
|
1923
|
+
if (typeof value === "string") s = value;
|
|
1924
|
+
else if (typeof value === "number" || typeof value === "boolean") s = String(value);
|
|
1925
|
+
else s = stableJson(value, false);
|
|
1926
|
+
if (maxLength !== void 0 && maxLength >= 0 && s.length > maxLength) {
|
|
1927
|
+
return `${s.slice(0, maxLength)}\u2026`;
|
|
1928
|
+
}
|
|
1929
|
+
return s;
|
|
1930
|
+
}
|
|
1931
|
+
function escapeMarkdown(value) {
|
|
1932
|
+
return value.replace(/\|/g, "\\|").replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\n/g, " ");
|
|
1933
|
+
}
|
|
1934
|
+
function escapeHtml(value) {
|
|
1935
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1936
|
+
}
|
|
1937
|
+
function sortKeysDeep(input) {
|
|
1938
|
+
if (input === null || typeof input !== "object") return input;
|
|
1939
|
+
if (Array.isArray(input)) return input.map(sortKeysDeep);
|
|
1940
|
+
const o = input;
|
|
1941
|
+
const out = {};
|
|
1942
|
+
for (const k of Object.keys(o).sort()) {
|
|
1943
|
+
out[k] = sortKeysDeep(o[k]);
|
|
1944
|
+
}
|
|
1945
|
+
return out;
|
|
1946
|
+
}
|
|
1947
|
+
function stableJson(value, pretty) {
|
|
1948
|
+
const sorted = sortKeysDeep(value);
|
|
1949
|
+
return pretty === true ? JSON.stringify(sorted, null, 2) : JSON.stringify(sorted);
|
|
1950
|
+
}
|
|
1951
|
+
function compactAttributes(attrs, options) {
|
|
1952
|
+
if (attrs === void 0) return {};
|
|
1953
|
+
const maxLen = options?.maxLength ?? 500;
|
|
1954
|
+
const redacted = options?.redacted ?? true;
|
|
1955
|
+
const out = {};
|
|
1956
|
+
for (const key of Object.keys(attrs).sort()) {
|
|
1957
|
+
if (redacted && shouldRedactKey(key)) {
|
|
1958
|
+
out[key] = "[REDACTED]";
|
|
1959
|
+
continue;
|
|
1960
|
+
}
|
|
1961
|
+
const v = attrs[key];
|
|
1962
|
+
out[key] = compactValue(v, maxLen, redacted);
|
|
1963
|
+
}
|
|
1964
|
+
return out;
|
|
1965
|
+
}
|
|
1966
|
+
function compactValue(value, maxLen, redacted) {
|
|
1967
|
+
if (value === null || typeof value !== "object") {
|
|
1968
|
+
return typeof value === "string" ? safeString2(value, maxLen) : value;
|
|
1969
|
+
}
|
|
1970
|
+
if (Array.isArray(value)) {
|
|
1971
|
+
const arr = value.slice(0, 20).map((x) => compactValue(x, maxLen, redacted));
|
|
1972
|
+
if (value.length > 20) arr.push(`\u2026(+${value.length - 20} more)`);
|
|
1973
|
+
return arr;
|
|
1974
|
+
}
|
|
1975
|
+
const o = value;
|
|
1976
|
+
const inner = {};
|
|
1977
|
+
for (const k of Object.keys(o)) {
|
|
1978
|
+
if (redacted && shouldRedactKey(k)) inner[k] = "[REDACTED]";
|
|
1979
|
+
else inner[k] = compactValue(o[k], maxLen, redacted);
|
|
1980
|
+
}
|
|
1981
|
+
return inner;
|
|
1982
|
+
}
|
|
1983
|
+
function summarizeTree(tree) {
|
|
1984
|
+
const flat = flattenTree(tree);
|
|
1985
|
+
const errorNodes = flat.filter((n) => n.event.status === "error").length;
|
|
1986
|
+
return {
|
|
1987
|
+
runId: tree.runId,
|
|
1988
|
+
name: tree.name,
|
|
1989
|
+
status: tree.status,
|
|
1990
|
+
startedAt: tree.startedAt,
|
|
1991
|
+
endedAt: tree.endedAt,
|
|
1992
|
+
durationMs: tree.durationMs,
|
|
1993
|
+
stepCount: flat.length,
|
|
1994
|
+
errorStepCount: errorNodes,
|
|
1995
|
+
totalEvents: tree.metadata.totalEvents,
|
|
1996
|
+
confidenceBreakdown: { ...tree.metadata.confidenceBreakdown },
|
|
1997
|
+
kinds: { ...tree.metadata.kinds }
|
|
1998
|
+
};
|
|
1999
|
+
}
|
|
2000
|
+
function flattenTree(tree) {
|
|
2001
|
+
const out = [];
|
|
2002
|
+
function walk(nodes) {
|
|
2003
|
+
for (const n of nodes) {
|
|
2004
|
+
out.push(n);
|
|
2005
|
+
if (n.children.length > 0) walk(n.children);
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
walk(tree.children);
|
|
2009
|
+
return out;
|
|
2010
|
+
}
|
|
2011
|
+
function zeroKinds() {
|
|
2012
|
+
return {
|
|
2013
|
+
RUN: 0,
|
|
2014
|
+
AGENT: 0,
|
|
2015
|
+
LLM: 0,
|
|
2016
|
+
TOOL: 0,
|
|
2017
|
+
CHAIN: 0,
|
|
2018
|
+
RETRIEVER: 0,
|
|
2019
|
+
DECISION: 0,
|
|
2020
|
+
RESULT: 0,
|
|
2021
|
+
ERROR: 0,
|
|
2022
|
+
LOGIC: 0,
|
|
2023
|
+
LOG: 0
|
|
2024
|
+
};
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
// packages/core/src/diff/engine.ts
|
|
2028
|
+
var DEFAULT_THRESHOLD_MS = 0;
|
|
2029
|
+
function pathSeg(step2, index) {
|
|
2030
|
+
return { index, name: step2.name, stepId: step2.id };
|
|
2031
|
+
}
|
|
2032
|
+
function buildPath(segments) {
|
|
2033
|
+
return { path: [...segments] };
|
|
2034
|
+
}
|
|
2035
|
+
function pairSteps(left, right) {
|
|
2036
|
+
const usedRight = /* @__PURE__ */ new Set();
|
|
2037
|
+
const pairs = [];
|
|
2038
|
+
for (let i = 0; i < left.length; i++) {
|
|
2039
|
+
const L = left[i];
|
|
2040
|
+
let R = right.find((r) => !usedRight.has(r.id) && r.id === L.id);
|
|
2041
|
+
if (R === void 0 && i < right.length && !usedRight.has(right[i].id)) {
|
|
2042
|
+
const cand = right[i];
|
|
2043
|
+
if (cand.name === L.name && (cand.type ?? "") === (L.type ?? "")) {
|
|
2044
|
+
R = cand;
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
if (R === void 0) {
|
|
2048
|
+
R = right.find(
|
|
2049
|
+
(r) => !usedRight.has(r.id) && r.name === L.name && (r.type ?? "") === (L.type ?? "")
|
|
2050
|
+
);
|
|
2051
|
+
}
|
|
2052
|
+
if (R !== void 0) {
|
|
2053
|
+
usedRight.add(R.id);
|
|
2054
|
+
pairs.push([L, R]);
|
|
2055
|
+
} else {
|
|
2056
|
+
pairs.push([L, void 0]);
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
for (const R of right) {
|
|
2060
|
+
if (!usedRight.has(R.id)) {
|
|
2061
|
+
pairs.push([void 0, R]);
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
return pairs;
|
|
2065
|
+
}
|
|
2066
|
+
function compareLeafSteps(L, R, segments, opts, out) {
|
|
2067
|
+
const path5 = buildPath(segments);
|
|
2068
|
+
if (L.name !== R.name) {
|
|
2069
|
+
out.push({
|
|
2070
|
+
kind: "structure",
|
|
2071
|
+
severity: "warning",
|
|
2072
|
+
message: "Step name differs",
|
|
2073
|
+
path: path5,
|
|
2074
|
+
left: L.name,
|
|
2075
|
+
right: R.name
|
|
2076
|
+
});
|
|
2077
|
+
}
|
|
2078
|
+
if ((L.type ?? "") !== (R.type ?? "")) {
|
|
2079
|
+
out.push({
|
|
2080
|
+
kind: "step-type",
|
|
2081
|
+
severity: "warning",
|
|
2082
|
+
message: "Step type differs",
|
|
2083
|
+
path: path5,
|
|
2084
|
+
left: L.type,
|
|
2085
|
+
right: R.type
|
|
2086
|
+
});
|
|
2087
|
+
}
|
|
2088
|
+
if ((L.status ?? "") !== (R.status ?? "")) {
|
|
2089
|
+
out.push({
|
|
2090
|
+
kind: "step-status",
|
|
2091
|
+
severity: "warning",
|
|
2092
|
+
message: "Step status differs",
|
|
2093
|
+
path: path5,
|
|
2094
|
+
left: L.status,
|
|
2095
|
+
right: R.status
|
|
2096
|
+
});
|
|
2097
|
+
}
|
|
2098
|
+
const le = L.error ?? "";
|
|
2099
|
+
const re = R.error ?? "";
|
|
2100
|
+
if (le !== re) {
|
|
2101
|
+
out.push({
|
|
2102
|
+
kind: "error",
|
|
2103
|
+
severity: "error",
|
|
2104
|
+
message: "Step error message differs",
|
|
2105
|
+
path: path5,
|
|
2106
|
+
left: le || void 0,
|
|
2107
|
+
right: re || void 0
|
|
2108
|
+
});
|
|
2109
|
+
}
|
|
2110
|
+
if (!opts.ignoreDuration) {
|
|
2111
|
+
const ld = L.durationMs;
|
|
2112
|
+
const rd = R.durationMs;
|
|
2113
|
+
const th = opts.durationThresholdMs;
|
|
2114
|
+
let differs = false;
|
|
2115
|
+
if (ld === void 0 && rd === void 0) differs = false;
|
|
2116
|
+
else if (ld === void 0 || rd === void 0) differs = true;
|
|
2117
|
+
else differs = Math.abs(ld - rd) > th;
|
|
2118
|
+
if (differs) {
|
|
2119
|
+
out.push({
|
|
2120
|
+
kind: "duration",
|
|
2121
|
+
severity: "info",
|
|
2122
|
+
message: "Step duration differs",
|
|
2123
|
+
path: path5,
|
|
2124
|
+
left: ld,
|
|
2125
|
+
right: rd
|
|
2126
|
+
});
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
const lm = stableJson(L.metadata ?? {});
|
|
2130
|
+
const rm = stableJson(R.metadata ?? {});
|
|
2131
|
+
if (lm !== rm) {
|
|
2132
|
+
out.push({
|
|
2133
|
+
kind: "metadata",
|
|
2134
|
+
severity: "info",
|
|
2135
|
+
message: "Step metadata differs",
|
|
2136
|
+
path: path5,
|
|
2137
|
+
left: L.metadata,
|
|
2138
|
+
right: R.metadata
|
|
2139
|
+
});
|
|
2140
|
+
}
|
|
2141
|
+
const lo = stableJson(L.outputPreview ?? null);
|
|
2142
|
+
const ro = stableJson(R.outputPreview ?? null);
|
|
2143
|
+
if (lo !== ro) {
|
|
2144
|
+
out.push({
|
|
2145
|
+
kind: "output",
|
|
2146
|
+
severity: "info",
|
|
2147
|
+
message: "Output preview differs",
|
|
2148
|
+
path: path5,
|
|
2149
|
+
left: L.outputPreview,
|
|
2150
|
+
right: R.outputPreview
|
|
2151
|
+
});
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
function compareRecursive(L, R, segments, opts, out) {
|
|
2155
|
+
compareLeafSteps(L, R, segments, opts, out);
|
|
2156
|
+
const pairs = pairSteps(L.children, R.children);
|
|
2157
|
+
let ci = 0;
|
|
2158
|
+
for (const [lch, rch] of pairs) {
|
|
2159
|
+
if (lch !== void 0 && rch !== void 0) {
|
|
2160
|
+
compareRecursive(lch, rch, [...segments, pathSeg(lch, ci)], opts, out);
|
|
2161
|
+
} else if (lch !== void 0) {
|
|
2162
|
+
out.push({
|
|
2163
|
+
kind: "step-removed",
|
|
2164
|
+
severity: "warning",
|
|
2165
|
+
message: `Step only in left run: ${lch.name}`,
|
|
2166
|
+
path: buildPath([...segments, pathSeg(lch, ci)]),
|
|
2167
|
+
left: lch.id,
|
|
2168
|
+
right: void 0
|
|
2169
|
+
});
|
|
2170
|
+
} else if (rch !== void 0) {
|
|
2171
|
+
out.push({
|
|
2172
|
+
kind: "step-added",
|
|
2173
|
+
severity: "warning",
|
|
2174
|
+
message: `Step only in right run: ${rch.name}`,
|
|
2175
|
+
path: buildPath([...segments, pathSeg(rch, ci)]),
|
|
2176
|
+
left: void 0,
|
|
2177
|
+
right: rch.id
|
|
2178
|
+
});
|
|
2179
|
+
}
|
|
2180
|
+
ci += 1;
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
function mergeDiffDefaults(options) {
|
|
2184
|
+
return {
|
|
2185
|
+
ignoreDuration: options?.ignoreDuration ?? false,
|
|
2186
|
+
durationThresholdMs: options?.durationThresholdMs !== void 0 ? options.durationThresholdMs : DEFAULT_THRESHOLD_MS,
|
|
2187
|
+
focus: options?.focus ?? "all",
|
|
2188
|
+
check: options?.check ?? "all"
|
|
2189
|
+
};
|
|
2190
|
+
}
|
|
2191
|
+
function kindMatchesFilter(kind, merged) {
|
|
2192
|
+
const { focus, check } = merged;
|
|
2193
|
+
if (check !== "all") {
|
|
2194
|
+
if (check === "structure") {
|
|
2195
|
+
if (!["step-added", "step-removed", "structure", "step-type"].includes(kind)) return false;
|
|
2196
|
+
} else if (check === "outputs") {
|
|
2197
|
+
if (!["metadata", "output"].includes(kind)) return false;
|
|
2198
|
+
} else if (check === "errors") {
|
|
2199
|
+
if (!["run-status", "step-status", "error"].includes(kind)) return false;
|
|
2200
|
+
} else if (check === "timing") {
|
|
2201
|
+
if (kind !== "duration") return false;
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
if (focus !== "all") {
|
|
2205
|
+
if (focus === "errors") {
|
|
2206
|
+
if (!["run-status", "step-status", "error"].includes(kind)) return false;
|
|
2207
|
+
} else if (focus === "structure") {
|
|
2208
|
+
if (!["step-added", "step-removed", "structure", "step-type"].includes(kind)) return false;
|
|
2209
|
+
} else if (focus === "outputs") {
|
|
2210
|
+
if (!["metadata", "output"].includes(kind)) return false;
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
return true;
|
|
2214
|
+
}
|
|
2215
|
+
function diffRuns(left, right, options) {
|
|
2216
|
+
const merged = mergeDiffDefaults(options);
|
|
2217
|
+
const opts = {
|
|
2218
|
+
ignoreDuration: merged.ignoreDuration,
|
|
2219
|
+
durationThresholdMs: merged.durationThresholdMs
|
|
2220
|
+
};
|
|
2221
|
+
const raw = [];
|
|
2222
|
+
if ((left.status ?? "") !== (right.status ?? "")) {
|
|
2223
|
+
raw.push({
|
|
2224
|
+
kind: "run-status",
|
|
2225
|
+
severity: "warning",
|
|
2226
|
+
message: "Run completion status differs",
|
|
2227
|
+
left: left.status,
|
|
2228
|
+
right: right.status
|
|
2229
|
+
});
|
|
2230
|
+
}
|
|
2231
|
+
if (!merged.ignoreDuration) {
|
|
2232
|
+
const ld = left.durationMs;
|
|
2233
|
+
const rd = right.durationMs;
|
|
2234
|
+
const th = merged.durationThresholdMs;
|
|
2235
|
+
let differs = false;
|
|
2236
|
+
if (ld === void 0 && rd === void 0) differs = false;
|
|
2237
|
+
else if (ld === void 0 || rd === void 0) differs = true;
|
|
2238
|
+
else differs = Math.abs(ld - rd) > th;
|
|
2239
|
+
if (differs) {
|
|
2240
|
+
raw.push({
|
|
2241
|
+
kind: "duration",
|
|
2242
|
+
severity: "info",
|
|
2243
|
+
message: "Run duration differs",
|
|
2244
|
+
left: ld,
|
|
2245
|
+
right: rd
|
|
2246
|
+
});
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
const pairs = pairSteps(left.steps, right.steps);
|
|
2250
|
+
let idx = 0;
|
|
2251
|
+
for (const [ls, rs] of pairs) {
|
|
2252
|
+
if (ls !== void 0 && rs !== void 0) {
|
|
2253
|
+
compareRecursive(ls, rs, [pathSeg(ls, idx)], opts, raw);
|
|
2254
|
+
idx += 1;
|
|
2255
|
+
} else if (ls !== void 0) {
|
|
2256
|
+
raw.push({
|
|
2257
|
+
kind: "step-removed",
|
|
2258
|
+
severity: "warning",
|
|
2259
|
+
message: `Step only in left run: ${ls.name}`,
|
|
2260
|
+
path: buildPath([pathSeg(ls, idx)]),
|
|
2261
|
+
left: ls.id,
|
|
2262
|
+
right: void 0
|
|
2263
|
+
});
|
|
2264
|
+
idx += 1;
|
|
2265
|
+
} else if (rs !== void 0) {
|
|
2266
|
+
raw.push({
|
|
2267
|
+
kind: "step-added",
|
|
2268
|
+
severity: "warning",
|
|
2269
|
+
message: `Step only in right run: ${rs.name}`,
|
|
2270
|
+
path: buildPath([pathSeg(rs, idx)]),
|
|
2271
|
+
left: void 0,
|
|
2272
|
+
right: rs.id
|
|
2273
|
+
});
|
|
2274
|
+
idx += 1;
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
const differences = raw.filter((d) => kindMatchesFilter(d.kind, merged));
|
|
2278
|
+
let errors = 0;
|
|
2279
|
+
let warnings = 0;
|
|
2280
|
+
let info = 0;
|
|
2281
|
+
for (const d of differences) {
|
|
2282
|
+
if (d.severity === "error") errors += 1;
|
|
2283
|
+
else if (d.severity === "warning") warnings += 1;
|
|
2284
|
+
else info += 1;
|
|
2285
|
+
}
|
|
2286
|
+
const firstVisible = differences[0];
|
|
2287
|
+
const firstDivergence = firstVisible !== void 0 ? {
|
|
2288
|
+
kind: "first-divergence",
|
|
2289
|
+
severity: firstVisible.severity,
|
|
2290
|
+
message: `First divergence: ${firstVisible.message}`,
|
|
2291
|
+
path: firstVisible.path,
|
|
2292
|
+
left: firstVisible.left,
|
|
2293
|
+
right: firstVisible.right
|
|
2294
|
+
} : void 0;
|
|
2295
|
+
const summary = {
|
|
2296
|
+
leftRunId: left.runId,
|
|
2297
|
+
rightRunId: right.runId,
|
|
2298
|
+
totalDifferences: differences.length,
|
|
2299
|
+
errors,
|
|
2300
|
+
warnings,
|
|
2301
|
+
info,
|
|
2302
|
+
firstDivergence
|
|
2303
|
+
};
|
|
2304
|
+
return { summary, differences };
|
|
2305
|
+
}
|
|
2306
|
+
function formatPath(path5) {
|
|
2307
|
+
if (path5 === void 0 || path5.path.length === 0) {
|
|
2308
|
+
return "(run)";
|
|
2309
|
+
}
|
|
2310
|
+
return path5.path.map((s) => s.name).join(" > ");
|
|
2311
|
+
}
|
|
2312
|
+
function formatValue(v, verbose) {
|
|
2313
|
+
if (v === void 0) return "(undefined)";
|
|
2314
|
+
if (typeof v === "string") return v;
|
|
2315
|
+
if (typeof v === "number" || typeof v === "boolean") return String(v);
|
|
2316
|
+
const s = JSON.stringify(v);
|
|
2317
|
+
if (verbose || s.length <= 120) return s;
|
|
2318
|
+
return `${s.slice(0, 117)}...`;
|
|
2319
|
+
}
|
|
2320
|
+
function renderRunDiff(result, options) {
|
|
2321
|
+
const json = options?.json === true;
|
|
2322
|
+
const verbose = options?.verbose === true;
|
|
2323
|
+
const color = options?.color === true;
|
|
2324
|
+
if (json) {
|
|
2325
|
+
return JSON.stringify(result, null, 2);
|
|
2326
|
+
}
|
|
2327
|
+
const sev = (s, level) => {
|
|
2328
|
+
if (!color) return s;
|
|
2329
|
+
if (level === "error") return chalk2__default.default.red(s);
|
|
2330
|
+
if (level === "warning") return chalk2__default.default.yellow(s);
|
|
2331
|
+
return chalk2__default.default.gray(s);
|
|
2332
|
+
};
|
|
2333
|
+
const lines = [];
|
|
2334
|
+
const { summary } = result;
|
|
2335
|
+
lines.push("Run diff");
|
|
2336
|
+
lines.push(`Left: ${summary.leftRunId}`);
|
|
2337
|
+
lines.push(`Right: ${summary.rightRunId}`);
|
|
2338
|
+
lines.push("");
|
|
2339
|
+
lines.push("Summary:");
|
|
2340
|
+
lines.push(` Differences: ${summary.totalDifferences}`);
|
|
2341
|
+
lines.push(` Errors: ${summary.errors}`);
|
|
2342
|
+
lines.push(` Warnings: ${summary.warnings}`);
|
|
2343
|
+
lines.push(` Info: ${summary.info}`);
|
|
2344
|
+
lines.push("");
|
|
2345
|
+
const fd = summary.firstDivergence;
|
|
2346
|
+
const firstKind = result.differences[0]?.kind;
|
|
2347
|
+
if (fd !== void 0) {
|
|
2348
|
+
lines.push("First divergence:");
|
|
2349
|
+
const where = formatPath(fd.path);
|
|
2350
|
+
const displayKind = firstKind ?? fd.kind;
|
|
2351
|
+
lines.push(` ${displayKind} at ${where}`);
|
|
2352
|
+
if (fd.left !== void 0 || fd.right !== void 0) {
|
|
2353
|
+
lines.push(` left: ${formatValue(fd.left, verbose)}`);
|
|
2354
|
+
lines.push(` right: ${formatValue(fd.right, verbose)}`);
|
|
2355
|
+
}
|
|
2356
|
+
lines.push("");
|
|
2357
|
+
}
|
|
2358
|
+
lines.push("Differences:");
|
|
2359
|
+
if (result.differences.length === 0) {
|
|
2360
|
+
lines.push(" (none)");
|
|
2361
|
+
return lines.join("\n");
|
|
2362
|
+
}
|
|
2363
|
+
const showSides = (kind) => verbose || [
|
|
2364
|
+
"run-status",
|
|
2365
|
+
"step-status",
|
|
2366
|
+
"error",
|
|
2367
|
+
"duration",
|
|
2368
|
+
"step-type",
|
|
2369
|
+
"structure",
|
|
2370
|
+
"step-added",
|
|
2371
|
+
"step-removed"
|
|
2372
|
+
].includes(kind);
|
|
2373
|
+
for (const d of result.differences) {
|
|
2374
|
+
const tag = sev(`[${d.severity}]`, d.severity);
|
|
2375
|
+
const pathStr = d.path !== void 0 ? ` ${formatPath(d.path)}` : "";
|
|
2376
|
+
lines.push(` ${tag} ${d.kind}${pathStr}`);
|
|
2377
|
+
lines.push(` ${d.message}`);
|
|
2378
|
+
if (d.left !== void 0 || d.right !== void 0) {
|
|
2379
|
+
if (showSides(d.kind)) {
|
|
2380
|
+
lines.push(` left: ${formatValue(d.left, verbose)}`);
|
|
2381
|
+
lines.push(` right: ${formatValue(d.right, verbose)}`);
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
return lines.join("\n");
|
|
2386
|
+
}
|
|
2387
|
+
|
|
2388
|
+
// packages/core/src/diff/index.ts
|
|
2389
|
+
function diffTraceEvents(leftEvents, rightEvents, options) {
|
|
2390
|
+
const left = manualTraceEventsToComparableRun(leftEvents);
|
|
2391
|
+
const right = manualTraceEventsToComparableRun(rightEvents);
|
|
2392
|
+
return diffRuns(left, right, options);
|
|
2393
|
+
}
|
|
505
2394
|
var TERMINAL_INDENT = " ";
|
|
506
2395
|
var MAX_TERMINAL_NAME_LENGTH = 80;
|
|
507
2396
|
var MAX_TERMINAL_DEPTH = 10;
|
|
@@ -527,24 +2416,24 @@ function formatTerminalName(name) {
|
|
|
527
2416
|
return truncateName(name, MAX_TERMINAL_NAME_LENGTH);
|
|
528
2417
|
}
|
|
529
2418
|
function getStatusIcon(status) {
|
|
530
|
-
if (status === "success") return
|
|
531
|
-
if (status === "error") return
|
|
532
|
-
return
|
|
2419
|
+
if (status === "success") return chalk2__default.default.green("\u2714");
|
|
2420
|
+
if (status === "error") return chalk2__default.default.red("\u2716");
|
|
2421
|
+
return chalk2__default.default.yellow("\u23F3");
|
|
533
2422
|
}
|
|
534
2423
|
function renderStepLine(name, durationMs, status, depth) {
|
|
535
2424
|
try {
|
|
536
2425
|
const nm = formatTerminalName(name);
|
|
537
2426
|
const ind = getIndent(depth ?? 0);
|
|
538
2427
|
if (status === "running" && durationMs === void 0) {
|
|
539
|
-
return `${ind}${
|
|
2428
|
+
return `${ind}${chalk2__default.default.yellow("\u23F3")} ${nm}`;
|
|
540
2429
|
}
|
|
541
2430
|
const hasDur = durationMs !== void 0 && Number.isFinite(durationMs);
|
|
542
|
-
const dur = hasDur ?
|
|
2431
|
+
const dur = hasDur ? formatDuration2(durationMs) : void 0;
|
|
543
2432
|
if (status === "running") {
|
|
544
|
-
return dur !== void 0 ? `${ind}${
|
|
2433
|
+
return dur !== void 0 ? `${ind}${chalk2__default.default.yellow("\u23F3")} ${nm} (${dur})` : `${ind}${chalk2__default.default.yellow("\u23F3")} ${nm}`;
|
|
545
2434
|
}
|
|
546
2435
|
if (!hasDur || dur === void 0) {
|
|
547
|
-
return `${ind}${
|
|
2436
|
+
return `${ind}${chalk2__default.default.yellow("\u23F3")} ${nm}`;
|
|
548
2437
|
}
|
|
549
2438
|
if (status === "success") {
|
|
550
2439
|
return `${ind}${getStatusIcon("success")} ${nm} (${dur})`;
|
|
@@ -565,7 +2454,7 @@ function renderErrorLine(error, depth) {
|
|
|
565
2454
|
}
|
|
566
2455
|
function renderRunSummary(durationMs, status, traceFilePath) {
|
|
567
2456
|
try {
|
|
568
|
-
const dur = Number.isFinite(durationMs) ?
|
|
2457
|
+
const dur = Number.isFinite(durationMs) ? formatDuration2(durationMs) : formatDuration2(0);
|
|
569
2458
|
const head = status === "error" ? `Failed in ${dur}` : `Completed in ${dur}`;
|
|
570
2459
|
const lines = [head];
|
|
571
2460
|
if (traceFilePath !== void 0 && traceFilePath.trim() !== "") {
|
|
@@ -580,7 +2469,7 @@ function printRunStart(runId, name) {
|
|
|
580
2469
|
if (isSilentContext()) return;
|
|
581
2470
|
try {
|
|
582
2471
|
safePrint("");
|
|
583
|
-
const header = `${
|
|
2472
|
+
const header = `${chalk2__default.default.cyan.bold("\u{1F50D} AgentInspect:")} ${formatTerminalName(name)} ${chalk2__default.default.dim(`(${runId})`)}`;
|
|
584
2473
|
safePrint(header);
|
|
585
2474
|
} catch {
|
|
586
2475
|
}
|
|
@@ -613,10 +2502,10 @@ function printRunComplete(_name, _runId, durationMs, status, traceFilePath) {
|
|
|
613
2502
|
for (let i = 0; i < lines.length; i++) {
|
|
614
2503
|
const line = lines[i];
|
|
615
2504
|
if (i === 0) {
|
|
616
|
-
const color = status === "error" ?
|
|
2505
|
+
const color = status === "error" ? chalk2__default.default.red : status === "running" ? chalk2__default.default.yellow : chalk2__default.default.green;
|
|
617
2506
|
safePrint(color(line));
|
|
618
2507
|
} else {
|
|
619
|
-
safePrint(
|
|
2508
|
+
safePrint(chalk2__default.default.dim(line));
|
|
620
2509
|
}
|
|
621
2510
|
}
|
|
622
2511
|
} catch {
|
|
@@ -650,7 +2539,7 @@ async function inspectRun(name, fn, options) {
|
|
|
650
2539
|
}
|
|
651
2540
|
const runName = normalizeRunName(name);
|
|
652
2541
|
const runId = createRunId();
|
|
653
|
-
const traceDir =
|
|
2542
|
+
const traceDir = resolveTraceDir({ dir: options?.traceDir });
|
|
654
2543
|
const context = {
|
|
655
2544
|
runId,
|
|
656
2545
|
runName,
|
|
@@ -922,17 +2811,810 @@ function observe(agent, options) {
|
|
|
922
2811
|
}
|
|
923
2812
|
}
|
|
924
2813
|
|
|
2814
|
+
// packages/core/src/exporters/types.ts
|
|
2815
|
+
var EXPORT_PAYLOAD_VERSION = "0.1.2";
|
|
2816
|
+
|
|
2817
|
+
// packages/core/src/exporters/html-exporter.ts
|
|
2818
|
+
function renderTreeHtml(nodes, ulClass = "tree") {
|
|
2819
|
+
if (nodes.length === 0) return "";
|
|
2820
|
+
const parts = [`<ul class="${ulClass}">`];
|
|
2821
|
+
for (const n of nodes) {
|
|
2822
|
+
const ev = n.event;
|
|
2823
|
+
const status = ev.status ?? "?";
|
|
2824
|
+
const dur = ev.durationMs !== void 0 && Number.isFinite(ev.durationMs) ? `${ev.durationMs}ms` : "-";
|
|
2825
|
+
parts.push("<li>");
|
|
2826
|
+
parts.push(
|
|
2827
|
+
`<span class="nm">${escapeHtml(ev.name)}</span> <span class="meta">[${escapeHtml(ev.kind)}] ${escapeHtml(status)} (${escapeHtml(dur)})</span>`
|
|
2828
|
+
);
|
|
2829
|
+
if (n.children.length > 0) {
|
|
2830
|
+
parts.push(renderTreeHtml(n.children, "tree nested"));
|
|
2831
|
+
}
|
|
2832
|
+
parts.push("</li>");
|
|
2833
|
+
}
|
|
2834
|
+
parts.push("</ul>");
|
|
2835
|
+
return parts.join("");
|
|
2836
|
+
}
|
|
2837
|
+
function exportHtml(tree, options) {
|
|
2838
|
+
const warnings = [];
|
|
2839
|
+
const includeMetadata = options?.includeMetadata ?? true;
|
|
2840
|
+
const includeAttributes = options?.includeAttributes ?? false;
|
|
2841
|
+
const includeErrors = options?.includeErrors ?? true;
|
|
2842
|
+
const maxLen = options?.maxAttributeLength ?? 500;
|
|
2843
|
+
const redacted = options?.redacted ?? true;
|
|
2844
|
+
const titleName = escapeHtml(tree.name ?? tree.runId);
|
|
2845
|
+
const summaryRows = [];
|
|
2846
|
+
summaryRows.push(
|
|
2847
|
+
`<tr><th scope="row">runId</th><td><code>${escapeHtml(tree.runId)}</code></td></tr>`
|
|
2848
|
+
);
|
|
2849
|
+
if (tree.name !== void 0) {
|
|
2850
|
+
summaryRows.push(`<tr><th scope="row">name</th><td>${escapeHtml(tree.name)}</td></tr>`);
|
|
2851
|
+
}
|
|
2852
|
+
summaryRows.push(
|
|
2853
|
+
`<tr><th scope="row">status</th><td>${escapeHtml(String(tree.status ?? "unknown"))}</td></tr>`
|
|
2854
|
+
);
|
|
2855
|
+
summaryRows.push(
|
|
2856
|
+
`<tr><th scope="row">durationMs</th><td>${tree.durationMs !== void 0 ? escapeHtml(String(tree.durationMs)) : "\u2014"}</td></tr>`
|
|
2857
|
+
);
|
|
2858
|
+
summaryRows.push(
|
|
2859
|
+
`<tr><th scope="row">startedAt</th><td>${tree.startedAt !== void 0 ? escapeHtml(String(tree.startedAt)) : "\u2014"}</td></tr>`
|
|
2860
|
+
);
|
|
2861
|
+
summaryRows.push(
|
|
2862
|
+
`<tr><th scope="row">endedAt</th><td>${tree.endedAt !== void 0 ? escapeHtml(String(tree.endedAt)) : "\u2014"}</td></tr>`
|
|
2863
|
+
);
|
|
2864
|
+
summaryRows.push(
|
|
2865
|
+
`<tr><th scope="row">totalEvents</th><td>${escapeHtml(String(tree.metadata.totalEvents))}</td></tr>`
|
|
2866
|
+
);
|
|
2867
|
+
let confidenceHtml = "";
|
|
2868
|
+
if (includeMetadata) {
|
|
2869
|
+
const cb = tree.metadata.confidenceBreakdown;
|
|
2870
|
+
confidenceHtml += "<h3>Confidence breakdown</h3><table><thead><tr><th>bucket</th><th>count</th></tr></thead><tbody>";
|
|
2871
|
+
for (const k of Object.keys(cb).sort()) {
|
|
2872
|
+
const key = k;
|
|
2873
|
+
confidenceHtml += `<tr><td>${escapeHtml(key)}</td><td>${cb[key]}</td></tr>`;
|
|
2874
|
+
}
|
|
2875
|
+
confidenceHtml += "</tbody></table>";
|
|
2876
|
+
confidenceHtml += "<h3>Kind breakdown</h3><table><thead><tr><th>kind</th><th>count</th></tr></thead><tbody>";
|
|
2877
|
+
for (const k of Object.keys(tree.metadata.kinds).sort()) {
|
|
2878
|
+
const key = k;
|
|
2879
|
+
const c = tree.metadata.kinds[key];
|
|
2880
|
+
if (c > 0) confidenceHtml += `<tr><td>${escapeHtml(key)}</td><td>${c}</td></tr>`;
|
|
2881
|
+
}
|
|
2882
|
+
confidenceHtml += "</tbody></table>";
|
|
2883
|
+
}
|
|
2884
|
+
const flat = flattenTree(tree);
|
|
2885
|
+
const errors = flat.filter((n) => n.event.status === "error");
|
|
2886
|
+
let errorsHtml = "";
|
|
2887
|
+
if (includeErrors && errors.length > 0) {
|
|
2888
|
+
errorsHtml += "<h2>Errors</h2><ul>";
|
|
2889
|
+
for (const n of errors) {
|
|
2890
|
+
const msg = n.event.attributes && typeof n.event.attributes.error === "object" ? safeString2(
|
|
2891
|
+
n.event.attributes.error.message,
|
|
2892
|
+
maxLen
|
|
2893
|
+
) : "";
|
|
2894
|
+
errorsHtml += `<li><strong>${escapeHtml(n.event.name)}</strong> (${escapeHtml(n.event.eventId)}): ${escapeHtml(msg || "error")}</li>`;
|
|
2895
|
+
}
|
|
2896
|
+
errorsHtml += "</ul>";
|
|
2897
|
+
}
|
|
2898
|
+
let attrsHtml = "";
|
|
2899
|
+
if (includeAttributes) {
|
|
2900
|
+
attrsHtml += "<h2>Attributes (bounded)</h2>";
|
|
2901
|
+
for (const n of flat) {
|
|
2902
|
+
if (!n.event.attributes || Object.keys(n.event.attributes).length === 0) continue;
|
|
2903
|
+
const compact = compactAttributes(n.event.attributes, {
|
|
2904
|
+
maxLength: maxLen,
|
|
2905
|
+
redacted
|
|
2906
|
+
});
|
|
2907
|
+
attrsHtml += `<h3>${escapeHtml(n.event.name)}</h3><pre class="json">${escapeHtml(stableJson(compact, true))}</pre>`;
|
|
2908
|
+
}
|
|
2909
|
+
warnings.push(
|
|
2910
|
+
"Attributes may still contain sensitive data; review exports before sharing."
|
|
2911
|
+
);
|
|
2912
|
+
}
|
|
2913
|
+
const css = `
|
|
2914
|
+
body{font-family:system-ui,sans-serif;line-height:1.5;margin:1.5rem;max-width:960px;color:#111}
|
|
2915
|
+
h1{font-size:1.35rem}
|
|
2916
|
+
h2{font-size:1.1rem;margin-top:1.5rem}
|
|
2917
|
+
table{border-collapse:collapse;margin:0.75rem 0}
|
|
2918
|
+
th,td{border:1px solid #ccc;padding:0.35rem 0.6rem;text-align:left}
|
|
2919
|
+
th{background:#f5f5f5}
|
|
2920
|
+
pre.json{background:#f8f8f8;padding:0.75rem;overflow:auto;font-size:0.85rem}
|
|
2921
|
+
ul.tree{list-style:none;padding-left:1rem}
|
|
2922
|
+
ul.tree.nested{padding-left:1.25rem;border-left:1px solid #ddd;margin:0.25rem 0}
|
|
2923
|
+
.nm{font-weight:600}
|
|
2924
|
+
.meta{color:#555;font-size:0.9rem}
|
|
2925
|
+
footer{margin-top:2rem;font-size:0.85rem;color:#555}
|
|
2926
|
+
`.trim();
|
|
2927
|
+
const html = `<!doctype html>
|
|
2928
|
+
<html lang="en">
|
|
2929
|
+
<head>
|
|
2930
|
+
<meta charset="utf-8"/>
|
|
2931
|
+
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
2932
|
+
<title>${titleName}</title>
|
|
2933
|
+
<style>${css}</style>
|
|
2934
|
+
</head>
|
|
2935
|
+
<body>
|
|
2936
|
+
<header><h1>AgentInspect Run: ${titleName}</h1></header>
|
|
2937
|
+
<p class="note">Generated locally by AgentInspect.</p>
|
|
2938
|
+
${includeMetadata ? `<section class="summary"><h2>Summary</h2><table>${summaryRows.join("")}</table>${confidenceHtml}</section>` : ""}
|
|
2939
|
+
<section class="tree"><h2>Execution tree</h2>${tree.children.length > 0 ? renderTreeHtml(tree.children) : "<p>No steps recorded.</p>"}</section>
|
|
2940
|
+
${errorsHtml}
|
|
2941
|
+
${attrsHtml}
|
|
2942
|
+
<footer>Generated locally by AgentInspect. Review for sensitive data before sharing.</footer>
|
|
2943
|
+
</body>
|
|
2944
|
+
</html>`;
|
|
2945
|
+
return {
|
|
2946
|
+
format: "html",
|
|
2947
|
+
content: html,
|
|
2948
|
+
contentType: "text/html",
|
|
2949
|
+
fileExtension: ".html",
|
|
2950
|
+
warnings
|
|
2951
|
+
};
|
|
2952
|
+
}
|
|
2953
|
+
|
|
2954
|
+
// packages/core/src/exporters/markdown-exporter.ts
|
|
2955
|
+
function renderTreeAscii(nodes, indent = "") {
|
|
2956
|
+
const lines = [];
|
|
2957
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
2958
|
+
const n = nodes[i];
|
|
2959
|
+
const last = i === nodes.length - 1;
|
|
2960
|
+
const branch = last ? "\u2514\u2500 " : "\u251C\u2500 ";
|
|
2961
|
+
const ev = n.event;
|
|
2962
|
+
const status = ev.status ?? "?";
|
|
2963
|
+
const dur = ev.durationMs !== void 0 && Number.isFinite(ev.durationMs) ? `${ev.durationMs}ms` : "-";
|
|
2964
|
+
lines.push(`${indent}${branch}${escapeMarkdown(ev.name)} [${ev.kind}] ${status} (${dur})`);
|
|
2965
|
+
const nextIndent = indent + (last ? " " : "\u2502 ");
|
|
2966
|
+
if (n.children.length > 0) {
|
|
2967
|
+
const childStr = renderTreeAscii(n.children, nextIndent);
|
|
2968
|
+
if (childStr.length > 0) lines.push(childStr);
|
|
2969
|
+
}
|
|
2970
|
+
}
|
|
2971
|
+
return lines.join("\n");
|
|
2972
|
+
}
|
|
2973
|
+
function exportMarkdown(tree, options) {
|
|
2974
|
+
const warnings = [];
|
|
2975
|
+
const includeMetadata = options?.includeMetadata ?? true;
|
|
2976
|
+
const includeAttributes = options?.includeAttributes ?? false;
|
|
2977
|
+
const includeErrors = options?.includeErrors ?? true;
|
|
2978
|
+
const maxLen = options?.maxAttributeLength ?? 500;
|
|
2979
|
+
const redacted = options?.redacted ?? true;
|
|
2980
|
+
const titleName = tree.name ?? tree.runId;
|
|
2981
|
+
const lines = [];
|
|
2982
|
+
lines.push(`# AgentInspect Run: ${escapeMarkdown(titleName)}`);
|
|
2983
|
+
lines.push("");
|
|
2984
|
+
lines.push("Generated locally by AgentInspect. Review for sensitive data before sharing.");
|
|
2985
|
+
lines.push("");
|
|
2986
|
+
if (includeMetadata) {
|
|
2987
|
+
lines.push("## Summary");
|
|
2988
|
+
lines.push("");
|
|
2989
|
+
lines.push(`- **runId**: ${escapeMarkdown(tree.runId)}`);
|
|
2990
|
+
if (tree.name !== void 0) lines.push(`- **name**: ${escapeMarkdown(tree.name)}`);
|
|
2991
|
+
lines.push(`- **status**: ${escapeMarkdown(String(tree.status ?? "unknown"))}`);
|
|
2992
|
+
lines.push(
|
|
2993
|
+
`- **durationMs**: ${tree.durationMs !== void 0 ? escapeMarkdown(String(tree.durationMs)) : "-"}`
|
|
2994
|
+
);
|
|
2995
|
+
lines.push(
|
|
2996
|
+
`- **startedAt**: ${tree.startedAt !== void 0 ? escapeMarkdown(String(tree.startedAt)) : "-"}`
|
|
2997
|
+
);
|
|
2998
|
+
lines.push(
|
|
2999
|
+
`- **endedAt**: ${tree.endedAt !== void 0 ? escapeMarkdown(String(tree.endedAt)) : "-"}`
|
|
3000
|
+
);
|
|
3001
|
+
lines.push(`- **totalEvents**: ${tree.metadata.totalEvents}`);
|
|
3002
|
+
lines.push("");
|
|
3003
|
+
lines.push("### Confidence breakdown");
|
|
3004
|
+
lines.push("");
|
|
3005
|
+
lines.push("| bucket | count |");
|
|
3006
|
+
lines.push("| --- | --- |");
|
|
3007
|
+
for (const k of Object.keys(tree.metadata.confidenceBreakdown).sort()) {
|
|
3008
|
+
const key = k;
|
|
3009
|
+
lines.push(
|
|
3010
|
+
`| ${escapeMarkdown(key)} | ${tree.metadata.confidenceBreakdown[key]} |`
|
|
3011
|
+
);
|
|
3012
|
+
}
|
|
3013
|
+
lines.push("");
|
|
3014
|
+
lines.push("### Kind breakdown");
|
|
3015
|
+
lines.push("");
|
|
3016
|
+
lines.push("| kind | count |");
|
|
3017
|
+
lines.push("| --- | --- |");
|
|
3018
|
+
for (const k of Object.keys(tree.metadata.kinds).sort()) {
|
|
3019
|
+
const key = k;
|
|
3020
|
+
const c = tree.metadata.kinds[key];
|
|
3021
|
+
if (c > 0) lines.push(`| ${escapeMarkdown(key)} | ${c} |`);
|
|
3022
|
+
}
|
|
3023
|
+
lines.push("");
|
|
3024
|
+
}
|
|
3025
|
+
lines.push("## Execution tree");
|
|
3026
|
+
lines.push("");
|
|
3027
|
+
lines.push("```text");
|
|
3028
|
+
lines.push(
|
|
3029
|
+
tree.children.length > 0 ? renderTreeAscii(tree.children) : "(no steps)"
|
|
3030
|
+
);
|
|
3031
|
+
lines.push("```");
|
|
3032
|
+
lines.push("");
|
|
3033
|
+
const flat = flattenTree(tree);
|
|
3034
|
+
const errors = flat.filter((n) => n.event.status === "error");
|
|
3035
|
+
if (includeErrors && errors.length > 0) {
|
|
3036
|
+
lines.push("## Errors");
|
|
3037
|
+
lines.push("");
|
|
3038
|
+
for (const n of errors) {
|
|
3039
|
+
const msg = n.event.attributes && typeof n.event.attributes.error === "object" ? safeString2(
|
|
3040
|
+
n.event.attributes.error.message,
|
|
3041
|
+
maxLen
|
|
3042
|
+
) : "";
|
|
3043
|
+
lines.push(
|
|
3044
|
+
`- **${escapeMarkdown(n.event.name)}** (${escapeMarkdown(n.event.eventId)}): ${escapeMarkdown(msg || "error")}`
|
|
3045
|
+
);
|
|
3046
|
+
}
|
|
3047
|
+
lines.push("");
|
|
3048
|
+
}
|
|
3049
|
+
if (includeAttributes) {
|
|
3050
|
+
lines.push("## Attributes (bounded)");
|
|
3051
|
+
lines.push("");
|
|
3052
|
+
for (const n of flat) {
|
|
3053
|
+
if (!n.event.attributes || Object.keys(n.event.attributes).length === 0) continue;
|
|
3054
|
+
const compact = compactAttributes(n.event.attributes, {
|
|
3055
|
+
maxLength: maxLen,
|
|
3056
|
+
redacted
|
|
3057
|
+
});
|
|
3058
|
+
lines.push(`### ${escapeMarkdown(n.event.name)}`);
|
|
3059
|
+
lines.push("");
|
|
3060
|
+
lines.push("```json");
|
|
3061
|
+
lines.push(stableJson(compact, true));
|
|
3062
|
+
lines.push("```");
|
|
3063
|
+
lines.push("");
|
|
3064
|
+
}
|
|
3065
|
+
warnings.push(
|
|
3066
|
+
"Attributes may still contain sensitive data; review exports before sharing."
|
|
3067
|
+
);
|
|
3068
|
+
}
|
|
3069
|
+
return {
|
|
3070
|
+
format: "markdown",
|
|
3071
|
+
content: lines.join("\n"),
|
|
3072
|
+
contentType: "text/markdown",
|
|
3073
|
+
fileExtension: ".md",
|
|
3074
|
+
warnings
|
|
3075
|
+
};
|
|
3076
|
+
}
|
|
3077
|
+
function hexFrom(seed, byteLen) {
|
|
3078
|
+
return crypto__default.default.createHash("sha256").update(seed, "utf8").digest("hex").slice(0, byteLen * 2);
|
|
3079
|
+
}
|
|
3080
|
+
function mapInspectKindToOI(kind, warnings) {
|
|
3081
|
+
switch (kind) {
|
|
3082
|
+
case "LLM":
|
|
3083
|
+
return { openInferenceKind: "LLM" };
|
|
3084
|
+
case "TOOL":
|
|
3085
|
+
return { openInferenceKind: "TOOL" };
|
|
3086
|
+
case "CHAIN":
|
|
3087
|
+
return { openInferenceKind: "CHAIN" };
|
|
3088
|
+
case "RETRIEVER":
|
|
3089
|
+
return { openInferenceKind: "RETRIEVER" };
|
|
3090
|
+
case "AGENT":
|
|
3091
|
+
return { openInferenceKind: "AGENT" };
|
|
3092
|
+
case "DECISION":
|
|
3093
|
+
warnings.push(
|
|
3094
|
+
`Ambiguous kind DECISION mapped to CHAIN for span compatibility (${EXPORT_PAYLOAD_VERSION}).`
|
|
3095
|
+
);
|
|
3096
|
+
return { openInferenceKind: "CHAIN" };
|
|
3097
|
+
case "RESULT":
|
|
3098
|
+
warnings.push(
|
|
3099
|
+
`Ambiguous kind RESULT mapped to UNKNOWN for span compatibility (${EXPORT_PAYLOAD_VERSION}).`
|
|
3100
|
+
);
|
|
3101
|
+
return { openInferenceKind: "UNKNOWN" };
|
|
3102
|
+
case "ERROR":
|
|
3103
|
+
warnings.push(`ERROR kind mapped to CHAIN for span compatibility.`);
|
|
3104
|
+
return { openInferenceKind: "CHAIN" };
|
|
3105
|
+
case "LOG":
|
|
3106
|
+
case "LOGIC":
|
|
3107
|
+
case "RUN":
|
|
3108
|
+
warnings.push(`${kind} mapped to CHAIN for span compatibility.`);
|
|
3109
|
+
return { openInferenceKind: "CHAIN" };
|
|
3110
|
+
default:
|
|
3111
|
+
warnings.push(`Unhandled InspectKind ${kind} mapped to UNKNOWN.`);
|
|
3112
|
+
return { openInferenceKind: "UNKNOWN" };
|
|
3113
|
+
}
|
|
3114
|
+
}
|
|
3115
|
+
function exportOpenInference(tree, options) {
|
|
3116
|
+
const warnings = [
|
|
3117
|
+
"OpenInference-compatible JSON export is experimental until verified against specific backends.",
|
|
3118
|
+
"This file was generated locally and not sent anywhere."
|
|
3119
|
+
];
|
|
3120
|
+
const traceId = hexFrom(`trace:${tree.runId}`, 16);
|
|
3121
|
+
const includeAttributes = options?.includeAttributes ?? false;
|
|
3122
|
+
const maxLen = options?.maxAttributeLength ?? 500;
|
|
3123
|
+
const pretty = options?.pretty ?? true;
|
|
3124
|
+
const spans = [];
|
|
3125
|
+
for (const n of flattenTree(tree)) {
|
|
3126
|
+
const ev = n.event;
|
|
3127
|
+
const spanId = hexFrom(`${tree.runId}:${ev.eventId}`, 8);
|
|
3128
|
+
const parentSpanHex = ev.parentId ? hexFrom(`${tree.runId}:${ev.parentId}`, 8) : void 0;
|
|
3129
|
+
const startNs = Math.round(ev.timestamp * 1e6);
|
|
3130
|
+
let endNs;
|
|
3131
|
+
if (ev.durationMs !== void 0 && Number.isFinite(ev.durationMs)) {
|
|
3132
|
+
endNs = startNs + Math.round(ev.durationMs * 1e6);
|
|
3133
|
+
}
|
|
3134
|
+
const { openInferenceKind } = mapInspectKindToOI(ev.kind, warnings);
|
|
3135
|
+
const attrs = {
|
|
3136
|
+
"openinference.span.kind": openInferenceKind,
|
|
3137
|
+
"agent_inspect.kind": ev.kind,
|
|
3138
|
+
"agent_inspect.confidence": ev.confidence,
|
|
3139
|
+
"agent_inspect.source.type": ev.source.type,
|
|
3140
|
+
"agent_inspect.run_id": tree.runId,
|
|
3141
|
+
"agent_inspect.event_id": ev.eventId,
|
|
3142
|
+
"agent_inspect.status": ev.status ?? "unset"
|
|
3143
|
+
};
|
|
3144
|
+
if (ev.durationMs !== void 0) {
|
|
3145
|
+
attrs["agent_inspect.duration_ms"] = ev.durationMs;
|
|
3146
|
+
}
|
|
3147
|
+
const meta = ev.attributes;
|
|
3148
|
+
if (meta?.model !== void 0 && typeof meta.model === "string") {
|
|
3149
|
+
attrs["llm.model_name"] = meta.model;
|
|
3150
|
+
}
|
|
3151
|
+
const tokens = meta?.tokens;
|
|
3152
|
+
if (tokens && typeof tokens === "object" && tokens !== null) {
|
|
3153
|
+
const inp = tokens.input;
|
|
3154
|
+
const outp = tokens.output;
|
|
3155
|
+
if (typeof inp === "number") attrs["llm.token_count.prompt"] = inp;
|
|
3156
|
+
if (typeof outp === "number") attrs["llm.token_count.completion"] = outp;
|
|
3157
|
+
}
|
|
3158
|
+
if (includeAttributes && meta && typeof meta === "object") {
|
|
3159
|
+
for (const [k, v] of Object.entries(meta)) {
|
|
3160
|
+
if (k === "tokens" || k === "model") continue;
|
|
3161
|
+
if (v !== void 0 && v !== null && typeof v !== "object") {
|
|
3162
|
+
attrs[`agent_inspect.preview.${k}`] = typeof v === "string" ? v.slice(0, maxLen) : v;
|
|
3163
|
+
}
|
|
3164
|
+
}
|
|
3165
|
+
}
|
|
3166
|
+
let status;
|
|
3167
|
+
if (ev.status === "error") {
|
|
3168
|
+
const msg = meta && typeof meta.error === "object" && meta.error !== null ? String(meta.error.message ?? "error") : "error";
|
|
3169
|
+
status = { code: "ERROR", message: msg.slice(0, maxLen) };
|
|
3170
|
+
} else if (ev.status === "ok") {
|
|
3171
|
+
status = { code: "OK" };
|
|
3172
|
+
} else {
|
|
3173
|
+
status = { code: "UNSET" };
|
|
3174
|
+
}
|
|
3175
|
+
spans.push({
|
|
3176
|
+
trace_id: traceId,
|
|
3177
|
+
span_id: spanId,
|
|
3178
|
+
parent_span_id: parentSpanHex,
|
|
3179
|
+
name: ev.name,
|
|
3180
|
+
start_time_unix_nano: startNs,
|
|
3181
|
+
end_time_unix_nano: endNs,
|
|
3182
|
+
attributes: attrs,
|
|
3183
|
+
status
|
|
3184
|
+
});
|
|
3185
|
+
}
|
|
3186
|
+
const payload = {
|
|
3187
|
+
exporter: "agent-inspect",
|
|
3188
|
+
format: "openinference",
|
|
3189
|
+
compatibility: "openinference-compatible",
|
|
3190
|
+
version: EXPORT_PAYLOAD_VERSION,
|
|
3191
|
+
trace_id: traceId,
|
|
3192
|
+
spans,
|
|
3193
|
+
warnings
|
|
3194
|
+
};
|
|
3195
|
+
return {
|
|
3196
|
+
format: "openinference",
|
|
3197
|
+
content: JSON.stringify(payload, null, pretty ? 2 : void 0),
|
|
3198
|
+
contentType: "application/json",
|
|
3199
|
+
fileExtension: ".openinference.json",
|
|
3200
|
+
warnings
|
|
3201
|
+
};
|
|
3202
|
+
}
|
|
3203
|
+
function hexFrom2(seed, byteLen) {
|
|
3204
|
+
return crypto__default.default.createHash("sha256").update(seed, "utf8").digest("hex").slice(0, byteLen * 2);
|
|
3205
|
+
}
|
|
3206
|
+
function stringAttr(key, value) {
|
|
3207
|
+
return { key, value: { stringValue: value } };
|
|
3208
|
+
}
|
|
3209
|
+
function intAttr(key, value) {
|
|
3210
|
+
return { key, value: { intValue: String(value) } };
|
|
3211
|
+
}
|
|
3212
|
+
function genAiOperationName(kind) {
|
|
3213
|
+
switch (kind) {
|
|
3214
|
+
case "LLM":
|
|
3215
|
+
return "generate_content";
|
|
3216
|
+
case "TOOL":
|
|
3217
|
+
return "execute_tool";
|
|
3218
|
+
case "AGENT":
|
|
3219
|
+
return "invoke_agent";
|
|
3220
|
+
default:
|
|
3221
|
+
return void 0;
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3224
|
+
function exportOtlpJson(tree, options) {
|
|
3225
|
+
const warnings = [
|
|
3226
|
+
"OTLP JSON export uses OTel GenAI-aligned attributes where applicable; experimental until verified against specific collectors.",
|
|
3227
|
+
"Not OTLP gRPC/protobuf \u2014 JSON mapping only. Generated locally; no network upload."
|
|
3228
|
+
];
|
|
3229
|
+
const traceId = hexFrom2(`trace:${tree.runId}`, 16);
|
|
3230
|
+
const includeAttributes = options?.includeAttributes ?? false;
|
|
3231
|
+
const maxLen = options?.maxAttributeLength ?? 500;
|
|
3232
|
+
const pretty = options?.pretty ?? true;
|
|
3233
|
+
const flat = flattenTree(tree);
|
|
3234
|
+
const spans = [];
|
|
3235
|
+
for (const n of flat) {
|
|
3236
|
+
const ev = n.event;
|
|
3237
|
+
const spanId = hexFrom2(`${tree.runId}:${ev.eventId}`, 8);
|
|
3238
|
+
const parentSpanId = ev.parentId ? hexFrom2(`${tree.runId}:${ev.parentId}`, 8) : void 0;
|
|
3239
|
+
const startNs = String(Math.round(ev.timestamp * 1e6));
|
|
3240
|
+
let endNs;
|
|
3241
|
+
if (ev.durationMs !== void 0 && Number.isFinite(ev.durationMs)) {
|
|
3242
|
+
endNs = String(Math.round(ev.timestamp * 1e6 + ev.durationMs * 1e6));
|
|
3243
|
+
}
|
|
3244
|
+
const attrs = [
|
|
3245
|
+
stringAttr("agent_inspect.kind", ev.kind),
|
|
3246
|
+
stringAttr("agent_inspect.confidence", ev.confidence),
|
|
3247
|
+
stringAttr("agent_inspect.source.type", ev.source.type),
|
|
3248
|
+
stringAttr("agent_inspect.run_id", tree.runId),
|
|
3249
|
+
stringAttr("agent_inspect.event_id", ev.eventId),
|
|
3250
|
+
stringAttr("agent_inspect.status", ev.status ?? "unset")
|
|
3251
|
+
];
|
|
3252
|
+
if (ev.durationMs !== void 0) {
|
|
3253
|
+
attrs.push(intAttr("agent_inspect.duration_ms", ev.durationMs));
|
|
3254
|
+
}
|
|
3255
|
+
const op = genAiOperationName(ev.kind);
|
|
3256
|
+
if (op !== void 0) {
|
|
3257
|
+
attrs.push(stringAttr("gen_ai.operation.name", op));
|
|
3258
|
+
}
|
|
3259
|
+
const meta = ev.attributes;
|
|
3260
|
+
if (meta?.model !== void 0 && typeof meta.model === "string") {
|
|
3261
|
+
attrs.push(stringAttr("gen_ai.request.model", meta.model.slice(0, maxLen)));
|
|
3262
|
+
}
|
|
3263
|
+
const tokens = meta?.tokens;
|
|
3264
|
+
if (tokens && typeof tokens === "object" && tokens !== null) {
|
|
3265
|
+
const inp = tokens.input;
|
|
3266
|
+
const outp = tokens.output;
|
|
3267
|
+
if (typeof inp === "number") attrs.push(intAttr("gen_ai.usage.input_tokens", inp));
|
|
3268
|
+
if (typeof outp === "number") attrs.push(intAttr("gen_ai.usage.output_tokens", outp));
|
|
3269
|
+
}
|
|
3270
|
+
if (includeAttributes && meta && typeof meta === "object") {
|
|
3271
|
+
for (const [k, v] of Object.entries(meta)) {
|
|
3272
|
+
if (k === "tokens" || k === "model") continue;
|
|
3273
|
+
if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
|
|
3274
|
+
attrs.push(
|
|
3275
|
+
stringAttr(
|
|
3276
|
+
`agent_inspect.preview.${k}`,
|
|
3277
|
+
typeof v === "string" ? v.slice(0, maxLen) : String(v)
|
|
3278
|
+
)
|
|
3279
|
+
);
|
|
3280
|
+
}
|
|
3281
|
+
}
|
|
3282
|
+
}
|
|
3283
|
+
let statusCode = "STATUS_CODE_UNSET";
|
|
3284
|
+
let statusMessage;
|
|
3285
|
+
if (ev.status === "error") {
|
|
3286
|
+
statusCode = "STATUS_CODE_ERROR";
|
|
3287
|
+
statusMessage = meta && typeof meta.error === "object" && meta.error !== null ? String(meta.error.message ?? "error").slice(0, maxLen) : "error";
|
|
3288
|
+
} else if (ev.status === "ok") {
|
|
3289
|
+
statusCode = "STATUS_CODE_OK";
|
|
3290
|
+
}
|
|
3291
|
+
const spanJson = {
|
|
3292
|
+
traceId,
|
|
3293
|
+
spanId,
|
|
3294
|
+
name: ev.name,
|
|
3295
|
+
kind: "SPAN_KIND_INTERNAL",
|
|
3296
|
+
startTimeUnixNano: startNs,
|
|
3297
|
+
attributes: attrs,
|
|
3298
|
+
status: {
|
|
3299
|
+
code: statusCode,
|
|
3300
|
+
...statusMessage !== void 0 ? { message: statusMessage } : {}
|
|
3301
|
+
}
|
|
3302
|
+
};
|
|
3303
|
+
if (parentSpanId !== void 0) {
|
|
3304
|
+
spanJson.parentSpanId = parentSpanId;
|
|
3305
|
+
}
|
|
3306
|
+
if (endNs !== void 0) {
|
|
3307
|
+
spanJson.endTimeUnixNano = endNs;
|
|
3308
|
+
}
|
|
3309
|
+
spans.push(spanJson);
|
|
3310
|
+
}
|
|
3311
|
+
const payload = {
|
|
3312
|
+
resourceSpans: [
|
|
3313
|
+
{
|
|
3314
|
+
resource: {
|
|
3315
|
+
attributes: [stringAttr("service.name", "agent-inspect")]
|
|
3316
|
+
},
|
|
3317
|
+
scopeSpans: [
|
|
3318
|
+
{
|
|
3319
|
+
scope: { name: "agent-inspect" },
|
|
3320
|
+
spans
|
|
3321
|
+
}
|
|
3322
|
+
]
|
|
3323
|
+
}
|
|
3324
|
+
]
|
|
3325
|
+
};
|
|
3326
|
+
return {
|
|
3327
|
+
format: "otlp-json",
|
|
3328
|
+
content: JSON.stringify(payload, null, pretty ? 2 : void 0),
|
|
3329
|
+
contentType: "application/json",
|
|
3330
|
+
fileExtension: ".otlp.json",
|
|
3331
|
+
warnings
|
|
3332
|
+
};
|
|
3333
|
+
}
|
|
3334
|
+
|
|
3335
|
+
// packages/core/src/exporters/validation.ts
|
|
3336
|
+
var EXPERIMENTAL = "Experimental compatibility export \u2014 verify against your target tooling before relying on it.";
|
|
3337
|
+
function validateExportContent(format, content) {
|
|
3338
|
+
const errors = [];
|
|
3339
|
+
const warnings = [EXPERIMENTAL];
|
|
3340
|
+
if (format === "markdown") {
|
|
3341
|
+
if (!content.startsWith("# AgentInspect Run")) {
|
|
3342
|
+
errors.push('Markdown export must start with "# AgentInspect Run"');
|
|
3343
|
+
}
|
|
3344
|
+
return { ok: errors.length === 0, format, errors, warnings };
|
|
3345
|
+
}
|
|
3346
|
+
if (format === "html") {
|
|
3347
|
+
const lower = content.toLowerCase();
|
|
3348
|
+
if (!lower.includes("<!doctype html")) {
|
|
3349
|
+
errors.push("HTML export must include <!doctype html>");
|
|
3350
|
+
}
|
|
3351
|
+
if (/<\s*script\b/i.test(content)) {
|
|
3352
|
+
errors.push("HTML export must not contain script tags");
|
|
3353
|
+
}
|
|
3354
|
+
if (/<\s*link\b[^>]*href\s*=/i.test(content)) {
|
|
3355
|
+
warnings.push("HTML export contains link tags \u2014 ensure no external stylesheets.");
|
|
3356
|
+
}
|
|
3357
|
+
return { ok: errors.length === 0, format, errors, warnings };
|
|
3358
|
+
}
|
|
3359
|
+
if (format === "openinference") {
|
|
3360
|
+
let parsed;
|
|
3361
|
+
try {
|
|
3362
|
+
parsed = JSON.parse(content);
|
|
3363
|
+
} catch {
|
|
3364
|
+
errors.push("OpenInference export is not valid JSON");
|
|
3365
|
+
return { ok: false, format, errors, warnings };
|
|
3366
|
+
}
|
|
3367
|
+
if (!parsed || typeof parsed !== "object") {
|
|
3368
|
+
errors.push("OpenInference export JSON must be an object");
|
|
3369
|
+
return { ok: false, format, errors, warnings };
|
|
3370
|
+
}
|
|
3371
|
+
const o = parsed;
|
|
3372
|
+
if (o.format !== "openinference") {
|
|
3373
|
+
errors.push('OpenInference export must include format: "openinference"');
|
|
3374
|
+
}
|
|
3375
|
+
if (!Array.isArray(o.spans)) {
|
|
3376
|
+
errors.push("OpenInference export must include a spans array");
|
|
3377
|
+
}
|
|
3378
|
+
warnings.push("OpenInference-compatible JSON is not guaranteed for every backend.");
|
|
3379
|
+
return { ok: errors.length === 0, format, errors, warnings };
|
|
3380
|
+
}
|
|
3381
|
+
if (format === "otlp-json") {
|
|
3382
|
+
let parsed;
|
|
3383
|
+
try {
|
|
3384
|
+
parsed = JSON.parse(content);
|
|
3385
|
+
} catch {
|
|
3386
|
+
errors.push("OTLP JSON export is not valid JSON");
|
|
3387
|
+
return { ok: false, format, errors, warnings };
|
|
3388
|
+
}
|
|
3389
|
+
if (!parsed || typeof parsed !== "object") {
|
|
3390
|
+
errors.push("OTLP JSON export must be an object");
|
|
3391
|
+
return { ok: false, format, errors, warnings };
|
|
3392
|
+
}
|
|
3393
|
+
const o = parsed;
|
|
3394
|
+
if (!Array.isArray(o.resourceSpans)) {
|
|
3395
|
+
errors.push("OTLP JSON export must include resourceSpans array");
|
|
3396
|
+
}
|
|
3397
|
+
warnings.push(
|
|
3398
|
+
"OTLP JSON mapping uses OTel GenAI-aligned attributes where applicable; collectors may require transformation."
|
|
3399
|
+
);
|
|
3400
|
+
return { ok: errors.length === 0, format, errors, warnings };
|
|
3401
|
+
}
|
|
3402
|
+
errors.push(`Unsupported export format`);
|
|
3403
|
+
return { ok: false, format, errors, warnings };
|
|
3404
|
+
}
|
|
3405
|
+
|
|
3406
|
+
// packages/core/src/exporters/manual-trace-adapter.ts
|
|
3407
|
+
function stepTypeToInspectKind(t) {
|
|
3408
|
+
switch (t) {
|
|
3409
|
+
case "llm":
|
|
3410
|
+
return "LLM";
|
|
3411
|
+
case "tool":
|
|
3412
|
+
return "TOOL";
|
|
3413
|
+
case "decision":
|
|
3414
|
+
return "DECISION";
|
|
3415
|
+
case "run":
|
|
3416
|
+
return "CHAIN";
|
|
3417
|
+
default:
|
|
3418
|
+
return "LOGIC";
|
|
3419
|
+
}
|
|
3420
|
+
}
|
|
3421
|
+
function mapStepStatus2(s) {
|
|
3422
|
+
if (s === void 0) return "running";
|
|
3423
|
+
if (s === "success") return "ok";
|
|
3424
|
+
return "error";
|
|
3425
|
+
}
|
|
3426
|
+
function manualTraceEventsToRunTree(events) {
|
|
3427
|
+
const started = events.find((e) => e.event === "run_started");
|
|
3428
|
+
if (!started || started.event !== "run_started") {
|
|
3429
|
+
throw new Error("Invalid trace: missing run_started");
|
|
3430
|
+
}
|
|
3431
|
+
const runId = started.runId;
|
|
3432
|
+
const runName = started.name;
|
|
3433
|
+
const completedAll = events.filter((e) => e.event === "run_completed");
|
|
3434
|
+
const lastCompleted = completedAll[completedAll.length - 1];
|
|
3435
|
+
let runStatus;
|
|
3436
|
+
if (lastCompleted === void 0) {
|
|
3437
|
+
runStatus = "running";
|
|
3438
|
+
} else if (lastCompleted.status === "success") {
|
|
3439
|
+
runStatus = "ok";
|
|
3440
|
+
} else {
|
|
3441
|
+
runStatus = "error";
|
|
3442
|
+
}
|
|
3443
|
+
const startedAt = started.startTime;
|
|
3444
|
+
const endedAt = lastCompleted !== void 0 && runStatus !== "running" ? lastCompleted.endTime : void 0;
|
|
3445
|
+
const durationMs = lastCompleted !== void 0 && Number.isFinite(lastCompleted.durationMs) ? lastCompleted.durationMs : void 0;
|
|
3446
|
+
const steps = /* @__PURE__ */ new Map();
|
|
3447
|
+
for (const e of events) {
|
|
3448
|
+
if (e.event !== "step_started") continue;
|
|
3449
|
+
const s = e;
|
|
3450
|
+
steps.set(s.stepId, {
|
|
3451
|
+
id: s.stepId,
|
|
3452
|
+
parentId: s.parentId,
|
|
3453
|
+
name: s.name,
|
|
3454
|
+
type: s.type,
|
|
3455
|
+
startTime: s.startTime,
|
|
3456
|
+
timestamp: s.timestamp,
|
|
3457
|
+
metadata: s.metadata
|
|
3458
|
+
});
|
|
3459
|
+
}
|
|
3460
|
+
for (const e of events) {
|
|
3461
|
+
if (e.event !== "step_completed") continue;
|
|
3462
|
+
const acc = steps.get(e.stepId);
|
|
3463
|
+
if (!acc) continue;
|
|
3464
|
+
acc.status = e.status;
|
|
3465
|
+
acc.endTime = e.endTime;
|
|
3466
|
+
acc.durationMs = e.durationMs;
|
|
3467
|
+
if (e.error?.message) {
|
|
3468
|
+
acc.error = e.error;
|
|
3469
|
+
}
|
|
3470
|
+
}
|
|
3471
|
+
const inspectNodes = /* @__PURE__ */ new Map();
|
|
3472
|
+
for (const acc of steps.values()) {
|
|
3473
|
+
const kind = stepTypeToInspectKind(acc.type);
|
|
3474
|
+
const status = mapStepStatus2(acc.status);
|
|
3475
|
+
const attrs = { ...acc.metadata ?? {} };
|
|
3476
|
+
if (acc.error?.message) {
|
|
3477
|
+
attrs.error = acc.error;
|
|
3478
|
+
}
|
|
3479
|
+
const evt = {
|
|
3480
|
+
eventId: acc.id,
|
|
3481
|
+
runId,
|
|
3482
|
+
parentId: acc.parentId,
|
|
3483
|
+
name: acc.name,
|
|
3484
|
+
kind,
|
|
3485
|
+
timestamp: acc.timestamp,
|
|
3486
|
+
status,
|
|
3487
|
+
durationMs: acc.durationMs,
|
|
3488
|
+
attributes: Object.keys(attrs).length > 0 ? attrs : void 0,
|
|
3489
|
+
confidence: "explicit",
|
|
3490
|
+
source: { type: "manual" }
|
|
3491
|
+
};
|
|
3492
|
+
inspectNodes.set(acc.id, { event: evt, children: [], depth: 0 });
|
|
3493
|
+
}
|
|
3494
|
+
const roots = [];
|
|
3495
|
+
const sortByStart = (a, b) => a.event.timestamp - b.event.timestamp;
|
|
3496
|
+
for (const node of inspectNodes.values()) {
|
|
3497
|
+
const pid = node.event.parentId;
|
|
3498
|
+
if (pid !== void 0 && inspectNodes.has(pid)) {
|
|
3499
|
+
inspectNodes.get(pid).children.push(node);
|
|
3500
|
+
} else {
|
|
3501
|
+
roots.push(node);
|
|
3502
|
+
}
|
|
3503
|
+
}
|
|
3504
|
+
roots.sort(sortByStart);
|
|
3505
|
+
for (const n of inspectNodes.values()) {
|
|
3506
|
+
n.children.sort(sortByStart);
|
|
3507
|
+
}
|
|
3508
|
+
const assignDepth = (n, depth) => {
|
|
3509
|
+
n.depth = depth;
|
|
3510
|
+
for (const c of n.children) assignDepth(c, depth + 1);
|
|
3511
|
+
};
|
|
3512
|
+
for (const r of roots) assignDepth(r, 0);
|
|
3513
|
+
const confidenceBreakdown = {
|
|
3514
|
+
explicit: 0,
|
|
3515
|
+
correlated: 0,
|
|
3516
|
+
heuristic: 0,
|
|
3517
|
+
unknown: 0
|
|
3518
|
+
};
|
|
3519
|
+
const kinds = zeroKinds();
|
|
3520
|
+
function countWalk(nodes) {
|
|
3521
|
+
for (const n of nodes) {
|
|
3522
|
+
confidenceBreakdown[n.event.confidence] += 1;
|
|
3523
|
+
kinds[n.event.kind] += 1;
|
|
3524
|
+
if (n.children.length > 0) countWalk(n.children);
|
|
3525
|
+
}
|
|
3526
|
+
}
|
|
3527
|
+
countWalk(roots);
|
|
3528
|
+
return {
|
|
3529
|
+
runId,
|
|
3530
|
+
name: runName,
|
|
3531
|
+
status: runStatus,
|
|
3532
|
+
startedAt,
|
|
3533
|
+
endedAt,
|
|
3534
|
+
durationMs,
|
|
3535
|
+
children: roots,
|
|
3536
|
+
metadata: {
|
|
3537
|
+
totalEvents: inspectNodes.size,
|
|
3538
|
+
confidenceBreakdown,
|
|
3539
|
+
kinds
|
|
3540
|
+
}
|
|
3541
|
+
};
|
|
3542
|
+
}
|
|
3543
|
+
|
|
3544
|
+
// packages/core/src/exporters/index.ts
|
|
3545
|
+
function mergeExportDefaults(options) {
|
|
3546
|
+
return {
|
|
3547
|
+
format: options.format,
|
|
3548
|
+
includeMetadata: options.includeMetadata ?? true,
|
|
3549
|
+
includeAttributes: options.includeAttributes ?? false,
|
|
3550
|
+
includeErrors: options.includeErrors ?? true,
|
|
3551
|
+
pretty: options.pretty ?? true,
|
|
3552
|
+
redacted: options.redacted ?? true,
|
|
3553
|
+
maxAttributeLength: options.maxAttributeLength ?? 500
|
|
3554
|
+
};
|
|
3555
|
+
}
|
|
3556
|
+
function exportRunTree(tree, options) {
|
|
3557
|
+
const opts = mergeExportDefaults(options);
|
|
3558
|
+
switch (opts.format) {
|
|
3559
|
+
case "markdown":
|
|
3560
|
+
return exportMarkdown(tree, opts);
|
|
3561
|
+
case "html":
|
|
3562
|
+
return exportHtml(tree, opts);
|
|
3563
|
+
case "openinference":
|
|
3564
|
+
return exportOpenInference(tree, opts);
|
|
3565
|
+
case "otlp-json":
|
|
3566
|
+
return exportOtlpJson(tree, opts);
|
|
3567
|
+
default: {
|
|
3568
|
+
const _x = opts.format;
|
|
3569
|
+
throw new Error(`Unsupported export format: ${String(_x)}`);
|
|
3570
|
+
}
|
|
3571
|
+
}
|
|
3572
|
+
}
|
|
3573
|
+
function validateExport(result) {
|
|
3574
|
+
const base = validateExportContent(result.format, result.content);
|
|
3575
|
+
return {
|
|
3576
|
+
ok: base.ok,
|
|
3577
|
+
format: base.format,
|
|
3578
|
+
errors: base.errors,
|
|
3579
|
+
warnings: [...result.warnings, ...base.warnings]
|
|
3580
|
+
};
|
|
3581
|
+
}
|
|
3582
|
+
|
|
3583
|
+
exports.DEFAULT_LOG_INGEST_CONFIG = DEFAULT_LOG_INGEST_CONFIG;
|
|
3584
|
+
exports.DEFAULT_REDACT_KEYS = DEFAULT_REDACT_KEYS;
|
|
925
3585
|
exports.DEFAULT_TRACE_DIR_NAME = DEFAULT_TRACE_DIR_NAME;
|
|
3586
|
+
exports.EXPORT_PAYLOAD_VERSION = EXPORT_PAYLOAD_VERSION;
|
|
3587
|
+
exports.EventNormalizer = EventNormalizer;
|
|
926
3588
|
exports.FALLBACK_TRACE_DIR = FALLBACK_TRACE_DIR;
|
|
3589
|
+
exports.JsonLogParser = JsonLogParser;
|
|
3590
|
+
exports.LiveLogAccumulator = LiveLogAccumulator;
|
|
3591
|
+
exports.Log4jsParser = Log4jsParser;
|
|
927
3592
|
exports.MAX_NAME_LENGTH = MAX_NAME_LENGTH;
|
|
928
3593
|
exports.MAX_TERMINAL_DEPTH = MAX_TERMINAL_DEPTH;
|
|
929
3594
|
exports.MAX_TERMINAL_NAME_LENGTH = MAX_TERMINAL_NAME_LENGTH;
|
|
930
3595
|
exports.RUNS_DIR_NAME = RUNS_DIR_NAME;
|
|
3596
|
+
exports.Redactor = Redactor;
|
|
931
3597
|
exports.TERMINAL_INDENT = TERMINAL_INDENT;
|
|
3598
|
+
exports.TraceDirectory = TraceDirectory;
|
|
3599
|
+
exports.TreeBuilder = TreeBuilder;
|
|
3600
|
+
exports.buildRunSummary = buildRunSummary;
|
|
3601
|
+
exports.compactAttributes = compactAttributes;
|
|
932
3602
|
exports.createRunId = createRunId;
|
|
933
3603
|
exports.createStepId = createStepId;
|
|
3604
|
+
exports.diffRuns = diffRuns;
|
|
3605
|
+
exports.diffTraceEvents = diffTraceEvents;
|
|
934
3606
|
exports.ensureTraceDir = ensureTraceDir;
|
|
935
|
-
exports.
|
|
3607
|
+
exports.escapeHtml = escapeHtml;
|
|
3608
|
+
exports.escapeMarkdown = escapeMarkdown;
|
|
3609
|
+
exports.exportHtml = exportHtml;
|
|
3610
|
+
exports.exportMarkdown = exportMarkdown;
|
|
3611
|
+
exports.exportOpenInference = exportOpenInference;
|
|
3612
|
+
exports.exportOtlpJson = exportOtlpJson;
|
|
3613
|
+
exports.exportRunTree = exportRunTree;
|
|
3614
|
+
exports.extractMetadata = extractMetadata;
|
|
3615
|
+
exports.filterTraces = filterTraces;
|
|
3616
|
+
exports.flattenTree = flattenTree;
|
|
3617
|
+
exports.formatDuration = formatDuration2;
|
|
936
3618
|
exports.formatError = formatError;
|
|
937
3619
|
exports.formatTerminalName = formatTerminalName;
|
|
938
3620
|
exports.formatTimestamp = formatTimestamp;
|
|
@@ -950,12 +3632,22 @@ exports.getTraceFilePath = getTraceFilePath;
|
|
|
950
3632
|
exports.hasActiveContext = hasActiveContext;
|
|
951
3633
|
exports.initializeTraceFile = initializeTraceFile;
|
|
952
3634
|
exports.inspectRun = inspectRun;
|
|
3635
|
+
exports.isAgentInspectTrace = isAgentInspectTrace;
|
|
953
3636
|
exports.isSilentContext = isSilentContext;
|
|
954
3637
|
exports.isStepStatus = isStepStatus;
|
|
955
3638
|
exports.isStepType = isStepType;
|
|
956
3639
|
exports.isTraceEvent = isTraceEvent;
|
|
957
3640
|
exports.listTraceFiles = listTraceFiles;
|
|
3641
|
+
exports.loadLogIngestConfig = loadLogIngestConfig;
|
|
3642
|
+
exports.manualTraceEventsToComparableRun = manualTraceEventsToComparableRun;
|
|
3643
|
+
exports.manualTraceEventsToRunTree = manualTraceEventsToRunTree;
|
|
3644
|
+
exports.matchMapping = matchMapping;
|
|
3645
|
+
exports.mergeExportDefaults = mergeExportDefaults;
|
|
3646
|
+
exports.mergeLogIngestConfig = mergeLogIngestConfig;
|
|
958
3647
|
exports.observe = observe;
|
|
3648
|
+
exports.parseDuration = parseDuration;
|
|
3649
|
+
exports.parseLogLine = parseLogLine;
|
|
3650
|
+
exports.parseLogsToTrees = parseLogsToTrees;
|
|
959
3651
|
exports.printError = printError;
|
|
960
3652
|
exports.printFailedAt = printFailedAt;
|
|
961
3653
|
exports.printRunComplete = printRunComplete;
|
|
@@ -965,15 +3657,25 @@ exports.printStepStart = printStepStart;
|
|
|
965
3657
|
exports.readTraceEvents = readTraceEvents;
|
|
966
3658
|
exports.readTraceFile = readTraceFile;
|
|
967
3659
|
exports.renderErrorLine = renderErrorLine;
|
|
3660
|
+
exports.renderRunDiff = renderRunDiff;
|
|
968
3661
|
exports.renderRunSummary = renderRunSummary;
|
|
3662
|
+
exports.renderRunTree = renderRunTree;
|
|
3663
|
+
exports.renderRunTrees = renderRunTrees;
|
|
969
3664
|
exports.renderStepLine = renderStepLine;
|
|
3665
|
+
exports.resolveTraceDir = resolveTraceDir;
|
|
970
3666
|
exports.runWithContext = runWithContext;
|
|
971
3667
|
exports.runWithStepContext = runWithStepContext;
|
|
3668
|
+
exports.safeString = safeString2;
|
|
972
3669
|
exports.serializeEvent = serializeEvent;
|
|
3670
|
+
exports.stableJson = stableJson;
|
|
973
3671
|
exports.step = step;
|
|
3672
|
+
exports.summarizeTree = summarizeTree;
|
|
974
3673
|
exports.truncateName = truncateName;
|
|
975
3674
|
exports.validateEvent = validateEvent;
|
|
3675
|
+
exports.validateExport = validateExport;
|
|
3676
|
+
exports.validateExportContent = validateExportContent;
|
|
976
3677
|
exports.warn = warn;
|
|
3678
|
+
exports.wildcardMatch = wildcardMatch;
|
|
977
3679
|
exports.writeTraceEvent = writeTraceEvent;
|
|
978
3680
|
//# sourceMappingURL=index.cjs.map
|
|
979
3681
|
//# sourceMappingURL=index.cjs.map
|