agent-inspect 1.4.0 → 1.6.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 +63 -0
- package/README.md +34 -6
- package/docs/ADAPTERS.md +6 -0
- package/docs/API.md +132 -9
- package/docs/ARCHITECTURE.md +4 -0
- package/docs/CLI.md +98 -5
- package/docs/DIFF.md +8 -0
- package/docs/EXPORTS.md +13 -0
- package/docs/GETTING-STARTED.md +19 -1
- package/docs/KNOWN-ISSUES.md +8 -1
- package/docs/LIMITATIONS.md +13 -2
- package/docs/LOGS.md +22 -0
- package/docs/SCHEMA.md +19 -7
- package/docs/SCREENSHOTS.md +190 -9
- package/package.json +71 -1
- package/packages/cli/dist/index.cjs +7121 -3986
- package/packages/cli/dist/index.cjs.map +1 -1
- package/packages/cli/dist/index.mjs +7122 -3987
- package/packages/cli/dist/index.mjs.map +1 -1
- package/packages/core/dist/advanced.cjs +2258 -0
- package/packages/core/dist/advanced.cjs.map +1 -0
- package/packages/core/dist/advanced.d.cts +254 -0
- package/packages/core/dist/advanced.d.ts +254 -0
- package/packages/core/dist/advanced.mjs +11 -0
- package/packages/core/dist/advanced.mjs.map +1 -0
- package/packages/core/dist/chunk-57S5D6HR.mjs +655 -0
- package/packages/core/dist/chunk-57S5D6HR.mjs.map +1 -0
- package/packages/core/dist/chunk-6QSLZCBJ.mjs +743 -0
- package/packages/core/dist/chunk-6QSLZCBJ.mjs.map +1 -0
- package/packages/core/dist/chunk-6SZPTECC.mjs +342 -0
- package/packages/core/dist/chunk-6SZPTECC.mjs.map +1 -0
- package/packages/core/dist/chunk-74XZ6N7Q.mjs +409 -0
- package/packages/core/dist/chunk-74XZ6N7Q.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-HR7G62IE.mjs +785 -0
- package/packages/core/dist/chunk-HR7G62IE.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-S4YWKV4G.mjs +48 -0
- package/packages/core/dist/chunk-S4YWKV4G.mjs.map +1 -0
- package/packages/core/dist/chunk-TFLPUZ56.mjs +1571 -0
- package/packages/core/dist/chunk-TFLPUZ56.mjs.map +1 -0
- package/packages/core/dist/chunk-TZISEVLQ.mjs +390 -0
- package/packages/core/dist/chunk-TZISEVLQ.mjs.map +1 -0
- package/packages/core/dist/chunk-U2BGPESY.mjs +150 -0
- package/packages/core/dist/chunk-U2BGPESY.mjs.map +1 -0
- package/packages/core/dist/chunk-VTIB5MDK.mjs +304 -0
- package/packages/core/dist/chunk-VTIB5MDK.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-Y56BPA3B.mjs +990 -0
- package/packages/core/dist/chunk-Y56BPA3B.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 +82 -0
- package/packages/core/dist/diff.d.ts +82 -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 +114 -0
- package/packages/core/dist/exporters.d.ts +114 -0
- package/packages/core/dist/exporters.mjs +6 -0
- package/packages/core/dist/exporters.mjs.map +1 -0
- package/packages/core/dist/index.cjs +5542 -2218
- package/packages/core/dist/index.cjs.map +1 -1
- package/packages/core/dist/index.d.cts +113 -908
- package/packages/core/dist/index.d.ts +113 -908
- package/packages/core/dist/index.mjs +1048 -5403
- package/packages/core/dist/index.mjs.map +1 -1
- package/packages/core/dist/inspect-event-Des4JDHo.d.cts +41 -0
- package/packages/core/dist/inspect-event-Des4JDHo.d.ts +41 -0
- package/packages/core/dist/log-config-BnH8Ykcb.d.cts +33 -0
- package/packages/core/dist/log-config-C1GcJPIM.d.ts +33 -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 +138 -0
- package/packages/core/dist/logs.d.ts +138 -0
- package/packages/core/dist/logs.mjs +6 -0
- package/packages/core/dist/logs.mjs.map +1 -0
- package/packages/core/dist/persisted-inspect-event-0kaRADsp.d.cts +56 -0
- package/packages/core/dist/persisted-inspect-event-DiFto0K2.d.ts +56 -0
- package/packages/core/dist/persisted.cjs +1055 -0
- package/packages/core/dist/persisted.cjs.map +1 -0
- package/packages/core/dist/persisted.d.cts +111 -0
- package/packages/core/dist/persisted.d.ts +111 -0
- package/packages/core/dist/persisted.mjs +7 -0
- package/packages/core/dist/persisted.mjs.map +1 -0
- package/packages/core/dist/readers.cjs +2590 -0
- package/packages/core/dist/readers.cjs.map +1 -0
- package/packages/core/dist/readers.d.cts +80 -0
- package/packages/core/dist/readers.d.ts +80 -0
- package/packages/core/dist/readers.mjs +9 -0
- package/packages/core/dist/readers.mjs.map +1 -0
- package/packages/core/dist/types-DB8jB6Jg.d.cts +232 -0
- package/packages/core/dist/types-tSix7tfv.d.ts +232 -0
- package/packages/core/dist/writers.cjs +997 -0
- package/packages/core/dist/writers.cjs.map +1 -0
- package/packages/core/dist/writers.d.cts +62 -0
- package/packages/core/dist/writers.d.ts +62 -0
- package/packages/core/dist/writers.mjs +9 -0
- package/packages/core/dist/writers.mjs.map +1 -0
|
@@ -0,0 +1,1571 @@
|
|
|
1
|
+
import { persistedInspectEventsToRunTrees, traceEventToPersistedInspectEvent, traceEventsToPersistedInspectEvents } from './chunk-TZISEVLQ.mjs';
|
|
2
|
+
import { parseTraceJsonl } from './chunk-VTIB5MDK.mjs';
|
|
3
|
+
import { readFile, readdir } from 'fs/promises';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
var DEFAULT_MAX_TRACE_INPUT_BYTES = 10 * 1024 * 1024;
|
|
7
|
+
var MIN_DETECTION_CONFIDENCE = 0.5;
|
|
8
|
+
var AMBIGUOUS_CONFIDENCE_DELTA = 0.05;
|
|
9
|
+
var resolvedInputCache = /* @__PURE__ */ new WeakMap();
|
|
10
|
+
var OPENINFERENCE_READER_FORMAT = "openinference-json";
|
|
11
|
+
var OTLP_READER_FORMAT = "otlp-json";
|
|
12
|
+
var OPENINFERENCE_SPAN_KEYS = /* @__PURE__ */ new Set([
|
|
13
|
+
"trace_id",
|
|
14
|
+
"traceId",
|
|
15
|
+
"span_id",
|
|
16
|
+
"spanId",
|
|
17
|
+
"parent_span_id",
|
|
18
|
+
"parentSpanId",
|
|
19
|
+
"name",
|
|
20
|
+
"start_time_unix_nano",
|
|
21
|
+
"startTimeUnixNano",
|
|
22
|
+
"end_time_unix_nano",
|
|
23
|
+
"endTimeUnixNano",
|
|
24
|
+
"start_time",
|
|
25
|
+
"startTime",
|
|
26
|
+
"end_time",
|
|
27
|
+
"endTime",
|
|
28
|
+
"attributes",
|
|
29
|
+
"status",
|
|
30
|
+
"kind",
|
|
31
|
+
"span_kind",
|
|
32
|
+
"spanKind"
|
|
33
|
+
]);
|
|
34
|
+
var OPENINFERENCE_SENSITIVE_ATTRIBUTE_KEYS = [
|
|
35
|
+
"input.value",
|
|
36
|
+
"output.value",
|
|
37
|
+
"input.mime_type",
|
|
38
|
+
"output.mime_type",
|
|
39
|
+
"llm.input_messages",
|
|
40
|
+
"llm.output_messages",
|
|
41
|
+
"llm.prompts",
|
|
42
|
+
"llm.completions",
|
|
43
|
+
"retrieval.documents",
|
|
44
|
+
"reranker.input_documents",
|
|
45
|
+
"reranker.output_documents",
|
|
46
|
+
"document.content",
|
|
47
|
+
"gen_ai.prompt",
|
|
48
|
+
"gen_ai.completion",
|
|
49
|
+
"gen_ai.input.messages",
|
|
50
|
+
"gen_ai.output.messages"
|
|
51
|
+
];
|
|
52
|
+
var OTLP_SPAN_KEYS = /* @__PURE__ */ new Set([
|
|
53
|
+
"traceId",
|
|
54
|
+
"spanId",
|
|
55
|
+
"parentSpanId",
|
|
56
|
+
"name",
|
|
57
|
+
"kind",
|
|
58
|
+
"startTimeUnixNano",
|
|
59
|
+
"endTimeUnixNano",
|
|
60
|
+
"attributes",
|
|
61
|
+
"events",
|
|
62
|
+
"status",
|
|
63
|
+
"droppedAttributesCount",
|
|
64
|
+
"droppedEventsCount",
|
|
65
|
+
"droppedLinksCount",
|
|
66
|
+
"links",
|
|
67
|
+
"flags"
|
|
68
|
+
]);
|
|
69
|
+
var TraceReadError = class extends Error {
|
|
70
|
+
code;
|
|
71
|
+
warnings;
|
|
72
|
+
constructor(code, message, warnings = []) {
|
|
73
|
+
super(message);
|
|
74
|
+
this.name = "TraceReadError";
|
|
75
|
+
this.code = code;
|
|
76
|
+
this.warnings = warnings;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
function normalizeCandidate(reader, candidate) {
|
|
80
|
+
const confidence = Number.isFinite(candidate.confidence) ? Math.max(0, Math.min(1, candidate.confidence)) : 0;
|
|
81
|
+
return {
|
|
82
|
+
...candidate,
|
|
83
|
+
format: candidate.format || reader.format,
|
|
84
|
+
confidence,
|
|
85
|
+
readerName: candidate.readerName ?? reader.name
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function sortCandidates(candidates) {
|
|
89
|
+
return [...candidates].sort((a, b) => {
|
|
90
|
+
if (b.confidence !== a.confidence) return b.confidence - a.confidence;
|
|
91
|
+
return a.format.localeCompare(b.format);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
function collectWarnings(candidates) {
|
|
95
|
+
return candidates.flatMap((candidate) => candidate.warnings ?? []);
|
|
96
|
+
}
|
|
97
|
+
function dedupeWarnings(warnings) {
|
|
98
|
+
const seen = /* @__PURE__ */ new Set();
|
|
99
|
+
const out = [];
|
|
100
|
+
for (const warning of warnings) {
|
|
101
|
+
const key = [
|
|
102
|
+
warning.code,
|
|
103
|
+
warning.message,
|
|
104
|
+
warning.severity ?? "",
|
|
105
|
+
warning.sourceFile ?? "",
|
|
106
|
+
warning.line ?? "",
|
|
107
|
+
warning.field ?? ""
|
|
108
|
+
].join("\0");
|
|
109
|
+
if (seen.has(key)) continue;
|
|
110
|
+
seen.add(key);
|
|
111
|
+
out.push(warning);
|
|
112
|
+
}
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
function attachSingleSourceFile(warnings, resolved) {
|
|
116
|
+
if (resolved.sourceFiles.length !== 1) return [...warnings];
|
|
117
|
+
const [sourceFile] = resolved.sourceFiles;
|
|
118
|
+
return warnings.map((warning) => ({
|
|
119
|
+
...warning,
|
|
120
|
+
sourceFile: warning.sourceFile ?? sourceFile
|
|
121
|
+
}));
|
|
122
|
+
}
|
|
123
|
+
function findReaderByFormat(format, readers) {
|
|
124
|
+
return readers.find((reader) => reader.format === format);
|
|
125
|
+
}
|
|
126
|
+
async function jsonlFilesInDirectory(dirPath) {
|
|
127
|
+
const entries = await readdir(dirPath, { withFileTypes: true });
|
|
128
|
+
return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map((entry) => path.join(dirPath, entry.name)).sort((a, b) => a.localeCompare(b));
|
|
129
|
+
}
|
|
130
|
+
async function resolveInput(input) {
|
|
131
|
+
const cached = resolvedInputCache.get(input);
|
|
132
|
+
if (cached) return cached;
|
|
133
|
+
const promise = resolveInputUncached(input);
|
|
134
|
+
resolvedInputCache.set(input, promise);
|
|
135
|
+
return promise;
|
|
136
|
+
}
|
|
137
|
+
function assertInputWithinBounds(content, sourceFile) {
|
|
138
|
+
const bytes = Buffer.byteLength(content, "utf8");
|
|
139
|
+
if (bytes <= DEFAULT_MAX_TRACE_INPUT_BYTES) return;
|
|
140
|
+
throw new TraceReadError("unsupported_format", "Trace input exceeds the local reader size limit.", [
|
|
141
|
+
{
|
|
142
|
+
code: "input_too_large",
|
|
143
|
+
message: `Trace input is ${bytes} bytes; max is ${DEFAULT_MAX_TRACE_INPUT_BYTES} bytes.`,
|
|
144
|
+
severity: "error",
|
|
145
|
+
...sourceFile !== void 0 ? { sourceFile } : {}
|
|
146
|
+
}
|
|
147
|
+
]);
|
|
148
|
+
}
|
|
149
|
+
async function resolveInputUncached(input) {
|
|
150
|
+
if (input.type === "string") {
|
|
151
|
+
assertInputWithinBounds(input.content);
|
|
152
|
+
return { content: input.content, sourceFiles: [] };
|
|
153
|
+
}
|
|
154
|
+
if (input.type === "buffer") {
|
|
155
|
+
const content = input.content.toString("utf-8");
|
|
156
|
+
assertInputWithinBounds(content);
|
|
157
|
+
return { content, sourceFiles: [] };
|
|
158
|
+
}
|
|
159
|
+
if (input.type === "file") {
|
|
160
|
+
const content = await readFile(input.path, "utf-8");
|
|
161
|
+
assertInputWithinBounds(content, input.path);
|
|
162
|
+
return { content, sourceFiles: [input.path] };
|
|
163
|
+
}
|
|
164
|
+
if (input.type === "directory") {
|
|
165
|
+
const files = await jsonlFilesInDirectory(input.path);
|
|
166
|
+
const parts = await Promise.all(
|
|
167
|
+
files.map(async (file) => (await readFile(file, "utf-8")).trimEnd())
|
|
168
|
+
);
|
|
169
|
+
const content = parts.filter((part) => part.trim() !== "").join("\n");
|
|
170
|
+
assertInputWithinBounds(content, input.path);
|
|
171
|
+
return {
|
|
172
|
+
content,
|
|
173
|
+
sourceFiles: files
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
return void 0;
|
|
177
|
+
}
|
|
178
|
+
function detectJsonlFormat(content) {
|
|
179
|
+
let saw01 = false;
|
|
180
|
+
let saw02 = false;
|
|
181
|
+
let validRows = 0;
|
|
182
|
+
let invalidJsonRows = 0;
|
|
183
|
+
let unknownSchemaRows = 0;
|
|
184
|
+
let firstInvalidJsonLine;
|
|
185
|
+
let firstUnknownSchemaLine;
|
|
186
|
+
let lineNumber = 0;
|
|
187
|
+
for (const line of content.split(/\r?\n/)) {
|
|
188
|
+
lineNumber += 1;
|
|
189
|
+
const trimmed = line.trim();
|
|
190
|
+
if (trimmed === "") continue;
|
|
191
|
+
let parsed;
|
|
192
|
+
try {
|
|
193
|
+
parsed = JSON.parse(trimmed);
|
|
194
|
+
} catch {
|
|
195
|
+
invalidJsonRows += 1;
|
|
196
|
+
firstInvalidJsonLine ??= lineNumber;
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) && "schemaVersion" in parsed) {
|
|
200
|
+
const version = parsed.schemaVersion;
|
|
201
|
+
if (version === "0.1") {
|
|
202
|
+
saw01 = true;
|
|
203
|
+
validRows += 1;
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
if (version === "0.2") {
|
|
207
|
+
saw02 = true;
|
|
208
|
+
validRows += 1;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
unknownSchemaRows += 1;
|
|
213
|
+
firstUnknownSchemaLine ??= lineNumber;
|
|
214
|
+
}
|
|
215
|
+
const warnings = [];
|
|
216
|
+
if (invalidJsonRows > 0) {
|
|
217
|
+
warnings.push({
|
|
218
|
+
code: "invalid_jsonl_rows",
|
|
219
|
+
message: `Skipped ${invalidJsonRows} invalid JSONL row(s) during format detection.`,
|
|
220
|
+
severity: "warning",
|
|
221
|
+
...firstInvalidJsonLine !== void 0 ? { line: firstInvalidJsonLine } : {}
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
if (unknownSchemaRows > 0) {
|
|
225
|
+
warnings.push({
|
|
226
|
+
code: "unknown_schema_rows",
|
|
227
|
+
message: `Skipped ${unknownSchemaRows} row(s) with unknown schemaVersion during format detection.`,
|
|
228
|
+
severity: "warning",
|
|
229
|
+
...firstUnknownSchemaLine !== void 0 ? { line: firstUnknownSchemaLine } : {}
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
let format = "empty";
|
|
233
|
+
if (saw01 && saw02) format = "mixed";
|
|
234
|
+
else if (saw01) format = "0.1";
|
|
235
|
+
else if (saw02) format = "0.2";
|
|
236
|
+
return { format, validRows, warnings };
|
|
237
|
+
}
|
|
238
|
+
function agentInspectFormatLabel(format) {
|
|
239
|
+
switch (format) {
|
|
240
|
+
case "0.1":
|
|
241
|
+
return "agent-inspect-v0.1-jsonl";
|
|
242
|
+
case "0.2":
|
|
243
|
+
return "agent-inspect-v0.2-jsonl";
|
|
244
|
+
case "mixed":
|
|
245
|
+
return "agent-inspect-mixed-jsonl";
|
|
246
|
+
default:
|
|
247
|
+
return "agent-inspect-jsonl";
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function persistedEventsForParsedTrace(parsed) {
|
|
251
|
+
if (parsed.format === "0.2" && parsed.persisted.length > 0) {
|
|
252
|
+
return [...parsed.persisted];
|
|
253
|
+
}
|
|
254
|
+
if (parsed.format === "mixed" && parsed.rows.length > 0) {
|
|
255
|
+
return parsed.rows.map((row, index) => {
|
|
256
|
+
if (row.format === "0.2") return row.event;
|
|
257
|
+
return traceEventToPersistedInspectEvent(row.event, {
|
|
258
|
+
eventIndex: index,
|
|
259
|
+
sourceName: "agent-inspect-jsonl-reader"
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
return traceEventsToPersistedInspectEvents(parsed.events, {
|
|
264
|
+
sourceName: "agent-inspect-jsonl-reader"
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
function isRecord(value) {
|
|
268
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
269
|
+
}
|
|
270
|
+
function isNonEmptyString(value) {
|
|
271
|
+
return typeof value === "string" && value.trim() !== "";
|
|
272
|
+
}
|
|
273
|
+
function readStringField(record, keys) {
|
|
274
|
+
for (const key of keys) {
|
|
275
|
+
const value = record[key];
|
|
276
|
+
if (isNonEmptyString(value)) return value;
|
|
277
|
+
}
|
|
278
|
+
return void 0;
|
|
279
|
+
}
|
|
280
|
+
function readRecordField(record, key) {
|
|
281
|
+
const value = record[key];
|
|
282
|
+
return isRecord(value) ? value : void 0;
|
|
283
|
+
}
|
|
284
|
+
function parseJsonDocument(content) {
|
|
285
|
+
return JSON.parse(content);
|
|
286
|
+
}
|
|
287
|
+
function looksLikeOpenInferenceSpan(value) {
|
|
288
|
+
if (!isRecord(value)) return false;
|
|
289
|
+
const attributes = readRecordField(value, "attributes");
|
|
290
|
+
return readStringField(value, ["trace_id", "traceId"]) !== void 0 && readStringField(value, ["span_id", "spanId"]) !== void 0 && (readStringField(value, ["name"]) !== void 0 || attributes?.["openinference.span.kind"] !== void 0);
|
|
291
|
+
}
|
|
292
|
+
function extractOpenInferenceDocument(root) {
|
|
293
|
+
const warnings = [];
|
|
294
|
+
const unsupportedFields = [];
|
|
295
|
+
if (Array.isArray(root)) {
|
|
296
|
+
const spans = root.filter(looksLikeOpenInferenceSpan);
|
|
297
|
+
if (spans.length === 0) return void 0;
|
|
298
|
+
if (spans.length !== root.length) {
|
|
299
|
+
warnings.push({
|
|
300
|
+
code: "openinference_skipped_items",
|
|
301
|
+
message: "Skipped non-span item(s) in OpenInference span array.",
|
|
302
|
+
severity: "warning"
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
return {
|
|
306
|
+
spans,
|
|
307
|
+
confidence: 0.82,
|
|
308
|
+
description: "OpenInference span array",
|
|
309
|
+
warnings,
|
|
310
|
+
unsupportedFields
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
if (!isRecord(root)) return void 0;
|
|
314
|
+
const rootFormat = root.format;
|
|
315
|
+
const rootCompatibility = root.compatibility;
|
|
316
|
+
const version = typeof root.version === "string" && root.version.trim() !== "" ? root.version : void 0;
|
|
317
|
+
if (Array.isArray(root.spans)) {
|
|
318
|
+
const spans = root.spans.filter(looksLikeOpenInferenceSpan);
|
|
319
|
+
if (spans.length === 0 && (rootFormat === "openinference" || rootCompatibility === "openinference-compatible")) {
|
|
320
|
+
warnings.push({
|
|
321
|
+
code: "openinference_no_valid_spans",
|
|
322
|
+
message: "OpenInference document did not contain any valid spans.",
|
|
323
|
+
severity: "error"
|
|
324
|
+
});
|
|
325
|
+
return {
|
|
326
|
+
spans,
|
|
327
|
+
confidence: 0.7,
|
|
328
|
+
description: "Malformed OpenInference document",
|
|
329
|
+
version,
|
|
330
|
+
warnings,
|
|
331
|
+
unsupportedFields
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
if (spans.length === 0) return void 0;
|
|
335
|
+
if (spans.length !== root.spans.length) {
|
|
336
|
+
warnings.push({
|
|
337
|
+
code: "openinference_skipped_spans",
|
|
338
|
+
message: "Skipped invalid OpenInference span item(s).",
|
|
339
|
+
severity: "warning"
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
return {
|
|
343
|
+
spans,
|
|
344
|
+
confidence: rootFormat === "openinference" || rootCompatibility === "openinference-compatible" ? 0.9 : 0.84,
|
|
345
|
+
description: rootFormat === "openinference" || rootCompatibility === "openinference-compatible" ? "OpenInference document" : "OpenInference spans document",
|
|
346
|
+
version,
|
|
347
|
+
warnings,
|
|
348
|
+
unsupportedFields
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
if (Array.isArray(root.data)) {
|
|
352
|
+
const spans = root.data.filter(looksLikeOpenInferenceSpan);
|
|
353
|
+
if (spans.length === 0) return void 0;
|
|
354
|
+
if (spans.length !== root.data.length) {
|
|
355
|
+
warnings.push({
|
|
356
|
+
code: "openinference_skipped_data_items",
|
|
357
|
+
message: "Skipped non-span item(s) in OpenInference data array.",
|
|
358
|
+
severity: "warning"
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
return {
|
|
362
|
+
spans,
|
|
363
|
+
confidence: 0.8,
|
|
364
|
+
description: "OpenInference data document",
|
|
365
|
+
version,
|
|
366
|
+
warnings,
|
|
367
|
+
unsupportedFields
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
if (looksLikeOpenInferenceSpan(root)) {
|
|
371
|
+
return {
|
|
372
|
+
spans: [root],
|
|
373
|
+
confidence: 0.76,
|
|
374
|
+
description: "OpenInference single span",
|
|
375
|
+
version,
|
|
376
|
+
warnings,
|
|
377
|
+
unsupportedFields
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
if (rootFormat === "openinference" || rootCompatibility === "openinference-compatible") {
|
|
381
|
+
warnings.push({
|
|
382
|
+
code: "openinference_missing_spans",
|
|
383
|
+
message: "OpenInference document is missing a spans array.",
|
|
384
|
+
severity: "error"
|
|
385
|
+
});
|
|
386
|
+
return {
|
|
387
|
+
spans: [],
|
|
388
|
+
confidence: 0.7,
|
|
389
|
+
description: "Malformed OpenInference document",
|
|
390
|
+
version,
|
|
391
|
+
warnings,
|
|
392
|
+
unsupportedFields
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
return void 0;
|
|
396
|
+
}
|
|
397
|
+
function parseUnixNanoToIso(value) {
|
|
398
|
+
if (typeof value === "bigint" && value >= 0n) {
|
|
399
|
+
return new Date(Number(value / 1000000n)).toISOString();
|
|
400
|
+
}
|
|
401
|
+
if (typeof value === "number" && Number.isFinite(value) && value >= 0) {
|
|
402
|
+
return new Date(Math.floor(value / 1e6)).toISOString();
|
|
403
|
+
}
|
|
404
|
+
if (typeof value === "string" && /^\d+$/.test(value)) {
|
|
405
|
+
return new Date(Number(BigInt(value) / 1000000n)).toISOString();
|
|
406
|
+
}
|
|
407
|
+
return void 0;
|
|
408
|
+
}
|
|
409
|
+
function parseIsoTime(value) {
|
|
410
|
+
if (!isNonEmptyString(value)) return void 0;
|
|
411
|
+
const ms = Date.parse(value);
|
|
412
|
+
if (!Number.isFinite(ms)) return void 0;
|
|
413
|
+
return new Date(ms).toISOString();
|
|
414
|
+
}
|
|
415
|
+
function readOpenInferenceTimestamp(span, nanoKeys, isoKeys) {
|
|
416
|
+
for (const key of nanoKeys) {
|
|
417
|
+
const iso = parseUnixNanoToIso(span[key]);
|
|
418
|
+
if (iso !== void 0) return iso;
|
|
419
|
+
}
|
|
420
|
+
for (const key of isoKeys) {
|
|
421
|
+
const iso = parseIsoTime(span[key]);
|
|
422
|
+
if (iso !== void 0) return iso;
|
|
423
|
+
}
|
|
424
|
+
return void 0;
|
|
425
|
+
}
|
|
426
|
+
function durationBetweenIso(startedAt, endedAt) {
|
|
427
|
+
if (startedAt === void 0 || endedAt === void 0) return void 0;
|
|
428
|
+
const startMs = Date.parse(startedAt);
|
|
429
|
+
const endMs = Date.parse(endedAt);
|
|
430
|
+
if (!Number.isFinite(startMs) || !Number.isFinite(endMs) || endMs < startMs) {
|
|
431
|
+
return void 0;
|
|
432
|
+
}
|
|
433
|
+
return endMs - startMs;
|
|
434
|
+
}
|
|
435
|
+
function isSensitiveOpenInferenceAttribute(key) {
|
|
436
|
+
return OPENINFERENCE_SENSITIVE_ATTRIBUTE_KEYS.some(
|
|
437
|
+
(sensitiveKey) => key === sensitiveKey || key.startsWith(`${sensitiveKey}.`) || key.endsWith(".message.content") || key.endsWith(".document.content")
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
function summarizeAttributeValue(value) {
|
|
441
|
+
if (typeof value === "string") {
|
|
442
|
+
return { type: "string", length: value.length };
|
|
443
|
+
}
|
|
444
|
+
if (typeof value === "number") {
|
|
445
|
+
return { type: "number", finite: Number.isFinite(value) };
|
|
446
|
+
}
|
|
447
|
+
if (typeof value === "boolean") {
|
|
448
|
+
return { type: "boolean" };
|
|
449
|
+
}
|
|
450
|
+
if (Array.isArray(value)) {
|
|
451
|
+
return { type: "array", length: value.length };
|
|
452
|
+
}
|
|
453
|
+
if (isRecord(value)) {
|
|
454
|
+
return { type: "object", keyCount: Object.keys(value).length };
|
|
455
|
+
}
|
|
456
|
+
if (value === null) {
|
|
457
|
+
return { type: "null" };
|
|
458
|
+
}
|
|
459
|
+
return { type: typeof value };
|
|
460
|
+
}
|
|
461
|
+
function sanitizeOpenInferenceAttributes(attributes, pathPrefix) {
|
|
462
|
+
const out = {};
|
|
463
|
+
const warnings = [];
|
|
464
|
+
const unsupportedFields = [];
|
|
465
|
+
const summarizedKeys = [];
|
|
466
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
467
|
+
if (isSensitiveOpenInferenceAttribute(key)) {
|
|
468
|
+
summarizedKeys.push(key);
|
|
469
|
+
out[`${key}.summary`] = summarizeAttributeValue(value);
|
|
470
|
+
unsupportedFields.push(`${pathPrefix}.attributes.${key}`);
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
out[key] = value;
|
|
474
|
+
}
|
|
475
|
+
if (summarizedKeys.length > 0) {
|
|
476
|
+
out["openinference.summarized_attributes"] = summarizedKeys;
|
|
477
|
+
warnings.push({
|
|
478
|
+
code: "openinference_sensitive_attribute_summarized",
|
|
479
|
+
message: "OpenInference prompt/output/document attribute(s) were summarized instead of copied verbatim.",
|
|
480
|
+
severity: "warning"
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
return { attributes: out, warnings, unsupportedFields };
|
|
484
|
+
}
|
|
485
|
+
function mapOpenInferenceKind(span, attributes, pathPrefix) {
|
|
486
|
+
const warnings = [];
|
|
487
|
+
const agentInspectKind = attributes["agent_inspect.kind"];
|
|
488
|
+
if (agentInspectKind === "RUN" || agentInspectKind === "AGENT" || agentInspectKind === "LLM" || agentInspectKind === "TOOL" || agentInspectKind === "CHAIN" || agentInspectKind === "RETRIEVER" || agentInspectKind === "DECISION" || agentInspectKind === "RESULT" || agentInspectKind === "ERROR" || agentInspectKind === "LOGIC" || agentInspectKind === "LOG") {
|
|
489
|
+
return { kind: agentInspectKind, warnings };
|
|
490
|
+
}
|
|
491
|
+
const rawKind = readStringField(span, ["kind", "span_kind", "spanKind"]) ?? (typeof attributes["openinference.span.kind"] === "string" ? attributes["openinference.span.kind"] : void 0);
|
|
492
|
+
const normalized = rawKind?.toUpperCase();
|
|
493
|
+
switch (normalized) {
|
|
494
|
+
case "LLM":
|
|
495
|
+
return { kind: "LLM", warnings };
|
|
496
|
+
case "TOOL":
|
|
497
|
+
return { kind: "TOOL", warnings };
|
|
498
|
+
case "CHAIN":
|
|
499
|
+
return { kind: "CHAIN", warnings };
|
|
500
|
+
case "RETRIEVER":
|
|
501
|
+
return { kind: "RETRIEVER", warnings };
|
|
502
|
+
case "AGENT":
|
|
503
|
+
return { kind: "AGENT", warnings };
|
|
504
|
+
case "EMBEDDING":
|
|
505
|
+
warnings.push({
|
|
506
|
+
code: "openinference_kind_semantic_loss",
|
|
507
|
+
message: "OpenInference EMBEDDING span kind mapped to AgentInspect LLM.",
|
|
508
|
+
severity: "warning",
|
|
509
|
+
field: `${pathPrefix}.attributes.openinference.span.kind`
|
|
510
|
+
});
|
|
511
|
+
return { kind: "LLM", warnings };
|
|
512
|
+
case "RERANKER":
|
|
513
|
+
warnings.push({
|
|
514
|
+
code: "openinference_kind_semantic_loss",
|
|
515
|
+
message: "OpenInference RERANKER span kind mapped to AgentInspect RETRIEVER.",
|
|
516
|
+
severity: "warning",
|
|
517
|
+
field: `${pathPrefix}.attributes.openinference.span.kind`
|
|
518
|
+
});
|
|
519
|
+
return { kind: "RETRIEVER", warnings };
|
|
520
|
+
case "UNKNOWN":
|
|
521
|
+
case void 0:
|
|
522
|
+
warnings.push({
|
|
523
|
+
code: "openinference_kind_unknown",
|
|
524
|
+
message: "OpenInference span kind was missing or unknown; mapped to AgentInspect LOGIC.",
|
|
525
|
+
severity: "warning",
|
|
526
|
+
field: `${pathPrefix}.attributes.openinference.span.kind`
|
|
527
|
+
});
|
|
528
|
+
return { kind: "LOGIC", warnings };
|
|
529
|
+
default:
|
|
530
|
+
warnings.push({
|
|
531
|
+
code: "openinference_kind_unsupported",
|
|
532
|
+
message: `Unsupported OpenInference span kind "${rawKind}" mapped to AgentInspect LOGIC.`,
|
|
533
|
+
severity: "warning",
|
|
534
|
+
field: `${pathPrefix}.attributes.openinference.span.kind`
|
|
535
|
+
});
|
|
536
|
+
return { kind: "LOGIC", warnings };
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
function mapOpenInferenceStatus(status) {
|
|
540
|
+
if (!isRecord(status)) return void 0;
|
|
541
|
+
const rawCode = status.code;
|
|
542
|
+
if (typeof rawCode !== "string") return void 0;
|
|
543
|
+
switch (rawCode.toUpperCase()) {
|
|
544
|
+
case "OK":
|
|
545
|
+
return "ok";
|
|
546
|
+
case "ERROR":
|
|
547
|
+
return "error";
|
|
548
|
+
case "UNSET":
|
|
549
|
+
return "unknown";
|
|
550
|
+
default:
|
|
551
|
+
return "unknown";
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
function readOpenInferenceTokenUsage(attributes) {
|
|
555
|
+
const prompt = attributes["llm.token_count.prompt"];
|
|
556
|
+
const completion = attributes["llm.token_count.completion"];
|
|
557
|
+
const total = attributes["llm.token_count.total"];
|
|
558
|
+
const cached = attributes["llm.token_count.prompt_details.cache_read"];
|
|
559
|
+
const usage = {};
|
|
560
|
+
if (typeof prompt === "number" && Number.isFinite(prompt) && prompt >= 0) {
|
|
561
|
+
usage.input = prompt;
|
|
562
|
+
}
|
|
563
|
+
if (typeof completion === "number" && Number.isFinite(completion) && completion >= 0) {
|
|
564
|
+
usage.output = completion;
|
|
565
|
+
}
|
|
566
|
+
if (typeof total === "number" && Number.isFinite(total) && total >= 0) {
|
|
567
|
+
usage.total = total;
|
|
568
|
+
}
|
|
569
|
+
if (typeof cached === "number" && Number.isFinite(cached) && cached >= 0) {
|
|
570
|
+
usage.cached = cached;
|
|
571
|
+
}
|
|
572
|
+
if (usage.total === void 0 && usage.input !== void 0 && usage.output !== void 0) {
|
|
573
|
+
usage.total = usage.input + usage.output;
|
|
574
|
+
}
|
|
575
|
+
return Object.keys(usage).length > 0 ? usage : void 0;
|
|
576
|
+
}
|
|
577
|
+
function readOpenInferenceConfidence(attributes) {
|
|
578
|
+
const confidence = attributes["agent_inspect.confidence"];
|
|
579
|
+
if (confidence === "explicit" || confidence === "correlated" || confidence === "heuristic" || confidence === "unknown") {
|
|
580
|
+
return confidence;
|
|
581
|
+
}
|
|
582
|
+
return "correlated";
|
|
583
|
+
}
|
|
584
|
+
function mapOpenInferenceSpan(span, index, version) {
|
|
585
|
+
const pathPrefix = `spans[${index}]`;
|
|
586
|
+
const warnings = [];
|
|
587
|
+
const unsupportedFields = [];
|
|
588
|
+
const rawAttributes = readRecordField(span, "attributes") ?? {};
|
|
589
|
+
const sanitized = sanitizeOpenInferenceAttributes(rawAttributes, pathPrefix);
|
|
590
|
+
warnings.push(...sanitized.warnings);
|
|
591
|
+
unsupportedFields.push(...sanitized.unsupportedFields);
|
|
592
|
+
const attributes = { ...sanitized.attributes };
|
|
593
|
+
for (const [key, value] of Object.entries(span)) {
|
|
594
|
+
if (OPENINFERENCE_SPAN_KEYS.has(key)) continue;
|
|
595
|
+
unsupportedFields.push(`${pathPrefix}.${key}`);
|
|
596
|
+
if (value === null || typeof value !== "object") {
|
|
597
|
+
attributes[`openinference.${key}`] = value;
|
|
598
|
+
} else {
|
|
599
|
+
attributes[`openinference.${key}.summary`] = summarizeAttributeValue(value);
|
|
600
|
+
warnings.push({
|
|
601
|
+
code: "openinference_unsupported_field_summarized",
|
|
602
|
+
message: `Unsupported OpenInference span field "${key}" was summarized.`,
|
|
603
|
+
severity: "warning",
|
|
604
|
+
field: `${pathPrefix}.${key}`
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
const traceId = readStringField(span, ["trace_id", "traceId"]) ?? `trace-${index}`;
|
|
609
|
+
const spanId = readStringField(span, ["span_id", "spanId"]) ?? `span-${index}`;
|
|
610
|
+
const parentSpanId = readStringField(span, ["parent_span_id", "parentSpanId"]);
|
|
611
|
+
const name = readStringField(span, ["name"]) ?? spanId;
|
|
612
|
+
const startedAt = readOpenInferenceTimestamp(
|
|
613
|
+
span,
|
|
614
|
+
["start_time_unix_nano", "startTimeUnixNano"],
|
|
615
|
+
["start_time", "startTime"]
|
|
616
|
+
);
|
|
617
|
+
const endedAt = readOpenInferenceTimestamp(
|
|
618
|
+
span,
|
|
619
|
+
["end_time_unix_nano", "endTimeUnixNano"],
|
|
620
|
+
["end_time", "endTime"]
|
|
621
|
+
);
|
|
622
|
+
const timestamp = startedAt ?? "1970-01-01T00:00:00.000Z";
|
|
623
|
+
if (startedAt === void 0) {
|
|
624
|
+
warnings.push({
|
|
625
|
+
code: "openinference_missing_start_time",
|
|
626
|
+
message: "OpenInference span is missing a valid start time; using Unix epoch.",
|
|
627
|
+
severity: "warning",
|
|
628
|
+
field: `${pathPrefix}.start_time_unix_nano`
|
|
629
|
+
});
|
|
630
|
+
unsupportedFields.push(`${pathPrefix}.start_time_unix_nano`);
|
|
631
|
+
}
|
|
632
|
+
const { kind, warnings: kindWarnings } = mapOpenInferenceKind(
|
|
633
|
+
span,
|
|
634
|
+
rawAttributes,
|
|
635
|
+
pathPrefix
|
|
636
|
+
);
|
|
637
|
+
warnings.push(...kindWarnings);
|
|
638
|
+
const status = mapOpenInferenceStatus(span.status);
|
|
639
|
+
const tokenUsage = readOpenInferenceTokenUsage(rawAttributes);
|
|
640
|
+
const errorMessage = isRecord(span.status) && typeof span.status.message === "string" ? span.status.message : void 0;
|
|
641
|
+
const event = {
|
|
642
|
+
schemaVersion: "0.2",
|
|
643
|
+
eventId: typeof rawAttributes["agent_inspect.event_id"] === "string" ? rawAttributes["agent_inspect.event_id"] : spanId,
|
|
644
|
+
runId: typeof rawAttributes["agent_inspect.run_id"] === "string" ? rawAttributes["agent_inspect.run_id"] : traceId,
|
|
645
|
+
kind,
|
|
646
|
+
name,
|
|
647
|
+
timestamp,
|
|
648
|
+
confidence: readOpenInferenceConfidence(rawAttributes),
|
|
649
|
+
source: {
|
|
650
|
+
type: "otel",
|
|
651
|
+
name: "openinference",
|
|
652
|
+
...version !== void 0 ? { version } : {}
|
|
653
|
+
},
|
|
654
|
+
attributes,
|
|
655
|
+
trace: {
|
|
656
|
+
traceId,
|
|
657
|
+
spanId,
|
|
658
|
+
...parentSpanId !== void 0 ? { parentSpanId } : {}
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
if (status !== void 0) {
|
|
662
|
+
event.status = status;
|
|
663
|
+
}
|
|
664
|
+
if (startedAt !== void 0) {
|
|
665
|
+
event.startedAt = startedAt;
|
|
666
|
+
}
|
|
667
|
+
if (endedAt !== void 0) {
|
|
668
|
+
event.endedAt = endedAt;
|
|
669
|
+
}
|
|
670
|
+
const durationMs = durationBetweenIso(startedAt, endedAt);
|
|
671
|
+
if (durationMs !== void 0) {
|
|
672
|
+
event.durationMs = durationMs;
|
|
673
|
+
}
|
|
674
|
+
if (tokenUsage !== void 0) {
|
|
675
|
+
event.tokenUsage = tokenUsage;
|
|
676
|
+
}
|
|
677
|
+
if (status === "error") {
|
|
678
|
+
event.error = {
|
|
679
|
+
message: errorMessage !== void 0 && errorMessage.trim() !== "" ? errorMessage : "OpenInference span error"
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
return {
|
|
683
|
+
event,
|
|
684
|
+
warnings,
|
|
685
|
+
unsupportedFields,
|
|
686
|
+
spanId,
|
|
687
|
+
...parentSpanId !== void 0 ? { parentSpanId } : {}
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
function mapOpenInferenceEvents(document) {
|
|
691
|
+
const mapped = document.spans.map(
|
|
692
|
+
(span, index) => mapOpenInferenceSpan(span, index, document.version)
|
|
693
|
+
);
|
|
694
|
+
const spanIdToEventId = new Map(
|
|
695
|
+
mapped.map((span) => [span.spanId, span.event.eventId])
|
|
696
|
+
);
|
|
697
|
+
for (const span of mapped) {
|
|
698
|
+
if (span.parentSpanId === void 0) continue;
|
|
699
|
+
span.event.parentId = spanIdToEventId.get(span.parentSpanId) ?? span.parentSpanId;
|
|
700
|
+
}
|
|
701
|
+
return {
|
|
702
|
+
events: mapped.map((span) => span.event),
|
|
703
|
+
warnings: mapped.flatMap((span) => span.warnings),
|
|
704
|
+
unsupportedFields: mapped.flatMap((span) => span.unsupportedFields)
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
var openInferenceJsonReader = {
|
|
708
|
+
format: OPENINFERENCE_READER_FORMAT,
|
|
709
|
+
name: "OpenInference JSON",
|
|
710
|
+
async detect(input) {
|
|
711
|
+
const resolved = await resolveInput(input);
|
|
712
|
+
if (!resolved) return void 0;
|
|
713
|
+
let parsed;
|
|
714
|
+
try {
|
|
715
|
+
parsed = parseJsonDocument(resolved.content);
|
|
716
|
+
} catch {
|
|
717
|
+
return void 0;
|
|
718
|
+
}
|
|
719
|
+
const document = extractOpenInferenceDocument(parsed);
|
|
720
|
+
if (!document) return void 0;
|
|
721
|
+
return {
|
|
722
|
+
format: OPENINFERENCE_READER_FORMAT,
|
|
723
|
+
confidence: document.confidence,
|
|
724
|
+
readerName: "OpenInference JSON",
|
|
725
|
+
description: document.description,
|
|
726
|
+
warnings: attachSingleSourceFile(document.warnings, resolved)
|
|
727
|
+
};
|
|
728
|
+
},
|
|
729
|
+
async read(input) {
|
|
730
|
+
const resolved = await resolveInput(input);
|
|
731
|
+
if (!resolved) {
|
|
732
|
+
throw new TraceReadError(
|
|
733
|
+
"unsupported_format",
|
|
734
|
+
"OpenInference JSON reader requires file, string, or buffer input."
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
let parsed;
|
|
738
|
+
try {
|
|
739
|
+
parsed = parseJsonDocument(resolved.content);
|
|
740
|
+
} catch {
|
|
741
|
+
throw new TraceReadError("unsupported_format", "OpenInference JSON input is not valid JSON.", [
|
|
742
|
+
{
|
|
743
|
+
code: "openinference_invalid_json",
|
|
744
|
+
message: "OpenInference JSON reader could not parse the input as JSON.",
|
|
745
|
+
severity: "error"
|
|
746
|
+
}
|
|
747
|
+
]);
|
|
748
|
+
}
|
|
749
|
+
const document = extractOpenInferenceDocument(parsed);
|
|
750
|
+
if (!document || document.spans.length === 0) {
|
|
751
|
+
throw new TraceReadError(
|
|
752
|
+
"unsupported_format",
|
|
753
|
+
"No valid OpenInference spans found.",
|
|
754
|
+
attachSingleSourceFile(
|
|
755
|
+
document?.warnings ?? [
|
|
756
|
+
{
|
|
757
|
+
code: "openinference_no_valid_spans",
|
|
758
|
+
message: "OpenInference JSON input did not contain valid spans.",
|
|
759
|
+
severity: "error"
|
|
760
|
+
}
|
|
761
|
+
],
|
|
762
|
+
resolved
|
|
763
|
+
)
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
const mapped = mapOpenInferenceEvents(document);
|
|
767
|
+
const warnings = attachSingleSourceFile(
|
|
768
|
+
[...document.warnings, ...mapped.warnings],
|
|
769
|
+
resolved
|
|
770
|
+
);
|
|
771
|
+
const unsupportedFields = [
|
|
772
|
+
...document.unsupportedFields,
|
|
773
|
+
...mapped.unsupportedFields
|
|
774
|
+
].sort((a, b) => a.localeCompare(b));
|
|
775
|
+
return {
|
|
776
|
+
format: OPENINFERENCE_READER_FORMAT,
|
|
777
|
+
events: mapped.events,
|
|
778
|
+
runs: persistedInspectEventsToRunTrees(mapped.events, { skipInvalid: true }),
|
|
779
|
+
warnings,
|
|
780
|
+
unsupportedFields,
|
|
781
|
+
sourceFiles: resolved.sourceFiles
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
};
|
|
785
|
+
function parseOtlpAnyValue(value, field, warnings, unsupportedFields) {
|
|
786
|
+
if (!isRecord(value)) {
|
|
787
|
+
unsupportedFields.push(field);
|
|
788
|
+
warnings.push({
|
|
789
|
+
code: "otlp_attribute_value_invalid",
|
|
790
|
+
message: "OTLP attribute value was not an AnyValue object.",
|
|
791
|
+
severity: "warning",
|
|
792
|
+
field
|
|
793
|
+
});
|
|
794
|
+
return void 0;
|
|
795
|
+
}
|
|
796
|
+
if (typeof value.stringValue === "string") return value.stringValue;
|
|
797
|
+
if (typeof value.boolValue === "boolean") return value.boolValue;
|
|
798
|
+
if (typeof value.intValue === "number" && Number.isFinite(value.intValue)) {
|
|
799
|
+
return value.intValue;
|
|
800
|
+
}
|
|
801
|
+
if (typeof value.intValue === "string" && value.intValue.trim() !== "") {
|
|
802
|
+
const n = Number(value.intValue);
|
|
803
|
+
if (Number.isFinite(n)) return n;
|
|
804
|
+
}
|
|
805
|
+
if (typeof value.doubleValue === "number" && Number.isFinite(value.doubleValue)) {
|
|
806
|
+
return value.doubleValue;
|
|
807
|
+
}
|
|
808
|
+
if (isRecord(value.arrayValue) && Array.isArray(value.arrayValue.values)) {
|
|
809
|
+
return value.arrayValue.values.map(
|
|
810
|
+
(item, index) => parseOtlpAnyValue(item, `${field}.arrayValue.values[${index}]`, warnings, unsupportedFields)
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
if (isRecord(value.kvlistValue) && Array.isArray(value.kvlistValue.values)) {
|
|
814
|
+
const out = {};
|
|
815
|
+
for (const [index, item] of value.kvlistValue.values.entries()) {
|
|
816
|
+
if (!isRecord(item) || typeof item.key !== "string") {
|
|
817
|
+
unsupportedFields.push(`${field}.kvlistValue.values[${index}]`);
|
|
818
|
+
continue;
|
|
819
|
+
}
|
|
820
|
+
out[item.key] = parseOtlpAnyValue(
|
|
821
|
+
item.value,
|
|
822
|
+
`${field}.kvlistValue.values[${index}].value`,
|
|
823
|
+
warnings,
|
|
824
|
+
unsupportedFields
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
return out;
|
|
828
|
+
}
|
|
829
|
+
if (typeof value.bytesValue === "string") {
|
|
830
|
+
unsupportedFields.push(field);
|
|
831
|
+
warnings.push({
|
|
832
|
+
code: "otlp_bytes_value_summarized",
|
|
833
|
+
message: "OTLP bytesValue attribute was summarized instead of decoded.",
|
|
834
|
+
severity: "warning",
|
|
835
|
+
field
|
|
836
|
+
});
|
|
837
|
+
return { type: "bytes", length: value.bytesValue.length };
|
|
838
|
+
}
|
|
839
|
+
unsupportedFields.push(field);
|
|
840
|
+
warnings.push({
|
|
841
|
+
code: "otlp_attribute_value_unsupported",
|
|
842
|
+
message: "OTLP attribute value used an unsupported AnyValue shape.",
|
|
843
|
+
severity: "warning",
|
|
844
|
+
field
|
|
845
|
+
});
|
|
846
|
+
return void 0;
|
|
847
|
+
}
|
|
848
|
+
function parseOtlpAttributes(value, pathPrefix) {
|
|
849
|
+
const attributes = {};
|
|
850
|
+
const warnings = [];
|
|
851
|
+
const unsupportedFields = [];
|
|
852
|
+
if (value === void 0) {
|
|
853
|
+
return { attributes, warnings, unsupportedFields };
|
|
854
|
+
}
|
|
855
|
+
if (!Array.isArray(value)) {
|
|
856
|
+
unsupportedFields.push(pathPrefix);
|
|
857
|
+
warnings.push({
|
|
858
|
+
code: "otlp_attributes_invalid",
|
|
859
|
+
message: "OTLP attributes field was not an array.",
|
|
860
|
+
severity: "warning",
|
|
861
|
+
field: pathPrefix
|
|
862
|
+
});
|
|
863
|
+
return { attributes, warnings, unsupportedFields };
|
|
864
|
+
}
|
|
865
|
+
for (const [index, item] of value.entries()) {
|
|
866
|
+
const field = `${pathPrefix}[${index}]`;
|
|
867
|
+
if (!isRecord(item) || typeof item.key !== "string") {
|
|
868
|
+
unsupportedFields.push(field);
|
|
869
|
+
warnings.push({
|
|
870
|
+
code: "otlp_attribute_invalid",
|
|
871
|
+
message: "Skipped OTLP attribute without a string key.",
|
|
872
|
+
severity: "warning",
|
|
873
|
+
field
|
|
874
|
+
});
|
|
875
|
+
continue;
|
|
876
|
+
}
|
|
877
|
+
const parsed = parseOtlpAnyValue(
|
|
878
|
+
item.value,
|
|
879
|
+
`${field}.value`,
|
|
880
|
+
warnings,
|
|
881
|
+
unsupportedFields
|
|
882
|
+
);
|
|
883
|
+
if (parsed !== void 0) {
|
|
884
|
+
attributes[item.key] = parsed;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
return { attributes, warnings, unsupportedFields };
|
|
888
|
+
}
|
|
889
|
+
function looksLikeOtlpSpan(value) {
|
|
890
|
+
return isRecord(value) && readStringField(value, ["traceId"]) !== void 0 && readStringField(value, ["spanId"]) !== void 0 && readStringField(value, ["name"]) !== void 0;
|
|
891
|
+
}
|
|
892
|
+
function extractOtlpDocument(root) {
|
|
893
|
+
if (!isRecord(root) || !Array.isArray(root.resourceSpans)) return void 0;
|
|
894
|
+
const spans = [];
|
|
895
|
+
const warnings = [];
|
|
896
|
+
const unsupportedFields = [];
|
|
897
|
+
for (const [resourceIndex, resourceSpan] of root.resourceSpans.entries()) {
|
|
898
|
+
const resourcePath = `resourceSpans[${resourceIndex}]`;
|
|
899
|
+
if (!isRecord(resourceSpan)) {
|
|
900
|
+
unsupportedFields.push(resourcePath);
|
|
901
|
+
continue;
|
|
902
|
+
}
|
|
903
|
+
const resource = readRecordField(resourceSpan, "resource");
|
|
904
|
+
const resourceParsed = parseOtlpAttributes(
|
|
905
|
+
resource?.attributes,
|
|
906
|
+
`${resourcePath}.resource.attributes`
|
|
907
|
+
);
|
|
908
|
+
warnings.push(...resourceParsed.warnings);
|
|
909
|
+
unsupportedFields.push(...resourceParsed.unsupportedFields);
|
|
910
|
+
if (!Array.isArray(resourceSpan.scopeSpans)) {
|
|
911
|
+
unsupportedFields.push(`${resourcePath}.scopeSpans`);
|
|
912
|
+
warnings.push({
|
|
913
|
+
code: "otlp_scope_spans_missing",
|
|
914
|
+
message: "OTLP resourceSpans entry did not contain a scopeSpans array.",
|
|
915
|
+
severity: "warning",
|
|
916
|
+
field: `${resourcePath}.scopeSpans`
|
|
917
|
+
});
|
|
918
|
+
continue;
|
|
919
|
+
}
|
|
920
|
+
for (const [scopeIndex, scopeSpan] of resourceSpan.scopeSpans.entries()) {
|
|
921
|
+
const scopePath = `${resourcePath}.scopeSpans[${scopeIndex}]`;
|
|
922
|
+
if (!isRecord(scopeSpan)) {
|
|
923
|
+
unsupportedFields.push(scopePath);
|
|
924
|
+
continue;
|
|
925
|
+
}
|
|
926
|
+
const scope = readRecordField(scopeSpan, "scope");
|
|
927
|
+
const scopeParsed = parseOtlpAttributes(
|
|
928
|
+
scope?.attributes,
|
|
929
|
+
`${scopePath}.scope.attributes`
|
|
930
|
+
);
|
|
931
|
+
warnings.push(...scopeParsed.warnings);
|
|
932
|
+
unsupportedFields.push(...scopeParsed.unsupportedFields);
|
|
933
|
+
if (!Array.isArray(scopeSpan.spans)) {
|
|
934
|
+
unsupportedFields.push(`${scopePath}.spans`);
|
|
935
|
+
warnings.push({
|
|
936
|
+
code: "otlp_spans_missing",
|
|
937
|
+
message: "OTLP scopeSpans entry did not contain a spans array.",
|
|
938
|
+
severity: "warning",
|
|
939
|
+
field: `${scopePath}.spans`
|
|
940
|
+
});
|
|
941
|
+
continue;
|
|
942
|
+
}
|
|
943
|
+
for (const [spanIndex, span] of scopeSpan.spans.entries()) {
|
|
944
|
+
const spanPath = `${scopePath}.spans[${spanIndex}]`;
|
|
945
|
+
if (!looksLikeOtlpSpan(span)) {
|
|
946
|
+
unsupportedFields.push(spanPath);
|
|
947
|
+
warnings.push({
|
|
948
|
+
code: "otlp_invalid_span",
|
|
949
|
+
message: "Skipped OTLP span without required traceId, spanId, or name.",
|
|
950
|
+
severity: "warning",
|
|
951
|
+
field: spanPath
|
|
952
|
+
});
|
|
953
|
+
continue;
|
|
954
|
+
}
|
|
955
|
+
spans.push({
|
|
956
|
+
span,
|
|
957
|
+
resourceAttributes: resourceParsed.attributes,
|
|
958
|
+
scopeAttributes: scopeParsed.attributes,
|
|
959
|
+
scopeName: readStringField(scope ?? {}, ["name"]),
|
|
960
|
+
scopeVersion: readStringField(scope ?? {}, ["version"]),
|
|
961
|
+
pathPrefix: spanPath
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
if (spans.length === 0) {
|
|
967
|
+
warnings.push({
|
|
968
|
+
code: "otlp_no_valid_spans",
|
|
969
|
+
message: "OTLP JSON payload did not contain any valid spans.",
|
|
970
|
+
severity: "error"
|
|
971
|
+
});
|
|
972
|
+
return {
|
|
973
|
+
spans,
|
|
974
|
+
confidence: 0.7,
|
|
975
|
+
description: "Malformed OTLP JSON trace payload",
|
|
976
|
+
warnings,
|
|
977
|
+
unsupportedFields
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
return {
|
|
981
|
+
spans,
|
|
982
|
+
confidence: 0.93,
|
|
983
|
+
description: "OTLP JSON trace payload",
|
|
984
|
+
warnings,
|
|
985
|
+
unsupportedFields
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
function mapOtlpStatus(status) {
|
|
989
|
+
if (!isRecord(status)) return void 0;
|
|
990
|
+
const rawCode = status.code;
|
|
991
|
+
if (typeof rawCode !== "string") return void 0;
|
|
992
|
+
switch (rawCode.toUpperCase()) {
|
|
993
|
+
case "STATUS_CODE_OK":
|
|
994
|
+
case "OK":
|
|
995
|
+
return "ok";
|
|
996
|
+
case "STATUS_CODE_ERROR":
|
|
997
|
+
case "ERROR":
|
|
998
|
+
return "error";
|
|
999
|
+
case "STATUS_CODE_UNSET":
|
|
1000
|
+
case "UNSET":
|
|
1001
|
+
return "unknown";
|
|
1002
|
+
default:
|
|
1003
|
+
return "unknown";
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
function readOtlpKind(attributes, pathPrefix) {
|
|
1007
|
+
const warnings = [];
|
|
1008
|
+
const agentInspectKind = attributes["agent_inspect.kind"];
|
|
1009
|
+
if (agentInspectKind === "RUN" || agentInspectKind === "AGENT" || agentInspectKind === "LLM" || agentInspectKind === "TOOL" || agentInspectKind === "CHAIN" || agentInspectKind === "RETRIEVER" || agentInspectKind === "DECISION" || agentInspectKind === "RESULT" || agentInspectKind === "ERROR" || agentInspectKind === "LOGIC" || agentInspectKind === "LOG") {
|
|
1010
|
+
return { kind: agentInspectKind, warnings };
|
|
1011
|
+
}
|
|
1012
|
+
const operation = attributes["gen_ai.operation.name"];
|
|
1013
|
+
if (typeof operation === "string") {
|
|
1014
|
+
switch (operation) {
|
|
1015
|
+
case "generate_content":
|
|
1016
|
+
case "chat":
|
|
1017
|
+
return { kind: "LLM", warnings };
|
|
1018
|
+
case "execute_tool":
|
|
1019
|
+
return { kind: "TOOL", warnings };
|
|
1020
|
+
case "invoke_agent":
|
|
1021
|
+
return { kind: "AGENT", warnings };
|
|
1022
|
+
default:
|
|
1023
|
+
warnings.push({
|
|
1024
|
+
code: "otlp_gen_ai_operation_semantic_loss",
|
|
1025
|
+
message: `OTLP GenAI operation "${operation}" mapped to AgentInspect LOGIC.`,
|
|
1026
|
+
severity: "warning",
|
|
1027
|
+
field: `${pathPrefix}.attributes.gen_ai.operation.name`
|
|
1028
|
+
});
|
|
1029
|
+
return { kind: "LOGIC", warnings };
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
warnings.push({
|
|
1033
|
+
code: "otlp_kind_unknown",
|
|
1034
|
+
message: "OTLP span had no AgentInspect kind or GenAI operation; mapped to LOGIC.",
|
|
1035
|
+
severity: "warning",
|
|
1036
|
+
field: `${pathPrefix}.attributes`
|
|
1037
|
+
});
|
|
1038
|
+
return { kind: "LOGIC", warnings };
|
|
1039
|
+
}
|
|
1040
|
+
function readOtlpTokenUsage(attributes) {
|
|
1041
|
+
const input = attributes["gen_ai.usage.input_tokens"];
|
|
1042
|
+
const output = attributes["gen_ai.usage.output_tokens"];
|
|
1043
|
+
const usage = {};
|
|
1044
|
+
if (typeof input === "number" && Number.isFinite(input) && input >= 0) {
|
|
1045
|
+
usage.input = input;
|
|
1046
|
+
}
|
|
1047
|
+
if (typeof output === "number" && Number.isFinite(output) && output >= 0) {
|
|
1048
|
+
usage.output = output;
|
|
1049
|
+
}
|
|
1050
|
+
if (usage.input !== void 0 && usage.output !== void 0) {
|
|
1051
|
+
usage.total = usage.input + usage.output;
|
|
1052
|
+
}
|
|
1053
|
+
return Object.keys(usage).length > 0 ? usage : void 0;
|
|
1054
|
+
}
|
|
1055
|
+
function readOtlpConfidence(attributes) {
|
|
1056
|
+
return readOpenInferenceConfidence(attributes);
|
|
1057
|
+
}
|
|
1058
|
+
function sanitizeOtlpAttributes(attributes, pathPrefix) {
|
|
1059
|
+
const ownerPath = pathPrefix.endsWith(".attributes") ? pathPrefix.slice(0, -".attributes".length) : pathPrefix;
|
|
1060
|
+
const sanitized = sanitizeOpenInferenceAttributes(attributes, ownerPath);
|
|
1061
|
+
return {
|
|
1062
|
+
...sanitized,
|
|
1063
|
+
warnings: sanitized.warnings.map(
|
|
1064
|
+
(warning) => warning.code === "openinference_sensitive_attribute_summarized" ? {
|
|
1065
|
+
...warning,
|
|
1066
|
+
code: "otlp_sensitive_attribute_summarized",
|
|
1067
|
+
message: "OTLP prompt/output/document attribute(s) were summarized instead of copied verbatim."
|
|
1068
|
+
} : warning
|
|
1069
|
+
)
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
function mapOtlpEvents(value, pathPrefix) {
|
|
1073
|
+
const warnings = [];
|
|
1074
|
+
const unsupportedFields = [];
|
|
1075
|
+
if (value === void 0) return { warnings, unsupportedFields };
|
|
1076
|
+
if (!Array.isArray(value)) {
|
|
1077
|
+
unsupportedFields.push(pathPrefix);
|
|
1078
|
+
warnings.push({
|
|
1079
|
+
code: "otlp_events_invalid",
|
|
1080
|
+
message: "OTLP events field was not an array.",
|
|
1081
|
+
severity: "warning",
|
|
1082
|
+
field: pathPrefix
|
|
1083
|
+
});
|
|
1084
|
+
return { warnings, unsupportedFields };
|
|
1085
|
+
}
|
|
1086
|
+
const events = [];
|
|
1087
|
+
for (const [index, event] of value.entries()) {
|
|
1088
|
+
const eventPath = `${pathPrefix}[${index}]`;
|
|
1089
|
+
if (!isRecord(event)) {
|
|
1090
|
+
unsupportedFields.push(eventPath);
|
|
1091
|
+
continue;
|
|
1092
|
+
}
|
|
1093
|
+
const parsedAttributes = parseOtlpAttributes(
|
|
1094
|
+
event.attributes,
|
|
1095
|
+
`${eventPath}.attributes`
|
|
1096
|
+
);
|
|
1097
|
+
warnings.push(...parsedAttributes.warnings);
|
|
1098
|
+
unsupportedFields.push(...parsedAttributes.unsupportedFields);
|
|
1099
|
+
const sanitized = sanitizeOtlpAttributes(
|
|
1100
|
+
parsedAttributes.attributes,
|
|
1101
|
+
`${eventPath}.attributes`
|
|
1102
|
+
);
|
|
1103
|
+
warnings.push(...sanitized.warnings);
|
|
1104
|
+
unsupportedFields.push(...sanitized.unsupportedFields);
|
|
1105
|
+
const out = {};
|
|
1106
|
+
const name = readStringField(event, ["name"]);
|
|
1107
|
+
if (name !== void 0) {
|
|
1108
|
+
out.name = name;
|
|
1109
|
+
}
|
|
1110
|
+
const timestamp = parseUnixNanoToIso(event.timeUnixNano);
|
|
1111
|
+
if (timestamp !== void 0) {
|
|
1112
|
+
out.timestamp = timestamp;
|
|
1113
|
+
} else if (event.timeUnixNano !== void 0) {
|
|
1114
|
+
unsupportedFields.push(`${eventPath}.timeUnixNano`);
|
|
1115
|
+
warnings.push({
|
|
1116
|
+
code: "otlp_event_timestamp_invalid",
|
|
1117
|
+
message: "OTLP event timeUnixNano could not be parsed.",
|
|
1118
|
+
severity: "warning",
|
|
1119
|
+
field: `${eventPath}.timeUnixNano`
|
|
1120
|
+
});
|
|
1121
|
+
}
|
|
1122
|
+
if (Object.keys(sanitized.attributes).length > 0) {
|
|
1123
|
+
out.attributes = sanitized.attributes;
|
|
1124
|
+
}
|
|
1125
|
+
events.push(out);
|
|
1126
|
+
}
|
|
1127
|
+
return {
|
|
1128
|
+
events: events.length > 0 ? events : void 0,
|
|
1129
|
+
warnings,
|
|
1130
|
+
unsupportedFields
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
function mapOtlpSpan(context) {
|
|
1134
|
+
const { span, pathPrefix } = context;
|
|
1135
|
+
const warnings = [];
|
|
1136
|
+
const unsupportedFields = [];
|
|
1137
|
+
const parsedSpanAttributes = parseOtlpAttributes(
|
|
1138
|
+
span.attributes,
|
|
1139
|
+
`${pathPrefix}.attributes`
|
|
1140
|
+
);
|
|
1141
|
+
warnings.push(...parsedSpanAttributes.warnings);
|
|
1142
|
+
unsupportedFields.push(...parsedSpanAttributes.unsupportedFields);
|
|
1143
|
+
const sanitizedSpanAttributes = sanitizeOtlpAttributes(
|
|
1144
|
+
parsedSpanAttributes.attributes,
|
|
1145
|
+
`${pathPrefix}.attributes`
|
|
1146
|
+
);
|
|
1147
|
+
warnings.push(...sanitizedSpanAttributes.warnings);
|
|
1148
|
+
unsupportedFields.push(...sanitizedSpanAttributes.unsupportedFields);
|
|
1149
|
+
const attributes = {
|
|
1150
|
+
...sanitizedSpanAttributes.attributes
|
|
1151
|
+
};
|
|
1152
|
+
for (const [key, value] of Object.entries(context.resourceAttributes)) {
|
|
1153
|
+
attributes[`resource.${key}`] = value;
|
|
1154
|
+
}
|
|
1155
|
+
for (const [key, value] of Object.entries(context.scopeAttributes)) {
|
|
1156
|
+
attributes[`scope.${key}`] = value;
|
|
1157
|
+
}
|
|
1158
|
+
if (context.scopeName !== void 0) {
|
|
1159
|
+
attributes["scope.name"] = context.scopeName;
|
|
1160
|
+
}
|
|
1161
|
+
if (context.scopeVersion !== void 0) {
|
|
1162
|
+
attributes["scope.version"] = context.scopeVersion;
|
|
1163
|
+
}
|
|
1164
|
+
for (const [key, value] of Object.entries(span)) {
|
|
1165
|
+
if (OTLP_SPAN_KEYS.has(key)) continue;
|
|
1166
|
+
unsupportedFields.push(`${pathPrefix}.${key}`);
|
|
1167
|
+
if (value === null || typeof value !== "object") {
|
|
1168
|
+
attributes[`otlp.${key}`] = value;
|
|
1169
|
+
} else {
|
|
1170
|
+
attributes[`otlp.${key}.summary`] = summarizeAttributeValue(value);
|
|
1171
|
+
warnings.push({
|
|
1172
|
+
code: "otlp_unsupported_field_summarized",
|
|
1173
|
+
message: `Unsupported OTLP span field "${key}" was summarized.`,
|
|
1174
|
+
severity: "warning",
|
|
1175
|
+
field: `${pathPrefix}.${key}`
|
|
1176
|
+
});
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
for (const key of [
|
|
1180
|
+
"droppedAttributesCount",
|
|
1181
|
+
"droppedEventsCount",
|
|
1182
|
+
"droppedLinksCount",
|
|
1183
|
+
"links"
|
|
1184
|
+
]) {
|
|
1185
|
+
if (span[key] !== void 0) {
|
|
1186
|
+
unsupportedFields.push(`${pathPrefix}.${key}`);
|
|
1187
|
+
warnings.push({
|
|
1188
|
+
code: "otlp_span_field_not_mapped",
|
|
1189
|
+
message: `OTLP span field "${key}" is not represented in AgentInspect events.`,
|
|
1190
|
+
severity: "warning",
|
|
1191
|
+
field: `${pathPrefix}.${key}`
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
const events = mapOtlpEvents(span.events, `${pathPrefix}.events`);
|
|
1196
|
+
warnings.push(...events.warnings);
|
|
1197
|
+
unsupportedFields.push(...events.unsupportedFields);
|
|
1198
|
+
if (events.events !== void 0) {
|
|
1199
|
+
attributes["otlp.events"] = events.events;
|
|
1200
|
+
}
|
|
1201
|
+
const traceId = readStringField(span, ["traceId"]) ?? "trace-unknown";
|
|
1202
|
+
const spanId = readStringField(span, ["spanId"]) ?? "span-unknown";
|
|
1203
|
+
const parentSpanId = readStringField(span, ["parentSpanId"]);
|
|
1204
|
+
const startedAt = readOpenInferenceTimestamp(
|
|
1205
|
+
span,
|
|
1206
|
+
["startTimeUnixNano"],
|
|
1207
|
+
[]
|
|
1208
|
+
);
|
|
1209
|
+
const endedAt = readOpenInferenceTimestamp(span, ["endTimeUnixNano"], []);
|
|
1210
|
+
const timestamp = startedAt ?? "1970-01-01T00:00:00.000Z";
|
|
1211
|
+
if (startedAt === void 0) {
|
|
1212
|
+
unsupportedFields.push(`${pathPrefix}.startTimeUnixNano`);
|
|
1213
|
+
warnings.push({
|
|
1214
|
+
code: "otlp_missing_start_time",
|
|
1215
|
+
message: "OTLP span is missing a valid startTimeUnixNano; using Unix epoch.",
|
|
1216
|
+
severity: "warning",
|
|
1217
|
+
field: `${pathPrefix}.startTimeUnixNano`
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
const { kind, warnings: kindWarnings } = readOtlpKind(
|
|
1221
|
+
parsedSpanAttributes.attributes,
|
|
1222
|
+
pathPrefix
|
|
1223
|
+
);
|
|
1224
|
+
warnings.push(...kindWarnings);
|
|
1225
|
+
const status = mapOtlpStatus(span.status);
|
|
1226
|
+
const tokenUsage = readOtlpTokenUsage(parsedSpanAttributes.attributes);
|
|
1227
|
+
const errorMessage = isRecord(span.status) && typeof span.status.message === "string" ? span.status.message : void 0;
|
|
1228
|
+
const event = {
|
|
1229
|
+
schemaVersion: "0.2",
|
|
1230
|
+
eventId: typeof parsedSpanAttributes.attributes["agent_inspect.event_id"] === "string" ? parsedSpanAttributes.attributes["agent_inspect.event_id"] : spanId,
|
|
1231
|
+
runId: typeof parsedSpanAttributes.attributes["agent_inspect.run_id"] === "string" ? parsedSpanAttributes.attributes["agent_inspect.run_id"] : traceId,
|
|
1232
|
+
kind,
|
|
1233
|
+
name: readStringField(span, ["name"]) ?? spanId,
|
|
1234
|
+
timestamp,
|
|
1235
|
+
confidence: readOtlpConfidence(parsedSpanAttributes.attributes),
|
|
1236
|
+
source: {
|
|
1237
|
+
type: "otel",
|
|
1238
|
+
name: context.scopeName ?? (typeof context.resourceAttributes["service.name"] === "string" ? context.resourceAttributes["service.name"] : "otlp-json"),
|
|
1239
|
+
...context.scopeVersion !== void 0 ? { version: context.scopeVersion } : {}
|
|
1240
|
+
},
|
|
1241
|
+
attributes,
|
|
1242
|
+
trace: {
|
|
1243
|
+
traceId,
|
|
1244
|
+
spanId,
|
|
1245
|
+
...parentSpanId !== void 0 ? { parentSpanId } : {}
|
|
1246
|
+
}
|
|
1247
|
+
};
|
|
1248
|
+
if (status !== void 0) {
|
|
1249
|
+
event.status = status;
|
|
1250
|
+
}
|
|
1251
|
+
if (startedAt !== void 0) {
|
|
1252
|
+
event.startedAt = startedAt;
|
|
1253
|
+
}
|
|
1254
|
+
if (endedAt !== void 0) {
|
|
1255
|
+
event.endedAt = endedAt;
|
|
1256
|
+
}
|
|
1257
|
+
const durationMs = durationBetweenIso(startedAt, endedAt);
|
|
1258
|
+
if (durationMs !== void 0) {
|
|
1259
|
+
event.durationMs = durationMs;
|
|
1260
|
+
}
|
|
1261
|
+
if (tokenUsage !== void 0) {
|
|
1262
|
+
event.tokenUsage = tokenUsage;
|
|
1263
|
+
}
|
|
1264
|
+
if (status === "error") {
|
|
1265
|
+
event.error = {
|
|
1266
|
+
message: errorMessage !== void 0 && errorMessage.trim() !== "" ? errorMessage : "OTLP span error"
|
|
1267
|
+
};
|
|
1268
|
+
}
|
|
1269
|
+
return {
|
|
1270
|
+
event,
|
|
1271
|
+
warnings,
|
|
1272
|
+
unsupportedFields,
|
|
1273
|
+
spanId,
|
|
1274
|
+
...parentSpanId !== void 0 ? { parentSpanId } : {}
|
|
1275
|
+
};
|
|
1276
|
+
}
|
|
1277
|
+
function mapOtlpEventsToPersisted(document) {
|
|
1278
|
+
const mapped = document.spans.map((span) => mapOtlpSpan(span));
|
|
1279
|
+
const spanIdToEventId = new Map(
|
|
1280
|
+
mapped.map((span) => [span.spanId, span.event.eventId])
|
|
1281
|
+
);
|
|
1282
|
+
for (const span of mapped) {
|
|
1283
|
+
if (span.parentSpanId === void 0) continue;
|
|
1284
|
+
span.event.parentId = spanIdToEventId.get(span.parentSpanId) ?? span.parentSpanId;
|
|
1285
|
+
}
|
|
1286
|
+
return {
|
|
1287
|
+
events: mapped.map((span) => span.event),
|
|
1288
|
+
warnings: mapped.flatMap((span) => span.warnings),
|
|
1289
|
+
unsupportedFields: mapped.flatMap((span) => span.unsupportedFields)
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
var otlpJsonReader = {
|
|
1293
|
+
format: OTLP_READER_FORMAT,
|
|
1294
|
+
name: "OTLP JSON",
|
|
1295
|
+
async detect(input) {
|
|
1296
|
+
const resolved = await resolveInput(input);
|
|
1297
|
+
if (!resolved) return void 0;
|
|
1298
|
+
let parsed;
|
|
1299
|
+
try {
|
|
1300
|
+
parsed = parseJsonDocument(resolved.content);
|
|
1301
|
+
} catch {
|
|
1302
|
+
return void 0;
|
|
1303
|
+
}
|
|
1304
|
+
const document = extractOtlpDocument(parsed);
|
|
1305
|
+
if (!document) return void 0;
|
|
1306
|
+
return {
|
|
1307
|
+
format: OTLP_READER_FORMAT,
|
|
1308
|
+
confidence: document.confidence,
|
|
1309
|
+
readerName: "OTLP JSON",
|
|
1310
|
+
description: document.description,
|
|
1311
|
+
warnings: attachSingleSourceFile(document.warnings, resolved)
|
|
1312
|
+
};
|
|
1313
|
+
},
|
|
1314
|
+
async read(input) {
|
|
1315
|
+
const resolved = await resolveInput(input);
|
|
1316
|
+
if (!resolved) {
|
|
1317
|
+
throw new TraceReadError(
|
|
1318
|
+
"unsupported_format",
|
|
1319
|
+
"OTLP JSON reader requires file, string, or buffer input."
|
|
1320
|
+
);
|
|
1321
|
+
}
|
|
1322
|
+
let parsed;
|
|
1323
|
+
try {
|
|
1324
|
+
parsed = parseJsonDocument(resolved.content);
|
|
1325
|
+
} catch {
|
|
1326
|
+
throw new TraceReadError("unsupported_format", "OTLP JSON input is not valid JSON.", [
|
|
1327
|
+
{
|
|
1328
|
+
code: "otlp_invalid_json",
|
|
1329
|
+
message: "OTLP JSON reader could not parse the input as JSON.",
|
|
1330
|
+
severity: "error"
|
|
1331
|
+
}
|
|
1332
|
+
]);
|
|
1333
|
+
}
|
|
1334
|
+
const document = extractOtlpDocument(parsed);
|
|
1335
|
+
if (!document || document.spans.length === 0) {
|
|
1336
|
+
throw new TraceReadError(
|
|
1337
|
+
"unsupported_format",
|
|
1338
|
+
"No valid OTLP spans found.",
|
|
1339
|
+
attachSingleSourceFile(
|
|
1340
|
+
document?.warnings ?? [
|
|
1341
|
+
{
|
|
1342
|
+
code: "otlp_no_valid_spans",
|
|
1343
|
+
message: "OTLP JSON input did not contain valid spans.",
|
|
1344
|
+
severity: "error"
|
|
1345
|
+
}
|
|
1346
|
+
],
|
|
1347
|
+
resolved
|
|
1348
|
+
)
|
|
1349
|
+
);
|
|
1350
|
+
}
|
|
1351
|
+
const mapped = mapOtlpEventsToPersisted(document);
|
|
1352
|
+
const warnings = attachSingleSourceFile(
|
|
1353
|
+
[...document.warnings, ...mapped.warnings],
|
|
1354
|
+
resolved
|
|
1355
|
+
);
|
|
1356
|
+
const unsupportedFields = [
|
|
1357
|
+
...document.unsupportedFields,
|
|
1358
|
+
...mapped.unsupportedFields
|
|
1359
|
+
].sort((a, b) => a.localeCompare(b));
|
|
1360
|
+
return {
|
|
1361
|
+
format: OTLP_READER_FORMAT,
|
|
1362
|
+
events: mapped.events,
|
|
1363
|
+
runs: persistedInspectEventsToRunTrees(mapped.events, { skipInvalid: true }),
|
|
1364
|
+
warnings,
|
|
1365
|
+
unsupportedFields,
|
|
1366
|
+
sourceFiles: resolved.sourceFiles
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1369
|
+
};
|
|
1370
|
+
var agentInspectJsonlReader = {
|
|
1371
|
+
format: "agent-inspect-jsonl",
|
|
1372
|
+
name: "AgentInspect JSONL",
|
|
1373
|
+
async detect(input) {
|
|
1374
|
+
const resolved = await resolveInput(input);
|
|
1375
|
+
if (!resolved) return void 0;
|
|
1376
|
+
const detected = detectJsonlFormat(resolved.content);
|
|
1377
|
+
if (detected.validRows === 0 || detected.format === "empty") {
|
|
1378
|
+
return void 0;
|
|
1379
|
+
}
|
|
1380
|
+
return {
|
|
1381
|
+
format: "agent-inspect-jsonl",
|
|
1382
|
+
confidence: 0.95,
|
|
1383
|
+
readerName: "AgentInspect JSONL",
|
|
1384
|
+
description: agentInspectFormatLabel(detected.format),
|
|
1385
|
+
warnings: attachSingleSourceFile(detected.warnings, resolved)
|
|
1386
|
+
};
|
|
1387
|
+
},
|
|
1388
|
+
async read(input) {
|
|
1389
|
+
const resolved = await resolveInput(input);
|
|
1390
|
+
if (!resolved) {
|
|
1391
|
+
throw new Error("AgentInspect JSONL reader requires file, directory, string, or buffer input.");
|
|
1392
|
+
}
|
|
1393
|
+
const parsed = parseTraceJsonl(resolved.content, { warnings: false });
|
|
1394
|
+
if (parsed.sourceEventCount === 0) {
|
|
1395
|
+
throw new Error("No valid AgentInspect JSONL events found.");
|
|
1396
|
+
}
|
|
1397
|
+
const events = persistedEventsForParsedTrace(parsed);
|
|
1398
|
+
return {
|
|
1399
|
+
format: agentInspectFormatLabel(parsed.format),
|
|
1400
|
+
events,
|
|
1401
|
+
runs: persistedInspectEventsToRunTrees(events, { skipInvalid: true }),
|
|
1402
|
+
warnings: parsed.format === "mixed" ? attachSingleSourceFile(
|
|
1403
|
+
[
|
|
1404
|
+
{
|
|
1405
|
+
code: "mixed_agent_inspect_jsonl",
|
|
1406
|
+
message: "Trace input mixes schemaVersion 0.1 and 0.2 rows; events were normalized for reading.",
|
|
1407
|
+
severity: "warning"
|
|
1408
|
+
}
|
|
1409
|
+
],
|
|
1410
|
+
resolved
|
|
1411
|
+
) : [],
|
|
1412
|
+
unsupportedFields: [],
|
|
1413
|
+
sourceFiles: resolved.sourceFiles
|
|
1414
|
+
};
|
|
1415
|
+
}
|
|
1416
|
+
};
|
|
1417
|
+
var DEFAULT_TRACE_READERS = [
|
|
1418
|
+
agentInspectJsonlReader,
|
|
1419
|
+
openInferenceJsonReader,
|
|
1420
|
+
otlpJsonReader
|
|
1421
|
+
];
|
|
1422
|
+
async function detectTraceFormat(input, options = {}) {
|
|
1423
|
+
const readers = options.readers ?? DEFAULT_TRACE_READERS;
|
|
1424
|
+
if (options.format !== void 0) {
|
|
1425
|
+
const reader = findReaderByFormat(options.format, readers);
|
|
1426
|
+
if (!reader) {
|
|
1427
|
+
return {
|
|
1428
|
+
status: "unsupported",
|
|
1429
|
+
candidates: [],
|
|
1430
|
+
warnings: [
|
|
1431
|
+
{
|
|
1432
|
+
code: "unsupported_format",
|
|
1433
|
+
message: `No trace reader is registered for format "${options.format}".`,
|
|
1434
|
+
severity: "error"
|
|
1435
|
+
}
|
|
1436
|
+
]
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1439
|
+
return {
|
|
1440
|
+
status: "detected",
|
|
1441
|
+
format: reader.format,
|
|
1442
|
+
candidates: [
|
|
1443
|
+
{
|
|
1444
|
+
format: reader.format,
|
|
1445
|
+
confidence: 1,
|
|
1446
|
+
readerName: reader.name,
|
|
1447
|
+
description: "Explicit format override"
|
|
1448
|
+
}
|
|
1449
|
+
],
|
|
1450
|
+
warnings: []
|
|
1451
|
+
};
|
|
1452
|
+
}
|
|
1453
|
+
const candidates = [];
|
|
1454
|
+
const warnings = [];
|
|
1455
|
+
for (const reader of readers) {
|
|
1456
|
+
try {
|
|
1457
|
+
const candidate = await reader.detect(input);
|
|
1458
|
+
if (candidate !== void 0) {
|
|
1459
|
+
candidates.push(normalizeCandidate(reader, candidate));
|
|
1460
|
+
}
|
|
1461
|
+
} catch (error) {
|
|
1462
|
+
if (error instanceof TraceReadError) {
|
|
1463
|
+
warnings.push(...error.warnings);
|
|
1464
|
+
continue;
|
|
1465
|
+
}
|
|
1466
|
+
warnings.push({
|
|
1467
|
+
code: "reader_detect_failed",
|
|
1468
|
+
message: error instanceof Error && error.message.trim() !== "" ? error.message : `Trace reader "${reader.format}" failed during detection.`,
|
|
1469
|
+
severity: "warning"
|
|
1470
|
+
});
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
const sorted = sortCandidates(
|
|
1474
|
+
candidates.filter((candidate) => candidate.confidence >= MIN_DETECTION_CONFIDENCE)
|
|
1475
|
+
);
|
|
1476
|
+
const candidateWarnings = collectWarnings(sorted);
|
|
1477
|
+
const lowConfidenceWarnings = candidates.length > sorted.length ? [
|
|
1478
|
+
{
|
|
1479
|
+
code: "low_confidence_candidates",
|
|
1480
|
+
message: `Ignored ${candidates.length - sorted.length} low-confidence format candidate(s).`,
|
|
1481
|
+
severity: "info"
|
|
1482
|
+
}
|
|
1483
|
+
] : [];
|
|
1484
|
+
const allWarnings = dedupeWarnings([
|
|
1485
|
+
...warnings,
|
|
1486
|
+
...candidateWarnings,
|
|
1487
|
+
...lowConfidenceWarnings
|
|
1488
|
+
]);
|
|
1489
|
+
if (sorted.length === 0) {
|
|
1490
|
+
return {
|
|
1491
|
+
status: "unsupported",
|
|
1492
|
+
candidates: [],
|
|
1493
|
+
warnings: allWarnings
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1496
|
+
const [best, second] = sorted;
|
|
1497
|
+
if (second !== void 0 && best.confidence - second.confidence <= AMBIGUOUS_CONFIDENCE_DELTA) {
|
|
1498
|
+
return {
|
|
1499
|
+
status: "ambiguous",
|
|
1500
|
+
candidates: sorted,
|
|
1501
|
+
warnings: [
|
|
1502
|
+
...allWarnings,
|
|
1503
|
+
{
|
|
1504
|
+
code: "ambiguous_format_candidates",
|
|
1505
|
+
message: `Top trace format candidates are within ${AMBIGUOUS_CONFIDENCE_DELTA} confidence.`,
|
|
1506
|
+
severity: "warning"
|
|
1507
|
+
}
|
|
1508
|
+
]
|
|
1509
|
+
};
|
|
1510
|
+
}
|
|
1511
|
+
return {
|
|
1512
|
+
status: "detected",
|
|
1513
|
+
format: best.format,
|
|
1514
|
+
candidates: sorted,
|
|
1515
|
+
warnings: allWarnings
|
|
1516
|
+
};
|
|
1517
|
+
}
|
|
1518
|
+
async function readTrace(input, options = {}) {
|
|
1519
|
+
const readers = options.readers ?? DEFAULT_TRACE_READERS;
|
|
1520
|
+
const detection = await detectTraceFormat(input, options);
|
|
1521
|
+
if (detection.status === "unsupported" || detection.format === void 0) {
|
|
1522
|
+
throw new TraceReadError(
|
|
1523
|
+
"unsupported_format",
|
|
1524
|
+
"No trace reader could detect the input format.",
|
|
1525
|
+
detection.warnings
|
|
1526
|
+
);
|
|
1527
|
+
}
|
|
1528
|
+
if (detection.status === "ambiguous") {
|
|
1529
|
+
throw new TraceReadError(
|
|
1530
|
+
"ambiguous_format",
|
|
1531
|
+
"Multiple trace readers matched the input with equal confidence.",
|
|
1532
|
+
detection.warnings
|
|
1533
|
+
);
|
|
1534
|
+
}
|
|
1535
|
+
const reader = findReaderByFormat(detection.format, readers);
|
|
1536
|
+
if (!reader) {
|
|
1537
|
+
throw new TraceReadError(
|
|
1538
|
+
"unsupported_format",
|
|
1539
|
+
`No trace reader is registered for format "${detection.format}".`,
|
|
1540
|
+
detection.warnings
|
|
1541
|
+
);
|
|
1542
|
+
}
|
|
1543
|
+
try {
|
|
1544
|
+
const result = await reader.read(input, { format: detection.format });
|
|
1545
|
+
return {
|
|
1546
|
+
...result,
|
|
1547
|
+
format: result.format || detection.format,
|
|
1548
|
+
warnings: [...detection.warnings, ...result.warnings]
|
|
1549
|
+
};
|
|
1550
|
+
} catch (error) {
|
|
1551
|
+
if (error instanceof TraceReadError) {
|
|
1552
|
+
throw new TraceReadError(
|
|
1553
|
+
error.code,
|
|
1554
|
+
error.message,
|
|
1555
|
+
dedupeWarnings([...detection.warnings, ...error.warnings])
|
|
1556
|
+
);
|
|
1557
|
+
}
|
|
1558
|
+
throw new TraceReadError(
|
|
1559
|
+
"reader_failed",
|
|
1560
|
+
error instanceof Error && error.message.trim() !== "" ? error.message : `Trace reader "${reader.format}" failed.`,
|
|
1561
|
+
detection.warnings
|
|
1562
|
+
);
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
function openTrace(input, options = {}) {
|
|
1566
|
+
return readTrace(input, options);
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
export { DEFAULT_TRACE_READERS, TraceReadError, agentInspectJsonlReader, detectTraceFormat, openInferenceJsonReader, openTrace, otlpJsonReader, readTrace };
|
|
1570
|
+
//# sourceMappingURL=chunk-TFLPUZ56.mjs.map
|
|
1571
|
+
//# sourceMappingURL=chunk-TFLPUZ56.mjs.map
|