agent-inspect 1.3.0 → 1.5.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.
Files changed (82) hide show
  1. package/CHANGELOG.md +47 -4
  2. package/README.md +59 -37
  3. package/docs/ADAPTERS.md +117 -31
  4. package/docs/API.md +24 -6
  5. package/docs/CLI.md +126 -1
  6. package/docs/DIFF.md +8 -0
  7. package/docs/EXPORTS.md +86 -15
  8. package/docs/GETTING-STARTED.md +48 -2
  9. package/docs/KNOWN-ISSUES.md +6 -0
  10. package/docs/LIMITATIONS.md +11 -3
  11. package/docs/LOGS.md +22 -0
  12. package/docs/SCHEMA.md +8 -5
  13. package/docs/SCREENSHOTS.md +190 -9
  14. package/package.json +51 -1
  15. package/packages/cli/dist/index.cjs +3253 -1662
  16. package/packages/cli/dist/index.cjs.map +1 -1
  17. package/packages/cli/dist/index.mjs +3253 -1662
  18. package/packages/cli/dist/index.mjs.map +1 -1
  19. package/packages/core/dist/advanced.cjs +1437 -0
  20. package/packages/core/dist/advanced.cjs.map +1 -0
  21. package/packages/core/dist/advanced.d.cts +159 -0
  22. package/packages/core/dist/advanced.d.ts +159 -0
  23. package/packages/core/dist/advanced.mjs +8 -0
  24. package/packages/core/dist/advanced.mjs.map +1 -0
  25. package/packages/core/dist/chunk-5EMIZZXD.mjs +907 -0
  26. package/packages/core/dist/chunk-5EMIZZXD.mjs.map +1 -0
  27. package/packages/core/dist/chunk-7TGZLWEE.mjs +35 -0
  28. package/packages/core/dist/chunk-7TGZLWEE.mjs.map +1 -0
  29. package/packages/core/dist/chunk-BT7CATSD.mjs +497 -0
  30. package/packages/core/dist/chunk-BT7CATSD.mjs.map +1 -0
  31. package/packages/core/dist/chunk-E5F2LQCX.mjs +83 -0
  32. package/packages/core/dist/chunk-E5F2LQCX.mjs.map +1 -0
  33. package/packages/core/dist/chunk-EDTQHZPM.mjs +88 -0
  34. package/packages/core/dist/chunk-EDTQHZPM.mjs.map +1 -0
  35. package/packages/core/dist/chunk-HY7H3CQM.mjs +127 -0
  36. package/packages/core/dist/chunk-HY7H3CQM.mjs.map +1 -0
  37. package/packages/core/dist/chunk-Q6EPNB3V.mjs +539 -0
  38. package/packages/core/dist/chunk-Q6EPNB3V.mjs.map +1 -0
  39. package/packages/core/dist/chunk-QPAU2TPA.mjs +785 -0
  40. package/packages/core/dist/chunk-QPAU2TPA.mjs.map +1 -0
  41. package/packages/core/dist/chunk-QX3ZMPUF.mjs +451 -0
  42. package/packages/core/dist/chunk-QX3ZMPUF.mjs.map +1 -0
  43. package/packages/core/dist/chunk-VU6O5QAH.mjs +99 -0
  44. package/packages/core/dist/chunk-VU6O5QAH.mjs.map +1 -0
  45. package/packages/core/dist/chunk-XDBND27A.mjs +975 -0
  46. package/packages/core/dist/chunk-XDBND27A.mjs.map +1 -0
  47. package/packages/core/dist/chunk-YWAOOXLR.mjs +475 -0
  48. package/packages/core/dist/chunk-YWAOOXLR.mjs.map +1 -0
  49. package/packages/core/dist/diff.cjs +993 -0
  50. package/packages/core/dist/diff.cjs.map +1 -0
  51. package/packages/core/dist/diff.d.cts +81 -0
  52. package/packages/core/dist/diff.d.ts +81 -0
  53. package/packages/core/dist/diff.mjs +5 -0
  54. package/packages/core/dist/diff.mjs.map +1 -0
  55. package/packages/core/dist/exporters.cjs +1228 -0
  56. package/packages/core/dist/exporters.cjs.map +1 -0
  57. package/packages/core/dist/exporters.d.cts +113 -0
  58. package/packages/core/dist/exporters.d.ts +113 -0
  59. package/packages/core/dist/exporters.mjs +6 -0
  60. package/packages/core/dist/exporters.mjs.map +1 -0
  61. package/packages/core/dist/index.cjs +2982 -1829
  62. package/packages/core/dist/index.cjs.map +1 -1
  63. package/packages/core/dist/index.d.cts +201 -892
  64. package/packages/core/dist/index.d.ts +201 -892
  65. package/packages/core/dist/index.mjs +780 -4620
  66. package/packages/core/dist/index.mjs.map +1 -1
  67. package/packages/core/dist/log-config-BzGmDYum.d.cts +71 -0
  68. package/packages/core/dist/log-config-BzGmDYum.d.ts +71 -0
  69. package/packages/core/dist/logs.cjs +1007 -0
  70. package/packages/core/dist/logs.cjs.map +1 -0
  71. package/packages/core/dist/logs.d.cts +137 -0
  72. package/packages/core/dist/logs.d.ts +137 -0
  73. package/packages/core/dist/logs.mjs +6 -0
  74. package/packages/core/dist/logs.mjs.map +1 -0
  75. package/packages/core/dist/persisted.cjs +1057 -0
  76. package/packages/core/dist/persisted.cjs.map +1 -0
  77. package/packages/core/dist/persisted.d.cts +160 -0
  78. package/packages/core/dist/persisted.d.ts +160 -0
  79. package/packages/core/dist/persisted.mjs +5 -0
  80. package/packages/core/dist/persisted.mjs.map +1 -0
  81. package/packages/core/dist/types-Bkt7LS01.d.ts +226 -0
  82. package/packages/core/dist/types-CNbheSdk.d.cts +226 -0
