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