@@ -0,0 +1,1007 @@
1
+ 'use strict';
2
+
3
+ var promises = require('fs/promises');
4
+ var crypto = require('crypto');
5
+
6
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
7
+
8
+ var crypto__default = /*#__PURE__*/_interopDefault(crypto);
9
+
10
+ // packages/core/src/logs/config.ts
11
+ function isRecord(v) {
12
+ return typeof v === "object" && v !== null && !Array.isArray(v);
13
+ }
14
+ function isNonEmptyStringArray(v) {
15
+ return Array.isArray(v) && v.length > 0 && v.every((x) => typeof x === "string" && x.trim() !== "");
16
+ }
17
+ function validateRedact(redact) {
18
+ if (!Array.isArray(redact)) {
19
+ throw new Error("Invalid config: redact must be an array");
20
+ }
21
+ for (const r of redact) {
22
+ if (typeof r === "string") continue;
23
+ if (!isRecord(r)) {
24
+ throw new Error("Invalid config: redact entries must be strings or objects");
25
+ }
26
+ if (typeof r.key !== "string" || r.key.trim() === "") {
27
+ throw new Error("Invalid config: redact.key must be a non-empty string");
28
+ }
29
+ if (r.strategy !== "full" && r.strategy !== "prefix" && r.strategy !== "hash") {
30
+ throw new Error(
31
+ `Invalid config: redact.strategy must be one of full, prefix, hash (got ${String(
32
+ r.strategy
33
+ )})`
34
+ );
35
+ }
36
+ if (r.keep !== void 0 && (typeof r.keep !== "number" || !Number.isFinite(r.keep) || r.keep < 0)) {
37
+ throw new Error("Invalid config: redact.keep must be a non-negative number when provided");
38
+ }
39
+ }
40
+ }
41
+ function validateMappings(mappings) {
42
+ if (!isRecord(mappings)) {
43
+ throw new Error("Invalid config: mappings must be an object");
44
+ }
45
+ }
46
+ var DEFAULT_LOG_INGEST_CONFIG = {
47
+ runIdKeys: ["runId", "traceId", "requestId", "decisionId", "jobId"],
48
+ eventKey: "event",
49
+ timestampKey: "timestamp",
50
+ messageKey: "message",
51
+ levelKey: "level",
52
+ heuristicWindowMs: 2e3,
53
+ mappings: {
54
+ "*.error": { kind: "ERROR", status: "error" },
55
+ "*.failed": { kind: "ERROR", status: "error" },
56
+ "*.llm.*": { kind: "LLM" },
57
+ "*.tool.*": { kind: "TOOL" },
58
+ "*.agent.*": { kind: "AGENT" },
59
+ "*.retriever.*": { kind: "RETRIEVER" },
60
+ "*.result.*": { kind: "RESULT" }
61
+ }
62
+ };
63
+ function mergeLogIngestConfig(base, override) {
64
+ const merged = {
65
+ ...base,
66
+ ...override,
67
+ mappings: {
68
+ ...base.mappings ?? {},
69
+ ...override.mappings ?? {}
70
+ }
71
+ };
72
+ return merged;
73
+ }
74
+ async function loadLogIngestConfig(configPath) {
75
+ if (configPath === void 0 || configPath.trim() === "") {
76
+ return DEFAULT_LOG_INGEST_CONFIG;
77
+ }
78
+ let rawText;
79
+ try {
80
+ rawText = await promises.readFile(configPath, "utf-8");
81
+ } catch (e) {
82
+ const msg = e instanceof Error ? e.message : String(e);
83
+ throw new Error(`Failed to read config file: ${configPath} (${msg})`);
84
+ }
85
+ let parsed;
86
+ try {
87
+ parsed = JSON.parse(rawText);
88
+ } catch (e) {
89
+ const msg = e instanceof Error ? e.message : String(e);
90
+ throw new Error(`Invalid JSON in config file: ${configPath} (${msg})`);
91
+ }
92
+ if (!isRecord(parsed)) {
93
+ throw new Error("Invalid config: expected a JSON object at top-level");
94
+ }
95
+ const user = parsed;
96
+ if (user.runIdKeys !== void 0 && !isNonEmptyStringArray(user.runIdKeys)) {
97
+ throw new Error("Invalid config: runIdKeys must be a non-empty array of strings");
98
+ }
99
+ if (user.eventKey !== void 0 && (typeof user.eventKey !== "string" || user.eventKey.trim() === "")) {
100
+ throw new Error("Invalid config: eventKey must be a non-empty string");
101
+ }
102
+ for (const k of [
103
+ "timestampKey",
104
+ "messageKey",
105
+ "levelKey",
106
+ "parentIdKey",
107
+ "durationKey",
108
+ "statusKey"
109
+ ]) {
110
+ const v = user[k];
111
+ if (v !== void 0 && (typeof v !== "string" || v.trim() === "")) {
112
+ throw new Error(`Invalid config: ${k} must be a non-empty string when provided`);
113
+ }
114
+ }
115
+ if (user.mappings !== void 0) {
116
+ validateMappings(user.mappings);
117
+ }
118
+ if (user.redact !== void 0) {
119
+ validateRedact(user.redact);
120
+ }
121
+ if (user.heuristicWindowMs !== void 0 && (typeof user.heuristicWindowMs !== "number" || !Number.isFinite(user.heuristicWindowMs) || user.heuristicWindowMs < 0)) {
122
+ throw new Error("Invalid config: heuristicWindowMs must be a non-negative number when provided");
123
+ }
124
+ if (user.redact && JSON.stringify(user.redact).includes("=>")) {
125
+ throw new Error("Invalid config: function strings are not supported in redact rules");
126
+ }
127
+ return mergeLogIngestConfig(DEFAULT_LOG_INGEST_CONFIG, user);
128
+ }
129
+ function isRecord2(v) {
130
+ return typeof v === "object" && v !== null && !Array.isArray(v);
131
+ }
132
+ var JsonLogParser = class {
133
+ parseLines(lines, filePath) {
134
+ const records = [];
135
+ const warnings = [];
136
+ for (let i = 0; i < lines.length; i++) {
137
+ const lineNumber = i + 1;
138
+ const raw = lines[i] ?? "";
139
+ const trimmed = raw.trim();
140
+ if (trimmed === "") continue;
141
+ let parsed;
142
+ try {
143
+ parsed = JSON.parse(trimmed);
144
+ } catch {
145
+ warnings.push({
146
+ code: "MALFORMED_JSON",
147
+ message: "Malformed JSON log line",
148
+ file: filePath,
149
+ line: lineNumber,
150
+ raw: trimmed.slice(0, 500)
151
+ });
152
+ continue;
153
+ }
154
+ if (!isRecord2(parsed)) {
155
+ warnings.push({
156
+ code: "MALFORMED_JSON",
157
+ message: "JSON log line must be an object",
158
+ file: filePath,
159
+ line: lineNumber,
160
+ raw: trimmed.slice(0, 500)
161
+ });
162
+ continue;
163
+ }
164
+ records.push({
165
+ raw: parsed,
166
+ file: filePath,
167
+ line: lineNumber,
168
+ sourceType: "json-log"
169
+ });
170
+ }
171
+ return { records, warnings };
172
+ }
173
+ async parseFile(filePath) {
174
+ let text;
175
+ try {
176
+ text = await promises.readFile(filePath, "utf-8");
177
+ } catch (e) {
178
+ const msg = e instanceof Error ? e.message : String(e);
179
+ throw new Error(`Failed to read log file: ${filePath} (${msg})`);
180
+ }
181
+ const lines = text.split(/\r?\n/);
182
+ return this.parseLines(lines, filePath);
183
+ }
184
+ };
185
+ function isRecord3(v) {
186
+ return typeof v === "object" && v !== null && !Array.isArray(v);
187
+ }
188
+ function findLastJsonObjectSubstring(line) {
189
+ let last;
190
+ for (let i = 0; i < line.length; i++) {
191
+ if (line[i] !== "{") continue;
192
+ let depth = 0;
193
+ let inString = false;
194
+ let escape = false;
195
+ for (let j = i; j < line.length; j++) {
196
+ const ch = line[j];
197
+ if (inString) {
198
+ if (escape) {
199
+ escape = false;
200
+ continue;
201
+ }
202
+ if (ch === "\\") {
203
+ escape = true;
204
+ continue;
205
+ }
206
+ if (ch === '"') {
207
+ inString = false;
208
+ }
209
+ continue;
210
+ }
211
+ if (ch === '"') {
212
+ inString = true;
213
+ continue;
214
+ }
215
+ if (ch === "{") depth += 1;
216
+ if (ch === "}") depth -= 1;
217
+ if (depth === 0) {
218
+ last = { start: i, end: j + 1 };
219
+ i = j;
220
+ break;
221
+ }
222
+ if (depth < 0) break;
223
+ }
224
+ }
225
+ if (!last) return void 0;
226
+ return line.slice(last.start, last.end);
227
+ }
228
+ var Log4jsParser = class {
229
+ parseLines(lines, filePath) {
230
+ const records = [];
231
+ const warnings = [];
232
+ for (let i = 0; i < lines.length; i++) {
233
+ const lineNumber = i + 1;
234
+ const rawLine = lines[i] ?? "";
235
+ const trimmed = rawLine.trim();
236
+ if (trimmed === "") continue;
237
+ const jsonText = findLastJsonObjectSubstring(trimmed);
238
+ if (!jsonText) {
239
+ warnings.push({
240
+ code: "UNSUPPORTED_LOG4JS_PAYLOAD",
241
+ message: "No embedded JSON object found in log4js line",
242
+ file: filePath,
243
+ line: lineNumber,
244
+ raw: trimmed.slice(0, 500)
245
+ });
246
+ continue;
247
+ }
248
+ let parsed;
249
+ try {
250
+ parsed = JSON.parse(jsonText);
251
+ } catch {
252
+ warnings.push({
253
+ code: "MALFORMED_JSON",
254
+ message: "Malformed embedded JSON object in log4js line",
255
+ file: filePath,
256
+ line: lineNumber,
257
+ raw: jsonText.slice(0, 500)
258
+ });
259
+ continue;
260
+ }
261
+ if (!isRecord3(parsed)) {
262
+ warnings.push({
263
+ code: "UNSUPPORTED_LOG4JS_PAYLOAD",
264
+ message: "Embedded JSON payload must be an object",
265
+ file: filePath,
266
+ line: lineNumber,
267
+ raw: jsonText.slice(0, 500)
268
+ });
269
+ continue;
270
+ }
271
+ records.push({
272
+ raw: parsed,
273
+ file: filePath,
274
+ line: lineNumber,
275
+ sourceType: "log4js"
276
+ });
277
+ }
278
+ return { records, warnings };
279
+ }
280
+ async parseFile(filePath) {
281
+ let text;
282
+ try {
283
+ text = await promises.readFile(filePath, "utf-8");
284
+ } catch (e) {
285
+ const msg = e instanceof Error ? e.message : String(e);
286
+ throw new Error(`Failed to read log file: ${filePath} (${msg})`);
287
+ }
288
+ const lines = text.split(/\r?\n/);
289
+ return this.parseLines(lines, filePath);
290
+ }
291
+ };
292
+
293
+ // packages/core/src/logs/mapping.ts
294
+ function wildcardMatch(pattern, value) {
295
+ if (pattern === value) return true;
296
+ if (!pattern.includes("*")) return false;
297
+ const parts = pattern.split("*");
298
+ let idx = 0;
299
+ for (let i = 0; i < parts.length; i++) {
300
+ const part = parts[i];
301
+ if (part === "") continue;
302
+ const found = value.indexOf(part, idx);
303
+ if (found === -1) return false;
304
+ if (i === 0 && !pattern.startsWith("*") && found !== 0) return false;
305
+ idx = found + part.length;
306
+ }
307
+ if (!pattern.endsWith("*")) {
308
+ const last = parts[parts.length - 1];
309
+ if (last !== "" && !value.endsWith(last)) return false;
310
+ if (last === "" && !value.endsWith(parts[parts.length - 2] ?? "")) return false;
311
+ }
312
+ return true;
313
+ }
314
+ function matchMapping(eventName, mappings) {
315
+ if (!mappings) return void 0;
316
+ if (mappings[eventName]) return mappings[eventName];
317
+ let bestKey;
318
+ let bestScore = -1;
319
+ for (const key of Object.keys(mappings)) {
320
+ if (!key.includes("*")) continue;
321
+ if (!wildcardMatch(key, eventName)) continue;
322
+ const score = key.replaceAll("*", "").length;
323
+ if (score > bestScore) {
324
+ bestScore = score;
325
+ bestKey = key;
326
+ }
327
+ }
328
+ return bestKey ? mappings[bestKey] : void 0;
329
+ }
330
+ var DEFAULT_REDACT_KEYS = [
331
+ "authorization",
332
+ "cookie",
333
+ "token",
334
+ "apiKey",
335
+ "password",
336
+ "secret",
337
+ "email"
338
+ ];
339
+ function isRecord4(v) {
340
+ return typeof v === "object" && v !== null && !Array.isArray(v);
341
+ }
342
+ function toKey(s) {
343
+ return s.toLowerCase();
344
+ }
345
+ function stableHash(value) {
346
+ const h = crypto__default.default.createHash("sha256").update(value, "utf8").digest("hex");
347
+ return h.slice(0, 8);
348
+ }
349
+ function compileRules(rules, extraKeys) {
350
+ const out = /* @__PURE__ */ new Map();
351
+ const set = (r) => {
352
+ const k = toKey(r.key);
353
+ out.set(k, { ...r, key: k });
354
+ };
355
+ for (const k of DEFAULT_REDACT_KEYS) {
356
+ set({ key: k, strategy: "full" });
357
+ }
358
+ for (const k of extraKeys ?? []) {
359
+ if (typeof k === "string" && k.length > 0) {
360
+ set({ key: k, strategy: "full" });
361
+ }
362
+ }
363
+ for (const r of rules ?? []) {
364
+ if (typeof r === "string") {
365
+ set({ key: r, strategy: "full" });
366
+ continue;
367
+ }
368
+ const key = r.key;
369
+ if (r.strategy === "full") set({ key, strategy: "full" });
370
+ if (r.strategy === "hash") set({ key, strategy: "hash" });
371
+ if (r.strategy === "prefix") {
372
+ set({ key, strategy: "prefix", keep: typeof r.keep === "number" ? r.keep : 8 });
373
+ }
374
+ }
375
+ return [...out.values()];
376
+ }
377
+ var Redactor = class {
378
+ #rules;
379
+ constructor(options) {
380
+ this.#rules = compileRules(options?.rules, options?.extraKeys);
381
+ }
382
+ redactValue(key, value) {
383
+ const k = toKey(key);
384
+ const rule = this.#rules.find((r) => r.key === k);
385
+ if (!rule) {
386
+ return this.#redactNested(value);
387
+ }
388
+ if (rule.strategy === "full") return "[REDACTED]";
389
+ const asString = typeof value === "string" ? value : typeof value === "number" || typeof value === "boolean" || typeof value === "bigint" ? String(value) : void 0;
390
+ if (rule.strategy === "prefix") {
391
+ if (asString === void 0) return "[REDACTED]";
392
+ const keep = Math.max(0, Math.floor(rule.keep));
393
+ return asString.length <= keep ? `${asString}\u2026` : `${asString.slice(0, keep)}\u2026`;
394
+ }
395
+ if (rule.strategy === "hash") {
396
+ if (asString === void 0) return "[HASH:unknown]";
397
+ return `[HASH:${stableHash(asString)}]`;
398
+ }
399
+ return this.#redactNested(value);
400
+ }
401
+ redactRecord(record) {
402
+ const out = {};
403
+ for (const [k, v] of Object.entries(record)) {
404
+ out[k] = this.redactValue(k, v);
405
+ }
406
+ return out;
407
+ }
408
+ #redactNested(value) {
409
+ if (Array.isArray(value)) {
410
+ return value.map((v) => this.#redactNested(v));
411
+ }
412
+ if (isRecord4(value)) {
413
+ const out = {};
414
+ for (const [k, v] of Object.entries(value)) {
415
+ out[k] = this.redactValue(k, v);
416
+ }
417
+ return out;
418
+ }
419
+ return value;
420
+ }
421
+ };
422
+
423
+ // node_modules/.pnpm/nanoid@5.1.11/node_modules/nanoid/url-alphabet/index.js
424
+ var urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
425
+
426
+ // node_modules/.pnpm/nanoid@5.1.11/node_modules/nanoid/index.js
427
+ var POOL_SIZE_MULTIPLIER = 128;
428
+ var pool;
429
+ var poolOffset;
430
+ function fillPool(bytes) {
431
+ if (bytes < 0 || bytes > 1024) throw new RangeError("Wrong ID size");
432
+ if (!pool || pool.length < bytes) {
433
+ pool = Buffer.allocUnsafe(bytes * POOL_SIZE_MULTIPLIER);
434
+ crypto.webcrypto.getRandomValues(pool);
435
+ poolOffset = 0;
436
+ } else if (poolOffset + bytes > pool.length) {
437
+ crypto.webcrypto.getRandomValues(pool);
438
+ poolOffset = 0;
439
+ }
440
+ poolOffset += bytes;
441
+ }
442
+ function nanoid(size = 21) {
443
+ fillPool(size |= 0);
444
+ let id = "";
445
+ for (let i = poolOffset - size; i < poolOffset; i++) {
446
+ id += urlAlphabet[pool[i] & 63];
447
+ }
448
+ return id;
449
+ }
450
+
451
+ // packages/core/src/logs/normalizer.ts
452
+ function isFiniteNumber(v) {
453
+ return typeof v === "number" && Number.isFinite(v);
454
+ }
455
+ function safeString(v) {
456
+ if (typeof v !== "string") return void 0;
457
+ const t = v.trim();
458
+ return t === "" ? void 0 : t;
459
+ }
460
+ function parseTimestamp(v) {
461
+ if (isFiniteNumber(v)) return v;
462
+ if (typeof v === "string") {
463
+ const t = Date.parse(v);
464
+ if (Number.isFinite(t)) return t;
465
+ }
466
+ return void 0;
467
+ }
468
+ function hasToken(hay, token) {
469
+ return hay.includes(token);
470
+ }
471
+ function inferKind(eventName) {
472
+ if (hasToken(eventName, ".llm.")) return "LLM";
473
+ if (hasToken(eventName, ".tool.")) return "TOOL";
474
+ if (hasToken(eventName, ".agent.")) return "AGENT";
475
+ if (hasToken(eventName, ".retriever.")) return "RETRIEVER";
476
+ if (hasToken(eventName, ".result.")) return "RESULT";
477
+ if (eventName.endsWith(".error") || eventName.endsWith(".failed") || eventName.includes(".error") || eventName.includes(".failed")) {
478
+ return "ERROR";
479
+ }
480
+ return "LOG";
481
+ }
482
+ function deriveName(eventName, kind) {
483
+ const parts = eventName.split(".");
484
+ const last = parts[parts.length - 1] ?? eventName;
485
+ if (kind === "LLM") return `llm:${last}`;
486
+ if (kind === "TOOL") return `tool:${last}`;
487
+ if (kind === "AGENT") return `agent:${last}`;
488
+ if (kind === "RESULT") return `result:${last}`;
489
+ if (kind === "RUN") return `run:${last}`;
490
+ if (kind === "RETRIEVER") return `retriever:${last}`;
491
+ if (kind === "ERROR") return `error:${last}`;
492
+ return eventName;
493
+ }
494
+ var EventNormalizer = class {
495
+ #config;
496
+ constructor(options) {
497
+ this.#config = options.config;
498
+ }
499
+ #normalizeInternal(record) {
500
+ const raw = record.raw;
501
+ const cfg = this.#config;
502
+ let runId;
503
+ for (const k of cfg.runIdKeys) {
504
+ const v = raw[k];
505
+ const s = safeString(v);
506
+ if (s) {
507
+ runId = s;
508
+ break;
509
+ }
510
+ }
511
+ if (!runId) {
512
+ return {
513
+ warning: {
514
+ code: "MISSING_RUN_ID",
515
+ message: "Missing run id (none of runIdKeys present)",
516
+ file: record.file,
517
+ line: record.line
518
+ }
519
+ };
520
+ }
521
+ const eventName = safeString(raw[cfg.eventKey]);
522
+ if (!eventName) {
523
+ return {
524
+ warning: {
525
+ code: "MISSING_EVENT",
526
+ message: `Missing event name (key: ${cfg.eventKey})`,
527
+ file: record.file,
528
+ line: record.line
529
+ }
530
+ };
531
+ }
532
+ const mapping = matchMapping(eventName, cfg.mappings);
533
+ const tsKey = cfg.timestampKey ?? "timestamp";
534
+ const tsRaw = raw[tsKey];
535
+ const parsedTs = parseTimestamp(tsRaw);
536
+ const timestamp = parsedTs ?? Date.now();
537
+ const timestampMissing = parsedTs === void 0;
538
+ const parentIdKey = cfg.parentIdKey;
539
+ const parentId = parentIdKey ? safeString(raw[parentIdKey]) : void 0;
540
+ let durationMs;
541
+ const durationKey = cfg.durationKey;
542
+ if (durationKey) {
543
+ const v = raw[durationKey];
544
+ if (isFiniteNumber(v)) durationMs = v;
545
+ } else if (isFiniteNumber(raw.durationMs)) {
546
+ durationMs = raw.durationMs;
547
+ }
548
+ let status;
549
+ const statusKey = cfg.statusKey;
550
+ const statusRaw = statusKey ? safeString(raw[statusKey]) : void 0;
551
+ if (statusRaw === "running" || statusRaw === "ok" || statusRaw === "error") {
552
+ status = statusRaw;
553
+ } else if (mapping?.status) {
554
+ status = mapping.status;
555
+ } else if (mapping?.kind === "ERROR") {
556
+ status = "error";
557
+ } else if (eventName.includes(".failed") || eventName.includes(".error")) {
558
+ status = "error";
559
+ } else if (mapping?.startsRun || mapping?.startsStep) {
560
+ status = "running";
561
+ } else if (mapping?.endsRun || mapping?.endsStep) {
562
+ status = "ok";
563
+ }
564
+ const kind = mapping?.kind ?? inferKind(eventName);
565
+ const name = mapping?.name ?? deriveName(eventName, kind);
566
+ let confidence = "correlated";
567
+ if (parentId || mapping?.startsRun) confidence = "explicit";
568
+ if (timestampMissing) confidence = "unknown";
569
+ const omit = new Set([
570
+ ...cfg.runIdKeys,
571
+ cfg.eventKey,
572
+ tsKey,
573
+ cfg.messageKey ?? "message",
574
+ cfg.levelKey ?? "level",
575
+ cfg.parentIdKey ?? "",
576
+ cfg.durationKey ?? "",
577
+ cfg.statusKey ?? "",
578
+ "durationMs"
579
+ ].filter((k) => k !== ""));
580
+ const attributes = {};
581
+ for (const [k, v] of Object.entries(raw)) {
582
+ if (omit.has(k)) continue;
583
+ attributes[k] = v;
584
+ }
585
+ const event = {
586
+ eventId: nanoid(10),
587
+ runId,
588
+ ...parentId ? { parentId } : {},
589
+ name,
590
+ kind,
591
+ timestamp,
592
+ ...status ? { status } : {},
593
+ ...durationMs !== void 0 ? { durationMs } : {},
594
+ ...Object.keys(attributes).length > 0 ? { attributes } : {},
595
+ confidence,
596
+ source: {
597
+ type: record.sourceType,
598
+ file: record.file,
599
+ line: record.line
600
+ }
601
+ };
602
+ if (timestampMissing) {
603
+ return {
604
+ event,
605
+ warning: {
606
+ code: "MISSING_TIMESTAMP",
607
+ message: `Missing or invalid timestamp (key: ${tsKey})`,
608
+ file: record.file,
609
+ line: record.line,
610
+ raw: JSON.stringify(raw).slice(0, 500)
611
+ }
612
+ };
613
+ }
614
+ return { event };
615
+ }
616
+ normalize(record) {
617
+ const r = this.#normalizeInternal(record);
618
+ return r.event ?? r.warning;
619
+ }
620
+ normalizeAll(records) {
621
+ const out = [];
622
+ const warnings = [];
623
+ for (const r of records) {
624
+ const normalized = this.#normalizeInternal(r);
625
+ if (normalized.event) out.push(normalized.event);
626
+ if (normalized.warning) warnings.push(normalized.warning);
627
+ }
628
+ return { records: out, warnings };
629
+ }
630
+ };
631
+
632
+ // packages/core/src/logs/tree-builder.ts
633
+ function inc(map, key) {
634
+ map[key] = (map[key] ?? 0) + 1;
635
+ }
636
+ function computeRunStatus(events) {
637
+ let hasRunning = false;
638
+ for (const e of events) {
639
+ if (e.status === "error") return "error";
640
+ if (e.status === "running") hasRunning = true;
641
+ }
642
+ if (hasRunning) return "running";
643
+ return "ok";
644
+ }
645
+ var TreeBuilder = class {
646
+ constructor(options) {
647
+ void options?.config;
648
+ }
649
+ build(events) {
650
+ const byRun = /* @__PURE__ */ new Map();
651
+ for (const e of events) {
652
+ if (!byRun.has(e.runId)) byRun.set(e.runId, []);
653
+ byRun.get(e.runId).push(e);
654
+ }
655
+ const out = [];
656
+ for (const [runId, runEvents] of byRun.entries()) {
657
+ const sorted = [...runEvents].sort((a, b) => a.timestamp - b.timestamp);
658
+ const nodes = /* @__PURE__ */ new Map();
659
+ for (const e of sorted) {
660
+ nodes.set(e.eventId, { event: e, children: [], depth: 0 });
661
+ }
662
+ const roots = [];
663
+ for (const node of nodes.values()) {
664
+ const parentId = node.event.parentId;
665
+ if (parentId && nodes.has(parentId)) {
666
+ nodes.get(parentId).children.push(node);
667
+ } else {
668
+ roots.push(node);
669
+ }
670
+ }
671
+ const assignDepth = (n, depth) => {
672
+ n.depth = depth;
673
+ for (const c of n.children) assignDepth(c, depth + 1);
674
+ };
675
+ for (const r of roots) assignDepth(r, 0);
676
+ const confidenceBreakdown = {
677
+ explicit: 0,
678
+ correlated: 0,
679
+ heuristic: 0,
680
+ unknown: 0
681
+ };
682
+ const kinds = {};
683
+ for (const e of sorted) {
684
+ inc(confidenceBreakdown, e.confidence);
685
+ kinds[e.kind] = (kinds[e.kind] ?? 0) + 1;
686
+ }
687
+ const startedAt = sorted.length > 0 ? sorted[0].timestamp : void 0;
688
+ const endedAt = sorted.length > 0 ? sorted[sorted.length - 1].timestamp : void 0;
689
+ const status = computeRunStatus(sorted);
690
+ const durationMs = startedAt !== void 0 && endedAt !== void 0 && Number.isFinite(startedAt) && Number.isFinite(endedAt) && endedAt >= startedAt && status !== "running" ? endedAt - startedAt : void 0;
691
+ const name = sorted.find((e) => e.kind === "RUN")?.name;
692
+ out.push({
693
+ runId,
694
+ name,
695
+ status,
696
+ startedAt,
697
+ endedAt: status === "running" ? void 0 : endedAt,
698
+ durationMs,
699
+ children: roots,
700
+ metadata: {
701
+ totalEvents: sorted.length,
702
+ confidenceBreakdown,
703
+ kinds
704
+ }
705
+ });
706
+ }
707
+ out.sort((a, b) => (b.startedAt ?? 0) - (a.startedAt ?? 0));
708
+ return out;
709
+ }
710
+ };
711
+
712
+ // packages/core/src/logs/tree-renderer.ts
713
+ function truncate(v, max) {
714
+ if (v.length <= max) return v;
715
+ return v.slice(0, Math.max(0, max - 1)) + "\u2026";
716
+ }
717
+ function fmtAttrValue(value, maxLen) {
718
+ if (value === null || value === void 0) return void 0;
719
+ if (typeof value === "string") return truncate(value, maxLen);
720
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
721
+ return String(value);
722
+ }
723
+ if (typeof value === "object") {
724
+ try {
725
+ return truncate(JSON.stringify(value), maxLen);
726
+ } catch {
727
+ return "[object]";
728
+ }
729
+ }
730
+ return String(value);
731
+ }
732
+ function compactAttrs(attrs, maxLen) {
733
+ if (!attrs) return "";
734
+ const entries = Object.entries(attrs);
735
+ if (entries.length === 0) return "";
736
+ const picks = /* @__PURE__ */ new Map();
737
+ const set = (k, v) => {
738
+ const s = fmtAttrValue(v, maxLen);
739
+ if (s !== void 0) picks.set(k, s);
740
+ };
741
+ set("job", attrs.jobId ?? attrs.job ?? attrs.jobUuid);
742
+ set("user", attrs.userUuid ?? attrs.userId ?? attrs.user);
743
+ set("trip", attrs.tripUuid ?? attrs.tripId ?? attrs.trip);
744
+ set("msgs", attrs.messageCount ?? attrs.msgs);
745
+ set("trips", attrs.trips);
746
+ set("model", attrs.model);
747
+ const tokens = attrs.tokens;
748
+ if (tokens && typeof tokens === "object" && tokens !== null) {
749
+ const input = tokens.input;
750
+ const output = tokens.output;
751
+ if (typeof input === "number" || typeof output === "number") {
752
+ picks.set("tokens", `${input ?? "?"}/${output ?? "?"}`);
753
+ }
754
+ }
755
+ for (const k of ["shouldNotify", "variant"]) {
756
+ if (k in attrs) set(k, attrs[k]);
757
+ }
758
+ const rendered = [...picks.entries()].filter(([, v]) => v !== "").map(([k, v]) => `${k}=${v}`);
759
+ return rendered.length > 0 ? " " + rendered.join(" ") : "";
760
+ }
761
+ function statusMark(node) {
762
+ if (node.event.status === "error") return " \u2716";
763
+ if (node.event.status === "ok") return " \u2714";
764
+ return "";
765
+ }
766
+ function fmtDuration(ms) {
767
+ if (!Number.isFinite(ms) || ms < 0) return "";
768
+ if (ms < 1e3) return `${Math.round(ms)}ms`;
769
+ return `${(ms / 1e3).toFixed(2)}s`;
770
+ }
771
+ function renderNodeLines(node, prefix, isLast, options) {
772
+ const branch = prefix + (isLast ? "\u2514\u2500 " : "\u251C\u2500 ");
773
+ const nextPrefix = prefix + (isLast ? " " : "\u2502 ");
774
+ const attrs = compactAttrs(node.event.attributes, options.maxAttributeLength);
775
+ const dur = node.event.durationMs !== void 0 ? ` ${fmtDuration(node.event.durationMs)}` : "";
776
+ const line = `${branch}${node.event.name}${attrs}${statusMark(node)}${dur}`;
777
+ const lines = [line];
778
+ const showConf = options.showConfidence === "always" || options.showConfidence === "non-explicit" && node.event.confidence !== "explicit";
779
+ if (showConf) {
780
+ lines.push(`${nextPrefix}confidence: ${node.event.confidence}`);
781
+ }
782
+ const children = node.children;
783
+ for (let i = 0; i < children.length; i++) {
784
+ lines.push(...renderNodeLines(children[i], nextPrefix, i === children.length - 1, options));
785
+ }
786
+ return lines;
787
+ }
788
+ function renderRunTree(tree, options) {
789
+ const opts = {
790
+ verbose: options?.verbose ?? false,
791
+ showConfidence: options?.showConfidence ?? "always",
792
+ showMetadata: options?.showMetadata ?? false,
793
+ color: options?.color ?? false,
794
+ maxAttributeLength: options?.maxAttributeLength ?? 40,
795
+ summary: options?.summary ?? true
796
+ };
797
+ const header = `Run ${tree.runId}`;
798
+ const lines = [header];
799
+ const children = tree.children;
800
+ for (let i = 0; i < children.length; i++) {
801
+ lines.push(...renderNodeLines(children[i], "", i === children.length - 1, opts));
802
+ }
803
+ if (opts.summary) {
804
+ const cb = tree.metadata.confidenceBreakdown;
805
+ const tools = tree.metadata.kinds.TOOL ?? 0;
806
+ const llms = tree.metadata.kinds.LLM ?? 0;
807
+ lines.push("");
808
+ lines.push("Summary:");
809
+ lines.push(` Events: ${tree.metadata.totalEvents}`);
810
+ lines.push(` Tools: ${tools}`);
811
+ lines.push(` LLMs: ${llms}`);
812
+ lines.push(
813
+ ` Confidence: ${cb.explicit} explicit, ${cb.correlated} correlated, ${cb.heuristic} heuristic, ${cb.unknown} unknown`
814
+ );
815
+ lines.push("");
816
+ lines.push("Note:");
817
+ lines.push(" Flat timeline by default. Nesting only with explicit parentId.");
818
+ }
819
+ return lines.join("\n");
820
+ }
821
+ function renderRunTrees(trees, options) {
822
+ return trees.map((t) => renderRunTree(t, options)).join("\n\n");
823
+ }
824
+
825
+ // packages/core/src/logs/line-parser.ts
826
+ function shiftLineNumbers(res, options) {
827
+ const targetLine = typeof options.line === "number" && Number.isFinite(options.line) && options.line > 0 ? Math.floor(options.line) : void 0;
828
+ const file = typeof options.file === "string" && options.file.trim() !== "" ? options.file : void 0;
829
+ if (targetLine === void 0 && file === void 0) return res;
830
+ const mapRecord = (x) => ({
831
+ ...x,
832
+ ...targetLine !== void 0 ? { line: targetLine } : {},
833
+ ...file !== void 0 ? { file } : {}
834
+ });
835
+ const mapWarning = (x) => ({
836
+ ...x,
837
+ ...targetLine !== void 0 ? { line: targetLine } : {},
838
+ ...file !== void 0 ? { file } : {}
839
+ });
840
+ return {
841
+ records: res.records.map(mapRecord),
842
+ warnings: res.warnings.map(mapWarning)
843
+ };
844
+ }
845
+ function normalizeFormat(line, format) {
846
+ if (format && format !== "auto") return format;
847
+ const trimmed = line.trim();
848
+ if (trimmed.startsWith("{")) return "json";
849
+ return "log4js";
850
+ }
851
+ function parseLogLine(line, options = {}) {
852
+ const raw = typeof line === "string" ? line : "";
853
+ const trimmed = raw.trim();
854
+ if (trimmed === "") return { records: [], warnings: [] };
855
+ const format = normalizeFormat(raw, options.format);
856
+ const base = format === "json" ? new JsonLogParser().parseLines([raw], options.file) : new Log4jsParser().parseLines([raw], options.file);
857
+ return shiftLineNumbers(base, options);
858
+ }
859
+
860
+ // packages/core/src/logs/live-tree.ts
861
+ var LiveLogAccumulator = class {
862
+ #config;
863
+ #format;
864
+ #file;
865
+ #normalizer;
866
+ #redactor;
867
+ #treeBuilder;
868
+ #events = [];
869
+ #warnings = [];
870
+ #trees = [];
871
+ constructor(options) {
872
+ this.#config = mergeLogIngestConfig(options.config, {});
873
+ this.#format = options.format ?? "auto";
874
+ this.#file = options.file;
875
+ this.#normalizer = new EventNormalizer({ config: this.#config });
876
+ this.#redactor = new Redactor({ rules: this.#config.redact });
877
+ this.#treeBuilder = new TreeBuilder({ config: this.#config });
878
+ }
879
+ pushLine(line, lineNumber) {
880
+ try {
881
+ const parsed = parseLogLine(line, {
882
+ format: this.#format,
883
+ file: this.#file,
884
+ line: lineNumber
885
+ });
886
+ const normalized = this.#normalizer.normalizeAll(parsed.records);
887
+ const redactedEvents = normalized.records.map((e) => ({
888
+ ...e,
889
+ attributes: e.attributes ? this.#redactor.redactRecord(e.attributes) : void 0
890
+ }));
891
+ this.#events = [...this.#events, ...redactedEvents];
892
+ this.#warnings = [...this.#warnings, ...parsed.warnings, ...normalized.warnings];
893
+ this.#trees = this.#treeBuilder.build(this.#events);
894
+ return {
895
+ events: this.#events,
896
+ trees: this.#trees,
897
+ warnings: this.#warnings
898
+ };
899
+ } catch (e) {
900
+ const msg = e instanceof Error ? e.message : String(e);
901
+ const warning = {
902
+ code: "UNKNOWN",
903
+ message: `LiveLogAccumulator failed to process line (${msg})`,
904
+ file: this.#file,
905
+ line: lineNumber,
906
+ raw: typeof line === "string" ? line.slice(0, 500) : void 0
907
+ };
908
+ this.#warnings = [...this.#warnings, warning];
909
+ return { events: this.#events, trees: this.#trees, warnings: this.#warnings };
910
+ }
911
+ }
912
+ getEvents() {
913
+ return this.#events;
914
+ }
915
+ getTrees() {
916
+ return this.#trees;
917
+ }
918
+ getWarnings() {
919
+ return this.#warnings;
920
+ }
921
+ reset() {
922
+ this.#events = [];
923
+ this.#trees = [];
924
+ this.#warnings = [];
925
+ }
926
+ };
927
+
928
+ // packages/core/src/logs/index.ts
929
+ function firstNonEmptyLine(text) {
930
+ for (const line of text.split(/\r?\n/)) {
931
+ const t = line.trim();
932
+ if (t !== "") return t;
933
+ }
934
+ return void 0;
935
+ }
936
+ async function detectFormat(filePath) {
937
+ const text = await promises.readFile(filePath, "utf-8");
938
+ const first = firstNonEmptyLine(text);
939
+ if (!first) return "log4js";
940
+ if (!first.startsWith("{")) return "log4js";
941
+ try {
942
+ const parsed = JSON.parse(first);
943
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return "json";
944
+ } catch {
945
+ }
946
+ return "log4js";
947
+ }
948
+ function applyOverrides(cfg, options) {
949
+ const override = {};
950
+ for (const k of [
951
+ "runIdKeys",
952
+ "eventKey",
953
+ "timestampKey",
954
+ "messageKey",
955
+ "levelKey",
956
+ "parentIdKey",
957
+ "durationKey",
958
+ "statusKey"
959
+ ]) {
960
+ const v = options[k];
961
+ if (v !== void 0) override[k] = v;
962
+ }
963
+ return mergeLogIngestConfig(cfg, override);
964
+ }
965
+ async function parseLogsToTrees(filePath, options = {}) {
966
+ const base = options.config ?? await loadLogIngestConfig(options.configPath);
967
+ const config = applyOverrides(base, options);
968
+ const format = options.format === "auto" || options.format === void 0 ? await detectFormat(filePath) : options.format;
969
+ let parsed;
970
+ if (format === "json") {
971
+ parsed = await new JsonLogParser().parseFile(filePath);
972
+ } else {
973
+ parsed = await new Log4jsParser().parseFile(filePath);
974
+ }
975
+ const normalizer = new EventNormalizer({ config });
976
+ const normalized = normalizer.normalizeAll(parsed.records);
977
+ const redactor = new Redactor({ rules: config.redact });
978
+ const events = normalized.records.map((e) => ({
979
+ ...e,
980
+ attributes: e.attributes ? redactor.redactRecord(e.attributes) : void 0
981
+ }));
982
+ const trees = new TreeBuilder({ config }).build(events);
983
+ return {
984
+ events,
985
+ trees,
986
+ warnings: [...parsed.warnings, ...normalized.warnings]
987
+ };
988
+ }
989
+
990
+ exports.DEFAULT_LOG_INGEST_CONFIG = DEFAULT_LOG_INGEST_CONFIG;
991
+ exports.DEFAULT_REDACT_KEYS = DEFAULT_REDACT_KEYS;
992
+ exports.EventNormalizer = EventNormalizer;
993
+ exports.JsonLogParser = JsonLogParser;
994
+ exports.LiveLogAccumulator = LiveLogAccumulator;
995
+ exports.Log4jsParser = Log4jsParser;
996
+ exports.Redactor = Redactor;
997
+ exports.TreeBuilder = TreeBuilder;
998
+ exports.loadLogIngestConfig = loadLogIngestConfig;
999
+ exports.matchMapping = matchMapping;
1000
+ exports.mergeLogIngestConfig = mergeLogIngestConfig;
1001
+ exports.parseLogLine = parseLogLine;
1002
+ exports.parseLogsToTrees = parseLogsToTrees;
1003
+ exports.renderRunTree = renderRunTree;
1004
+ exports.renderRunTrees = renderRunTrees;
1005
+ exports.wildcardMatch = wildcardMatch;
1006
+ //# sourceMappingURL=logs.cjs.map
1007
+ //# sourceMappingURL=logs.cjs.map