agent-inspect 4.4.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/docs/CLI.md +34 -0
- package/package.json +1 -1
- package/packages/cli/dist/chunk-TB45H3QT.mjs +7881 -0
- package/packages/cli/dist/chunk-TB45H3QT.mjs.map +1 -0
- package/packages/cli/dist/index.cjs +10295 -9414
- package/packages/cli/dist/index.cjs.map +1 -1
- package/packages/cli/dist/index.mjs +4756 -7991
- package/packages/cli/dist/index.mjs.map +1 -1
- package/packages/cli/dist/{src-FJOHPC4X.mjs → src-ZRFSYML6.mjs} +3 -3
- package/packages/cli/dist/{src-FJOHPC4X.mjs.map → src-ZRFSYML6.mjs.map} +1 -1
- package/packages/core/dist/advanced.cjs +3210 -13
- package/packages/core/dist/advanced.cjs.map +1 -1
- package/packages/core/dist/advanced.d.cts +135 -1
- package/packages/core/dist/advanced.d.ts +135 -1
- package/packages/core/dist/advanced.mjs +575 -9
- package/packages/core/dist/advanced.mjs.map +1 -1
- package/packages/core/dist/checks.mjs +2 -1648
- package/packages/core/dist/checks.mjs.map +1 -1
- package/packages/core/dist/chunk-QLYOCCEF.mjs +1650 -0
- package/packages/core/dist/chunk-QLYOCCEF.mjs.map +1 -0
- package/packages/core/dist/chunk-X2FLDF7M.mjs +1581 -0
- package/packages/core/dist/chunk-X2FLDF7M.mjs.map +1 -0
- package/packages/core/dist/readers.mjs +3 -1579
- package/packages/core/dist/readers.mjs.map +1 -1
- package/packages/cli/dist/chunk-ORHDQUT6.mjs +0 -3862
- package/packages/cli/dist/chunk-ORHDQUT6.mjs.map +0 -1
|
@@ -9,6 +9,7 @@ var process2 = require('process');
|
|
|
9
9
|
var tty = require('tty');
|
|
10
10
|
var fs = require('fs');
|
|
11
11
|
var readline = require('readline');
|
|
12
|
+
var url = require('url');
|
|
12
13
|
|
|
13
14
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
14
15
|
|
|
@@ -123,15 +124,15 @@ var Redactor = class {
|
|
|
123
124
|
return this.#redactNested(value);
|
|
124
125
|
}
|
|
125
126
|
if (rule.strategy === "full") return "[REDACTED]";
|
|
126
|
-
const
|
|
127
|
+
const asString2 = typeof value === "string" ? value : typeof value === "number" || typeof value === "boolean" || typeof value === "bigint" ? String(value) : void 0;
|
|
127
128
|
if (rule.strategy === "prefix") {
|
|
128
|
-
if (
|
|
129
|
+
if (asString2 === void 0) return "[REDACTED]";
|
|
129
130
|
const keep = Math.max(0, Math.floor(rule.keep));
|
|
130
|
-
return
|
|
131
|
+
return asString2.length <= keep ? `${asString2}\u2026` : `${asString2.slice(0, keep)}\u2026`;
|
|
131
132
|
}
|
|
132
133
|
if (rule.strategy === "hash") {
|
|
133
|
-
if (
|
|
134
|
-
return `[HASH:${stableHash(
|
|
134
|
+
if (asString2 === void 0) return "[HASH:unknown]";
|
|
135
|
+
return `[HASH:${stableHash(asString2)}]`;
|
|
135
136
|
}
|
|
136
137
|
return this.#redactNested(value);
|
|
137
138
|
}
|
|
@@ -2011,6 +2012,10 @@ var OBSERVED_OUTCOME_METHODS = [
|
|
|
2011
2012
|
"queue",
|
|
2012
2013
|
"custom"
|
|
2013
2014
|
];
|
|
2015
|
+
var OUTCOME_ATTRIBUTE_STATUS_KEY = "outcomeStatus";
|
|
2016
|
+
var OUTCOME_ATTRIBUTE_EXPECTATION_KEY = "expectation";
|
|
2017
|
+
var OUTCOME_ATTRIBUTE_METHOD_KEY = "method";
|
|
2018
|
+
var OUTCOME_ATTRIBUTE_OBSERVED_AT_KEY = "observedAt";
|
|
2014
2019
|
var OUTCOME_LEGACY_EVENT = "outcome_observed";
|
|
2015
2020
|
|
|
2016
2021
|
// packages/core/src/outcomes/validate.ts
|
|
@@ -2067,6 +2072,13 @@ function normalizeObserveOutcomeInput(name, options, nowMs = Date.now()) {
|
|
|
2067
2072
|
}
|
|
2068
2073
|
|
|
2069
2074
|
// packages/core/src/outcomes/extract.ts
|
|
2075
|
+
function isOutcomeStatus(value) {
|
|
2076
|
+
return value === "passed" || value === "failed" || value === "unknown" || value === "skipped";
|
|
2077
|
+
}
|
|
2078
|
+
function parseMethod2(value) {
|
|
2079
|
+
if (typeof value !== "string" || value.trim() === "") return void 0;
|
|
2080
|
+
return value;
|
|
2081
|
+
}
|
|
2070
2082
|
function fromOutcomeObservedEvent(event) {
|
|
2071
2083
|
return {
|
|
2072
2084
|
outcomeId: event.outcomeId,
|
|
@@ -2081,6 +2093,27 @@ function fromOutcomeObservedEvent(event) {
|
|
|
2081
2093
|
observedAt: event.observedAt
|
|
2082
2094
|
};
|
|
2083
2095
|
}
|
|
2096
|
+
function fromPersistedOutcome(event) {
|
|
2097
|
+
if (event.kind !== "OUTCOME") return void 0;
|
|
2098
|
+
const attrs = event.attributes ?? {};
|
|
2099
|
+
const statusRaw = attrs[OUTCOME_ATTRIBUTE_STATUS_KEY];
|
|
2100
|
+
if (!isOutcomeStatus(statusRaw)) return void 0;
|
|
2101
|
+
const expectation = typeof attrs[OUTCOME_ATTRIBUTE_EXPECTATION_KEY] === "string" ? attrs[OUTCOME_ATTRIBUTE_EXPECTATION_KEY] : event.name;
|
|
2102
|
+
const observedAtRaw = attrs[OUTCOME_ATTRIBUTE_OBSERVED_AT_KEY];
|
|
2103
|
+
const observedAt = typeof observedAtRaw === "string" ? Date.parse(observedAtRaw) : typeof observedAtRaw === "number" && Number.isFinite(observedAtRaw) ? observedAtRaw : Date.parse(event.timestamp);
|
|
2104
|
+
return {
|
|
2105
|
+
outcomeId: event.eventId,
|
|
2106
|
+
runId: event.runId,
|
|
2107
|
+
...event.parentId !== void 0 ? { parentId: event.parentId } : {},
|
|
2108
|
+
name: event.name,
|
|
2109
|
+
expectation,
|
|
2110
|
+
status: statusRaw,
|
|
2111
|
+
...parseMethod2(attrs[OUTCOME_ATTRIBUTE_METHOD_KEY]) !== void 0 ? { method: parseMethod2(attrs[OUTCOME_ATTRIBUTE_METHOD_KEY]) } : {},
|
|
2112
|
+
...event.outputSummary !== void 0 ? { actual: event.outputSummary } : {},
|
|
2113
|
+
...attrs.evidence !== void 0 ? { evidence: attrs.evidence } : {},
|
|
2114
|
+
observedAt: Number.isFinite(observedAt) ? observedAt : Date.parse(event.timestamp)
|
|
2115
|
+
};
|
|
2116
|
+
}
|
|
2084
2117
|
function extractOutcomesFromTraceEvents(events) {
|
|
2085
2118
|
const out = [];
|
|
2086
2119
|
for (const event of events) {
|
|
@@ -2090,6 +2123,18 @@ function extractOutcomesFromTraceEvents(events) {
|
|
|
2090
2123
|
}
|
|
2091
2124
|
return out.sort((a, b) => a.observedAt - b.observedAt || a.name.localeCompare(b.name));
|
|
2092
2125
|
}
|
|
2126
|
+
function extractOutcomesFromPersistedEvents(events) {
|
|
2127
|
+
const out = [];
|
|
2128
|
+
for (const event of events) {
|
|
2129
|
+
const outcome = fromPersistedOutcome(event);
|
|
2130
|
+
if (outcome) out.push(outcome);
|
|
2131
|
+
}
|
|
2132
|
+
return out.sort((a, b) => a.observedAt - b.observedAt || a.name.localeCompare(b.name));
|
|
2133
|
+
}
|
|
2134
|
+
function outcomesMatchingStatus(outcomes, statuses) {
|
|
2135
|
+
const set = new Set(statuses);
|
|
2136
|
+
return outcomes.filter((outcome) => set.has(outcome.status));
|
|
2137
|
+
}
|
|
2093
2138
|
function parseObservationFilter(value) {
|
|
2094
2139
|
if (value === void 0 || value.trim() === "") return void 0;
|
|
2095
2140
|
return parseObservedOutcomeStatus(value);
|
|
@@ -2358,13 +2403,13 @@ function createInspector(options = {}) {
|
|
|
2358
2403
|
run,
|
|
2359
2404
|
step,
|
|
2360
2405
|
tool(name, fn, toolOptions) {
|
|
2361
|
-
const
|
|
2362
|
-
return step(`tool:${
|
|
2406
|
+
const toolName2 = normalizeName2(name, "unknown-tool");
|
|
2407
|
+
return step(`tool:${toolName2}`, fn, {
|
|
2363
2408
|
...toolOptions,
|
|
2364
2409
|
type: "tool",
|
|
2365
2410
|
metadata: {
|
|
2366
2411
|
...toolOptions?.metadata ?? {},
|
|
2367
|
-
toolName
|
|
2412
|
+
toolName: toolName2
|
|
2368
2413
|
}
|
|
2369
2414
|
});
|
|
2370
2415
|
},
|
|
@@ -4173,8 +4218,8 @@ function matchStepLevel(m, events, opts) {
|
|
|
4173
4218
|
fields.push("step.name");
|
|
4174
4219
|
}
|
|
4175
4220
|
if (opts.toolQuery) {
|
|
4176
|
-
const
|
|
4177
|
-
if (!nameMatches(
|
|
4221
|
+
const toolName2 = typeof s.metadata?.toolName === "string" ? s.metadata.toolName : s.name;
|
|
4222
|
+
if (!nameMatches(toolName2, opts.toolQuery)) continue;
|
|
4178
4223
|
fields.push("step.tool");
|
|
4179
4224
|
}
|
|
4180
4225
|
if (opts.durationFilter) {
|
|
@@ -4926,12 +4971,12 @@ function buildCriticalPath(runs, handoffs) {
|
|
|
4926
4971
|
handoffs.filter((edge) => edge.confidence === "explicit").map((edge) => edge.from)
|
|
4927
4972
|
);
|
|
4928
4973
|
const ordered = [...runs].sort(compareRuns);
|
|
4929
|
-
const
|
|
4974
|
+
const path11 = [];
|
|
4930
4975
|
const visited = /* @__PURE__ */ new Set();
|
|
4931
4976
|
const pushRun = (run, confidence, source) => {
|
|
4932
4977
|
if (visited.has(run.runId)) return;
|
|
4933
4978
|
visited.add(run.runId);
|
|
4934
|
-
|
|
4979
|
+
path11.push({
|
|
4935
4980
|
runId: run.runId,
|
|
4936
4981
|
name: run.name,
|
|
4937
4982
|
startedAt: run.startedAt,
|
|
@@ -4956,7 +5001,7 @@ function buildCriticalPath(runs, handoffs) {
|
|
|
4956
5001
|
const confidence = explicitTargets.has(run.runId) || explicitSources.has(run.runId) ? "explicit" : "correlated";
|
|
4957
5002
|
pushRun(run, confidence, confidence === "explicit" ? "manual" : "inferred");
|
|
4958
5003
|
}
|
|
4959
|
-
return
|
|
5004
|
+
return path11;
|
|
4960
5005
|
}
|
|
4961
5006
|
function metaRunIdMatches(run, token, runById) {
|
|
4962
5007
|
const meta = extractSessionWorkflowMetadata(run.metadata);
|
|
@@ -5260,6 +5305,3147 @@ function defaultBundleOutputPath(runIds) {
|
|
|
5260
5305
|
return path__default.default.resolve(`agent-inspect-bundle-${label}-${stamp}`);
|
|
5261
5306
|
}
|
|
5262
5307
|
|
|
5308
|
+
// packages/core/src/suite/types.ts
|
|
5309
|
+
var DEFAULT_SUITE_CONFIG_NAMES = [
|
|
5310
|
+
"agent-inspect.suite.json",
|
|
5311
|
+
"agent-inspect.suite.js",
|
|
5312
|
+
"agent-inspect.suite.mjs",
|
|
5313
|
+
"agent-inspect.suite.cjs"
|
|
5314
|
+
];
|
|
5315
|
+
var DEFAULT_SUITE_ARTIFACTS_DIR = ".agent-inspect/suite-runs";
|
|
5316
|
+
function diagnostic(code, message, severity = "error", caseId) {
|
|
5317
|
+
return { code, message, severity, ...caseId !== void 0 ? { caseId } : {} };
|
|
5318
|
+
}
|
|
5319
|
+
function asString(value, label) {
|
|
5320
|
+
if (value === void 0) return void 0;
|
|
5321
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
5322
|
+
throw new Error(`${label} must be a non-empty string.`);
|
|
5323
|
+
}
|
|
5324
|
+
return value.trim();
|
|
5325
|
+
}
|
|
5326
|
+
function asStringArray(value, label) {
|
|
5327
|
+
if (value === void 0) return void 0;
|
|
5328
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
|
|
5329
|
+
throw new Error(`${label} must be an array of strings.`);
|
|
5330
|
+
}
|
|
5331
|
+
return value;
|
|
5332
|
+
}
|
|
5333
|
+
function asPositiveNumber(value, label) {
|
|
5334
|
+
if (value === void 0) return void 0;
|
|
5335
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
5336
|
+
throw new Error(`${label} must be a non-negative number.`);
|
|
5337
|
+
}
|
|
5338
|
+
return value;
|
|
5339
|
+
}
|
|
5340
|
+
function validateCaseConfig(value, index) {
|
|
5341
|
+
const diagnostics = [];
|
|
5342
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
5343
|
+
diagnostics.push(
|
|
5344
|
+
diagnostic("AI_SUITE_CONFIG_INVALID", `cases[${index}] must be an object.`)
|
|
5345
|
+
);
|
|
5346
|
+
return { diagnostics };
|
|
5347
|
+
}
|
|
5348
|
+
const raw = value;
|
|
5349
|
+
try {
|
|
5350
|
+
const id = asString(raw.id, `cases[${index}].id`);
|
|
5351
|
+
if (id === void 0) {
|
|
5352
|
+
diagnostics.push(
|
|
5353
|
+
diagnostic("AI_SUITE_CONFIG_INVALID", `cases[${index}].id is required.`)
|
|
5354
|
+
);
|
|
5355
|
+
return { diagnostics };
|
|
5356
|
+
}
|
|
5357
|
+
const trace = asString(raw.trace, `cases[${index}].trace`);
|
|
5358
|
+
const runId = asString(raw.runId, `cases[${index}].runId`);
|
|
5359
|
+
const input = asString(raw.input, `cases[${index}].input`);
|
|
5360
|
+
return {
|
|
5361
|
+
caseConfig: {
|
|
5362
|
+
id,
|
|
5363
|
+
...trace !== void 0 ? { trace } : {},
|
|
5364
|
+
...runId !== void 0 ? { runId } : {},
|
|
5365
|
+
...input !== void 0 ? { input } : {},
|
|
5366
|
+
...asStringArray(raw.requireTools, `cases[${index}].requireTools`) !== void 0 ? { requireTools: asStringArray(raw.requireTools, `cases[${index}].requireTools`) } : {},
|
|
5367
|
+
...asStringArray(raw.forbidTools, `cases[${index}].forbidTools`) !== void 0 ? { forbidTools: asStringArray(raw.forbidTools, `cases[${index}].forbidTools`) } : {},
|
|
5368
|
+
...asPositiveNumber(raw.maxDurationMs, `cases[${index}].maxDurationMs`) !== void 0 ? {
|
|
5369
|
+
maxDurationMs: asPositiveNumber(
|
|
5370
|
+
raw.maxDurationMs,
|
|
5371
|
+
`cases[${index}].maxDurationMs`
|
|
5372
|
+
)
|
|
5373
|
+
} : {},
|
|
5374
|
+
...asStringArray(
|
|
5375
|
+
raw.expectedObservations,
|
|
5376
|
+
`cases[${index}].expectedObservations`
|
|
5377
|
+
) !== void 0 ? {
|
|
5378
|
+
expectedObservations: asStringArray(
|
|
5379
|
+
raw.expectedObservations,
|
|
5380
|
+
`cases[${index}].expectedObservations`
|
|
5381
|
+
)
|
|
5382
|
+
} : {}
|
|
5383
|
+
},
|
|
5384
|
+
diagnostics
|
|
5385
|
+
};
|
|
5386
|
+
} catch (error) {
|
|
5387
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5388
|
+
diagnostics.push(diagnostic("AI_SUITE_CONFIG_INVALID", message));
|
|
5389
|
+
return { diagnostics };
|
|
5390
|
+
}
|
|
5391
|
+
}
|
|
5392
|
+
function normalizeSuiteConfig(value) {
|
|
5393
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
5394
|
+
throw new Error("Suite config must export an object.");
|
|
5395
|
+
}
|
|
5396
|
+
const raw = value;
|
|
5397
|
+
const name = asString(raw.name, "name");
|
|
5398
|
+
const traces = asString(raw.traces, "traces");
|
|
5399
|
+
if (name === void 0) throw new Error("name is required.");
|
|
5400
|
+
if (traces === void 0) throw new Error("traces is required.");
|
|
5401
|
+
if (!Array.isArray(raw.cases) || raw.cases.length === 0) {
|
|
5402
|
+
throw new Error("cases must be a non-empty array.");
|
|
5403
|
+
}
|
|
5404
|
+
const cases = [];
|
|
5405
|
+
for (let index = 0; index < raw.cases.length; index += 1) {
|
|
5406
|
+
const { caseConfig, diagnostics } = validateCaseConfig(raw.cases[index], index);
|
|
5407
|
+
if (diagnostics.length > 0) {
|
|
5408
|
+
throw new Error(diagnostics.map((item) => item.message).join("; "));
|
|
5409
|
+
}
|
|
5410
|
+
if (caseConfig !== void 0) cases.push(caseConfig);
|
|
5411
|
+
}
|
|
5412
|
+
const ids = /* @__PURE__ */ new Set();
|
|
5413
|
+
for (const suiteCase of cases) {
|
|
5414
|
+
if (ids.has(suiteCase.id)) {
|
|
5415
|
+
throw new Error(`Duplicate case id "${suiteCase.id}".`);
|
|
5416
|
+
}
|
|
5417
|
+
ids.add(suiteCase.id);
|
|
5418
|
+
}
|
|
5419
|
+
const redactionProfile = raw.redactionProfile === "local" || raw.redactionProfile === "share" || raw.redactionProfile === "strict" ? raw.redactionProfile : void 0;
|
|
5420
|
+
if (raw.redactionProfile !== void 0 && redactionProfile === void 0) {
|
|
5421
|
+
throw new Error('redactionProfile must be "local", "share", or "strict".');
|
|
5422
|
+
}
|
|
5423
|
+
const config = { name, traces, cases };
|
|
5424
|
+
if (raw.checks !== void 0) {
|
|
5425
|
+
if (typeof raw.checks !== "object" || Array.isArray(raw.checks)) {
|
|
5426
|
+
throw new Error("checks must be an object.");
|
|
5427
|
+
}
|
|
5428
|
+
config.checks = raw.checks;
|
|
5429
|
+
}
|
|
5430
|
+
if (raw.eval !== void 0) {
|
|
5431
|
+
if (typeof raw.eval !== "object" || Array.isArray(raw.eval)) {
|
|
5432
|
+
throw new Error("eval must be an object.");
|
|
5433
|
+
}
|
|
5434
|
+
config.eval = raw.eval;
|
|
5435
|
+
}
|
|
5436
|
+
if (raw.artifacts !== void 0) {
|
|
5437
|
+
if (typeof raw.artifacts !== "object" || Array.isArray(raw.artifacts)) {
|
|
5438
|
+
throw new Error("artifacts must be an object.");
|
|
5439
|
+
}
|
|
5440
|
+
const outputDir = asString(
|
|
5441
|
+
raw.artifacts.outputDir,
|
|
5442
|
+
"artifacts.outputDir"
|
|
5443
|
+
);
|
|
5444
|
+
config.artifacts = outputDir !== void 0 ? { outputDir } : {};
|
|
5445
|
+
}
|
|
5446
|
+
if (raw.baseline !== void 0) {
|
|
5447
|
+
const baseline = asString(raw.baseline, "baseline");
|
|
5448
|
+
if (baseline !== void 0) config.baseline = baseline;
|
|
5449
|
+
}
|
|
5450
|
+
if (raw.candidate !== void 0) {
|
|
5451
|
+
const candidate = asString(raw.candidate, "candidate");
|
|
5452
|
+
if (candidate !== void 0) config.candidate = candidate;
|
|
5453
|
+
}
|
|
5454
|
+
if (redactionProfile !== void 0) config.redactionProfile = redactionProfile;
|
|
5455
|
+
return config;
|
|
5456
|
+
}
|
|
5457
|
+
async function validateSuiteConfig(config, options) {
|
|
5458
|
+
const diagnostics = [];
|
|
5459
|
+
const tracesDir = path__default.default.resolve(options.configDir, config.traces);
|
|
5460
|
+
try {
|
|
5461
|
+
await promises.access(tracesDir);
|
|
5462
|
+
} catch {
|
|
5463
|
+
diagnostics.push(
|
|
5464
|
+
diagnostic("AI_SUITE_CONFIG_INVALID", `traces directory not found: ${tracesDir}`)
|
|
5465
|
+
);
|
|
5466
|
+
}
|
|
5467
|
+
for (const suiteCase of config.cases) {
|
|
5468
|
+
if (suiteCase.input !== void 0) {
|
|
5469
|
+
const inputPath = path__default.default.resolve(options.configDir, suiteCase.input);
|
|
5470
|
+
try {
|
|
5471
|
+
await promises.access(inputPath);
|
|
5472
|
+
} catch {
|
|
5473
|
+
diagnostics.push(
|
|
5474
|
+
diagnostic(
|
|
5475
|
+
"AI_SUITE_CONFIG_INVALID",
|
|
5476
|
+
`input fixture not found: ${suiteCase.input}`,
|
|
5477
|
+
"warning",
|
|
5478
|
+
suiteCase.id
|
|
5479
|
+
)
|
|
5480
|
+
);
|
|
5481
|
+
}
|
|
5482
|
+
}
|
|
5483
|
+
}
|
|
5484
|
+
return { ok: diagnostics.every((item) => item.severity !== "error"), diagnostics };
|
|
5485
|
+
}
|
|
5486
|
+
|
|
5487
|
+
// packages/core/src/suite/load.ts
|
|
5488
|
+
var CONFIG_EXTENSIONS = /* @__PURE__ */ new Set([".json", ".js", ".mjs", ".cjs"]);
|
|
5489
|
+
var TS_CONFIG_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".mts", ".cts"]);
|
|
5490
|
+
function diagnostic2(code, message) {
|
|
5491
|
+
return { code, message, severity: "error" };
|
|
5492
|
+
}
|
|
5493
|
+
async function fileExists(filePath) {
|
|
5494
|
+
try {
|
|
5495
|
+
await promises.access(filePath);
|
|
5496
|
+
return true;
|
|
5497
|
+
} catch {
|
|
5498
|
+
return false;
|
|
5499
|
+
}
|
|
5500
|
+
}
|
|
5501
|
+
async function resolveSuiteConfigPath(options = {}) {
|
|
5502
|
+
const cwd = path__default.default.resolve(options.cwd ?? process.cwd());
|
|
5503
|
+
if (options.configPath !== void 0 && options.configPath.trim() !== "") {
|
|
5504
|
+
return path__default.default.resolve(cwd, options.configPath.trim());
|
|
5505
|
+
}
|
|
5506
|
+
for (const name of DEFAULT_SUITE_CONFIG_NAMES) {
|
|
5507
|
+
const candidate = path__default.default.join(cwd, name);
|
|
5508
|
+
if (await fileExists(candidate)) return candidate;
|
|
5509
|
+
}
|
|
5510
|
+
throw new Error(
|
|
5511
|
+
`No suite config found. Create one with \`agent-inspect suite init\` or pass --config.`
|
|
5512
|
+
);
|
|
5513
|
+
}
|
|
5514
|
+
async function loadSuiteConfig(options = {}) {
|
|
5515
|
+
let configPath;
|
|
5516
|
+
try {
|
|
5517
|
+
configPath = await resolveSuiteConfigPath(options);
|
|
5518
|
+
} catch (error) {
|
|
5519
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5520
|
+
throw Object.assign(new Error(message), {
|
|
5521
|
+
diagnostics: [diagnostic2("AI_SUITE_CONFIG_LOAD_FAILED", message)]
|
|
5522
|
+
});
|
|
5523
|
+
}
|
|
5524
|
+
const extension = path__default.default.extname(configPath);
|
|
5525
|
+
if (TS_CONFIG_EXTENSIONS.has(extension)) {
|
|
5526
|
+
const message = "TypeScript suite configs require an explicit precompiled JavaScript config or future --config-loader support.";
|
|
5527
|
+
throw Object.assign(new Error(message), {
|
|
5528
|
+
diagnostics: [diagnostic2("AI_SUITE_CONFIG_LOAD_FAILED", message)]
|
|
5529
|
+
});
|
|
5530
|
+
}
|
|
5531
|
+
if (!CONFIG_EXTENSIONS.has(extension)) {
|
|
5532
|
+
const message = "Unsupported suite config extension. Use .json, .js, .mjs, or .cjs.";
|
|
5533
|
+
throw Object.assign(new Error(message), {
|
|
5534
|
+
diagnostics: [diagnostic2("AI_SUITE_CONFIG_LOAD_FAILED", message)]
|
|
5535
|
+
});
|
|
5536
|
+
}
|
|
5537
|
+
try {
|
|
5538
|
+
let raw;
|
|
5539
|
+
if (extension === ".json") {
|
|
5540
|
+
raw = JSON.parse(await promises.readFile(configPath, "utf-8"));
|
|
5541
|
+
} else {
|
|
5542
|
+
const mod = await import(url.pathToFileURL(configPath).href);
|
|
5543
|
+
raw = "default" in mod ? mod.default : mod;
|
|
5544
|
+
}
|
|
5545
|
+
const config = normalizeSuiteConfig(raw);
|
|
5546
|
+
return {
|
|
5547
|
+
config,
|
|
5548
|
+
configPath,
|
|
5549
|
+
configDir: path__default.default.dirname(configPath)
|
|
5550
|
+
};
|
|
5551
|
+
} catch (error) {
|
|
5552
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5553
|
+
throw Object.assign(new Error(message), {
|
|
5554
|
+
diagnostics: [diagnostic2("AI_SUITE_CONFIG_LOAD_FAILED", message)]
|
|
5555
|
+
});
|
|
5556
|
+
}
|
|
5557
|
+
}
|
|
5558
|
+
function defaultSuiteConfigTemplate() {
|
|
5559
|
+
return {
|
|
5560
|
+
name: "my-agent-suite",
|
|
5561
|
+
traces: "./.agent-inspect",
|
|
5562
|
+
cases: [
|
|
5563
|
+
{
|
|
5564
|
+
id: "example-case",
|
|
5565
|
+
runId: "example-run",
|
|
5566
|
+
requireTools: ["searchDocs"],
|
|
5567
|
+
maxDurationMs: 3e4,
|
|
5568
|
+
expectedObservations: ["answerReady"]
|
|
5569
|
+
}
|
|
5570
|
+
],
|
|
5571
|
+
checks: {
|
|
5572
|
+
select: ["run.status"]
|
|
5573
|
+
},
|
|
5574
|
+
redactionProfile: "local",
|
|
5575
|
+
artifacts: {
|
|
5576
|
+
outputDir: ".agent-inspect/suite-runs"
|
|
5577
|
+
}
|
|
5578
|
+
};
|
|
5579
|
+
}
|
|
5580
|
+
async function exists(filePath) {
|
|
5581
|
+
try {
|
|
5582
|
+
await promises.access(filePath);
|
|
5583
|
+
return true;
|
|
5584
|
+
} catch {
|
|
5585
|
+
return false;
|
|
5586
|
+
}
|
|
5587
|
+
}
|
|
5588
|
+
async function resolveSuiteCaseTrace(suiteCase, options) {
|
|
5589
|
+
if (suiteCase.trace !== void 0) {
|
|
5590
|
+
const tracePath = path__default.default.resolve(options.configDir, suiteCase.trace);
|
|
5591
|
+
if (await exists(tracePath)) {
|
|
5592
|
+
return { caseId: suiteCase.id, tracePath, missing: false };
|
|
5593
|
+
}
|
|
5594
|
+
return {
|
|
5595
|
+
caseId: suiteCase.id,
|
|
5596
|
+
tracePath,
|
|
5597
|
+
missing: true,
|
|
5598
|
+
reason: `trace file not found: ${suiteCase.trace}`
|
|
5599
|
+
};
|
|
5600
|
+
}
|
|
5601
|
+
const runKey = suiteCase.runId ?? suiteCase.id;
|
|
5602
|
+
const directPath = getTraceFilePath(runKey, options.tracesDir);
|
|
5603
|
+
if (await exists(directPath)) {
|
|
5604
|
+
return { caseId: suiteCase.id, tracePath: directPath, runId: runKey, missing: false };
|
|
5605
|
+
}
|
|
5606
|
+
const nestedPath = path__default.default.join(options.tracesDir, `${path__default.default.basename(runKey)}.jsonl`);
|
|
5607
|
+
if (await exists(nestedPath)) {
|
|
5608
|
+
return { caseId: suiteCase.id, tracePath: nestedPath, runId: runKey, missing: false };
|
|
5609
|
+
}
|
|
5610
|
+
return {
|
|
5611
|
+
caseId: suiteCase.id,
|
|
5612
|
+
runId: runKey,
|
|
5613
|
+
missing: true,
|
|
5614
|
+
reason: `no trace found for run id "${runKey}" under ${options.tracesDir}`
|
|
5615
|
+
};
|
|
5616
|
+
}
|
|
5617
|
+
|
|
5618
|
+
// packages/core/src/checks/index.ts
|
|
5619
|
+
var SEVERITY_RANK = {
|
|
5620
|
+
error: 0,
|
|
5621
|
+
warning: 1,
|
|
5622
|
+
info: 2
|
|
5623
|
+
};
|
|
5624
|
+
var STATUS_RANK = {
|
|
5625
|
+
fail: 0,
|
|
5626
|
+
warning: 1,
|
|
5627
|
+
pass: 2
|
|
5628
|
+
};
|
|
5629
|
+
function compareStrings(a, b) {
|
|
5630
|
+
return (a ?? "").localeCompare(b ?? "");
|
|
5631
|
+
}
|
|
5632
|
+
function diagnostic3(code, message, ruleId) {
|
|
5633
|
+
return {
|
|
5634
|
+
code,
|
|
5635
|
+
message,
|
|
5636
|
+
severity: "error",
|
|
5637
|
+
...ruleId ? { ruleId } : {}
|
|
5638
|
+
};
|
|
5639
|
+
}
|
|
5640
|
+
function emptySummary2() {
|
|
5641
|
+
return {
|
|
5642
|
+
passed: 0,
|
|
5643
|
+
failed: 0,
|
|
5644
|
+
warnings: 0,
|
|
5645
|
+
errors: 0
|
|
5646
|
+
};
|
|
5647
|
+
}
|
|
5648
|
+
function errorResult(input, diagnostics, selectedRun) {
|
|
5649
|
+
return {
|
|
5650
|
+
ok: false,
|
|
5651
|
+
status: "error",
|
|
5652
|
+
format: input.read.format,
|
|
5653
|
+
...selectedRun ? { runId: selectedRun.runId } : {},
|
|
5654
|
+
summary: {
|
|
5655
|
+
...emptySummary2(),
|
|
5656
|
+
errors: diagnostics.filter((item) => item.severity === "error").length
|
|
5657
|
+
},
|
|
5658
|
+
findings: [],
|
|
5659
|
+
diagnostics: [...diagnostics]
|
|
5660
|
+
};
|
|
5661
|
+
}
|
|
5662
|
+
function flattenNodes(nodes) {
|
|
5663
|
+
return nodes.flatMap((node) => [node, ...flattenNodes(node.children)]);
|
|
5664
|
+
}
|
|
5665
|
+
function buildFacts2(input, selectedRun) {
|
|
5666
|
+
const scopedRuns = selectedRun ? [selectedRun] : input.read.runs;
|
|
5667
|
+
const scopedRunIds = new Set(scopedRuns.map((run) => run.runId));
|
|
5668
|
+
const scopedEvents = selectedRun === void 0 ? input.read.events : input.read.events.filter((event) => scopedRunIds.has(event.runId));
|
|
5669
|
+
const nodes = flattenNodes(scopedRuns.flatMap((run) => run.children));
|
|
5670
|
+
const nodesByEventId = /* @__PURE__ */ new Map();
|
|
5671
|
+
const childrenByParentId = /* @__PURE__ */ new Map();
|
|
5672
|
+
for (const node of nodes) {
|
|
5673
|
+
nodesByEventId.set(node.event.eventId, node);
|
|
5674
|
+
const parentId = node.event.parentId;
|
|
5675
|
+
if (parentId) {
|
|
5676
|
+
const children = childrenByParentId.get(parentId) ?? [];
|
|
5677
|
+
children.push(node);
|
|
5678
|
+
childrenByParentId.set(parentId, children);
|
|
5679
|
+
}
|
|
5680
|
+
}
|
|
5681
|
+
return {
|
|
5682
|
+
format: input.read.format,
|
|
5683
|
+
runs: Object.freeze([...input.read.runs]),
|
|
5684
|
+
events: Object.freeze([...scopedEvents]),
|
|
5685
|
+
readerWarnings: Object.freeze([...input.read.warnings]),
|
|
5686
|
+
unsupportedFields: Object.freeze([...input.read.unsupportedFields]),
|
|
5687
|
+
sourceFiles: Object.freeze([...input.read.sourceFiles]),
|
|
5688
|
+
nodesByEventId,
|
|
5689
|
+
childrenByParentId,
|
|
5690
|
+
rootNodes: Object.freeze(scopedRuns.flatMap((run) => run.children))
|
|
5691
|
+
};
|
|
5692
|
+
}
|
|
5693
|
+
function resolveSelectedRun(input, runId) {
|
|
5694
|
+
if (input.selectedRun) {
|
|
5695
|
+
if (runId && input.selectedRun.runId !== runId) {
|
|
5696
|
+
return {
|
|
5697
|
+
diagnostics: [
|
|
5698
|
+
diagnostic3(
|
|
5699
|
+
"AI_CHECK_INVALID_ARGUMENTS",
|
|
5700
|
+
`Selected run ${input.selectedRun.runId} does not match requested run ${runId}.`
|
|
5701
|
+
)
|
|
5702
|
+
]
|
|
5703
|
+
};
|
|
5704
|
+
}
|
|
5705
|
+
return { run: input.selectedRun, diagnostics: [] };
|
|
5706
|
+
}
|
|
5707
|
+
if (runId) {
|
|
5708
|
+
const run = input.read.runs.find((candidate) => candidate.runId === runId);
|
|
5709
|
+
if (!run) {
|
|
5710
|
+
return {
|
|
5711
|
+
diagnostics: [
|
|
5712
|
+
diagnostic3("AI_CHECK_RUN_SELECTION_REQUIRED", `Run not found: ${runId}.`)
|
|
5713
|
+
]
|
|
5714
|
+
};
|
|
5715
|
+
}
|
|
5716
|
+
return { run, diagnostics: [] };
|
|
5717
|
+
}
|
|
5718
|
+
if (input.read.runs.length === 1) {
|
|
5719
|
+
return { run: input.read.runs[0], diagnostics: [] };
|
|
5720
|
+
}
|
|
5721
|
+
if (input.read.runs.length === 0) {
|
|
5722
|
+
return {
|
|
5723
|
+
diagnostics: [
|
|
5724
|
+
diagnostic3("AI_CHECK_RUN_SELECTION_REQUIRED", "No runs are available for checks.")
|
|
5725
|
+
]
|
|
5726
|
+
};
|
|
5727
|
+
}
|
|
5728
|
+
return {
|
|
5729
|
+
diagnostics: [
|
|
5730
|
+
diagnostic3(
|
|
5731
|
+
"AI_CHECK_RUN_SELECTION_REQUIRED",
|
|
5732
|
+
"Multiple runs are available; select a run before executing checks."
|
|
5733
|
+
)
|
|
5734
|
+
]
|
|
5735
|
+
};
|
|
5736
|
+
}
|
|
5737
|
+
function selectRules(rules, selectedIds) {
|
|
5738
|
+
const diagnostics = [];
|
|
5739
|
+
const byId = /* @__PURE__ */ new Map();
|
|
5740
|
+
for (const rule of rules) {
|
|
5741
|
+
if (byId.has(rule.id)) {
|
|
5742
|
+
diagnostics.push(
|
|
5743
|
+
diagnostic3("AI_CHECK_INVALID_CONFIG", `Duplicate trace check rule id: ${rule.id}.`, rule.id)
|
|
5744
|
+
);
|
|
5745
|
+
continue;
|
|
5746
|
+
}
|
|
5747
|
+
byId.set(rule.id, rule);
|
|
5748
|
+
}
|
|
5749
|
+
if (selectedIds && selectedIds.length > 0) {
|
|
5750
|
+
const selected = new Set(selectedIds);
|
|
5751
|
+
for (const id of selected) {
|
|
5752
|
+
if (!byId.has(id)) {
|
|
5753
|
+
diagnostics.push(
|
|
5754
|
+
diagnostic3("AI_CHECK_INVALID_CONFIG", `Unknown trace check rule id: ${id}.`, id)
|
|
5755
|
+
);
|
|
5756
|
+
}
|
|
5757
|
+
}
|
|
5758
|
+
return {
|
|
5759
|
+
rules: [...byId.values()].filter((rule) => selected.has(rule.id)).sort(compareRules),
|
|
5760
|
+
diagnostics
|
|
5761
|
+
};
|
|
5762
|
+
}
|
|
5763
|
+
return { rules: [...byId.values()].sort(compareRules), diagnostics };
|
|
5764
|
+
}
|
|
5765
|
+
function compareRules(a, b) {
|
|
5766
|
+
return a.id.localeCompare(b.id);
|
|
5767
|
+
}
|
|
5768
|
+
function eventTimestamp(finding, eventById) {
|
|
5769
|
+
const eventId = finding.evidence[0]?.eventId;
|
|
5770
|
+
return eventId ? eventById.get(eventId)?.timestamp ?? "" : "";
|
|
5771
|
+
}
|
|
5772
|
+
function compareFindings(eventById) {
|
|
5773
|
+
return (a, b) => {
|
|
5774
|
+
if (SEVERITY_RANK[a.severity] !== SEVERITY_RANK[b.severity]) {
|
|
5775
|
+
return SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity];
|
|
5776
|
+
}
|
|
5777
|
+
const byRule = a.ruleId.localeCompare(b.ruleId);
|
|
5778
|
+
if (byRule !== 0) return byRule;
|
|
5779
|
+
if (STATUS_RANK[a.status] !== STATUS_RANK[b.status]) {
|
|
5780
|
+
return STATUS_RANK[a.status] - STATUS_RANK[b.status];
|
|
5781
|
+
}
|
|
5782
|
+
const byRun = compareStrings(a.evidence[0]?.runId, b.evidence[0]?.runId);
|
|
5783
|
+
if (byRun !== 0) return byRun;
|
|
5784
|
+
const byTime = eventTimestamp(a, eventById).localeCompare(eventTimestamp(b, eventById));
|
|
5785
|
+
if (byTime !== 0) return byTime;
|
|
5786
|
+
const byEvent = compareStrings(a.evidence[0]?.eventId, b.evidence[0]?.eventId);
|
|
5787
|
+
if (byEvent !== 0) return byEvent;
|
|
5788
|
+
return compareStrings(a.evidence[0]?.path, b.evidence[0]?.path);
|
|
5789
|
+
};
|
|
5790
|
+
}
|
|
5791
|
+
function normalizeFinding(rule, finding) {
|
|
5792
|
+
return {
|
|
5793
|
+
ruleId: finding.ruleId || rule.id,
|
|
5794
|
+
severity: finding.severity ?? rule.defaultSeverity,
|
|
5795
|
+
status: finding.status,
|
|
5796
|
+
message: finding.message,
|
|
5797
|
+
...finding.expected !== void 0 ? { expected: finding.expected } : {},
|
|
5798
|
+
...finding.actual !== void 0 ? { actual: finding.actual } : {},
|
|
5799
|
+
evidence: [...finding.evidence ?? []]
|
|
5800
|
+
};
|
|
5801
|
+
}
|
|
5802
|
+
function summarize(findings, diagnostics) {
|
|
5803
|
+
return {
|
|
5804
|
+
passed: findings.filter((finding) => finding.status === "pass").length,
|
|
5805
|
+
failed: findings.filter(
|
|
5806
|
+
(finding) => finding.status === "fail" && finding.severity === "error"
|
|
5807
|
+
).length,
|
|
5808
|
+
warnings: findings.filter(
|
|
5809
|
+
(finding) => finding.status === "warning" || finding.severity === "warning"
|
|
5810
|
+
).length,
|
|
5811
|
+
errors: diagnostics.filter((item) => item.severity === "error").length
|
|
5812
|
+
};
|
|
5813
|
+
}
|
|
5814
|
+
function stringAttr(event, keys) {
|
|
5815
|
+
for (const key of keys) {
|
|
5816
|
+
const value = event.attributes?.[key];
|
|
5817
|
+
if (typeof value === "string" && value.trim() !== "") return value;
|
|
5818
|
+
}
|
|
5819
|
+
return void 0;
|
|
5820
|
+
}
|
|
5821
|
+
function stripPrefix(name, prefixes) {
|
|
5822
|
+
for (const prefix of prefixes) {
|
|
5823
|
+
if (name.startsWith(prefix)) return name.slice(prefix.length);
|
|
5824
|
+
}
|
|
5825
|
+
return name;
|
|
5826
|
+
}
|
|
5827
|
+
function eventEvidence(event, path11) {
|
|
5828
|
+
return {
|
|
5829
|
+
runId: event.runId,
|
|
5830
|
+
eventId: event.eventId,
|
|
5831
|
+
parentId: event.parentId,
|
|
5832
|
+
traceId: event.trace?.traceId,
|
|
5833
|
+
spanId: event.trace?.spanId,
|
|
5834
|
+
kind: event.kind,
|
|
5835
|
+
name: event.name,
|
|
5836
|
+
status: event.status,
|
|
5837
|
+
...path11 ? { path: path11 } : {}
|
|
5838
|
+
};
|
|
5839
|
+
}
|
|
5840
|
+
function runEvidence(run) {
|
|
5841
|
+
return run ? [{ runId: run.runId, name: run.name, status: run.status }] : [];
|
|
5842
|
+
}
|
|
5843
|
+
function failFinding(ruleId, message, evidence, expected, actual) {
|
|
5844
|
+
return {
|
|
5845
|
+
ruleId,
|
|
5846
|
+
severity: "error",
|
|
5847
|
+
status: "fail",
|
|
5848
|
+
message,
|
|
5849
|
+
...expected !== void 0 ? { expected } : {},
|
|
5850
|
+
...actual !== void 0 ? { actual } : {},
|
|
5851
|
+
evidence: [...evidence]
|
|
5852
|
+
};
|
|
5853
|
+
}
|
|
5854
|
+
function toolName(event) {
|
|
5855
|
+
return stringAttr(event, ["toolName", "tool"]) ?? stripPrefix(event.name, ["tool:", "function:", "mcp-tools:"]);
|
|
5856
|
+
}
|
|
5857
|
+
function llmModel(event) {
|
|
5858
|
+
return stringAttr(event, ["model", "modelId", "responseModelId", "modelName", "model_name"]) ?? stripPrefix(event.name, ["llm:", "generation:", "transcription:", "speech:"]);
|
|
5859
|
+
}
|
|
5860
|
+
function llmProvider(event) {
|
|
5861
|
+
return stringAttr(event, ["provider", "providerName", "provider_name"]);
|
|
5862
|
+
}
|
|
5863
|
+
function llmFinishReason(event) {
|
|
5864
|
+
return stringAttr(event, ["finishReason", "rawFinishReason", "finish_reason"]);
|
|
5865
|
+
}
|
|
5866
|
+
function finishedEvents(context, kind) {
|
|
5867
|
+
return context.events.filter(
|
|
5868
|
+
(event) => (kind === void 0 || event.kind === kind) && event.status !== "running"
|
|
5869
|
+
);
|
|
5870
|
+
}
|
|
5871
|
+
function createRunStatusRule(options = {}) {
|
|
5872
|
+
const expected = options.expected ?? "ok";
|
|
5873
|
+
const allowIncomplete = options.allowIncomplete === true;
|
|
5874
|
+
return {
|
|
5875
|
+
id: "run.status",
|
|
5876
|
+
category: "run",
|
|
5877
|
+
defaultSeverity: "error",
|
|
5878
|
+
evaluate(context) {
|
|
5879
|
+
const findings = [];
|
|
5880
|
+
const actual = context.selectedRun?.status ?? "unknown";
|
|
5881
|
+
if (actual !== expected) {
|
|
5882
|
+
findings.push(
|
|
5883
|
+
failFinding(
|
|
5884
|
+
"run.status",
|
|
5885
|
+
`Run status ${actual} did not match expected ${expected}.`,
|
|
5886
|
+
runEvidence(context.selectedRun),
|
|
5887
|
+
expected,
|
|
5888
|
+
actual
|
|
5889
|
+
)
|
|
5890
|
+
);
|
|
5891
|
+
}
|
|
5892
|
+
if (!allowIncomplete) {
|
|
5893
|
+
const running = context.events.filter((event) => event.status === "running");
|
|
5894
|
+
if (running.length > 0) {
|
|
5895
|
+
findings.push(
|
|
5896
|
+
failFinding(
|
|
5897
|
+
"run.status",
|
|
5898
|
+
"Run contains incomplete running events.",
|
|
5899
|
+
running.map((event) => eventEvidence(event)),
|
|
5900
|
+
"no running events",
|
|
5901
|
+
running.length
|
|
5902
|
+
)
|
|
5903
|
+
);
|
|
5904
|
+
}
|
|
5905
|
+
}
|
|
5906
|
+
return findings;
|
|
5907
|
+
}
|
|
5908
|
+
};
|
|
5909
|
+
}
|
|
5910
|
+
function createRunDurationRule(options) {
|
|
5911
|
+
return {
|
|
5912
|
+
id: "run.duration",
|
|
5913
|
+
category: "run",
|
|
5914
|
+
defaultSeverity: "error",
|
|
5915
|
+
evaluate(context) {
|
|
5916
|
+
const actual = context.selectedRun?.durationMs;
|
|
5917
|
+
if (actual === void 0 || actual <= options.maxDurationMs) return [];
|
|
5918
|
+
return [
|
|
5919
|
+
failFinding(
|
|
5920
|
+
"run.duration",
|
|
5921
|
+
`Run duration ${actual}ms exceeded ${options.maxDurationMs}ms.`,
|
|
5922
|
+
runEvidence(context.selectedRun),
|
|
5923
|
+
{ maxDurationMs: options.maxDurationMs },
|
|
5924
|
+
actual
|
|
5925
|
+
)
|
|
5926
|
+
];
|
|
5927
|
+
}
|
|
5928
|
+
};
|
|
5929
|
+
}
|
|
5930
|
+
function createToolUsageRule(options) {
|
|
5931
|
+
return {
|
|
5932
|
+
id: "tool.usage",
|
|
5933
|
+
category: "tool",
|
|
5934
|
+
defaultSeverity: "error",
|
|
5935
|
+
evaluate(context) {
|
|
5936
|
+
const tools = finishedEvents(context, "TOOL");
|
|
5937
|
+
const names = tools.map(toolName);
|
|
5938
|
+
const nameSet = new Set(names);
|
|
5939
|
+
const findings = [];
|
|
5940
|
+
for (const required of options.required ?? []) {
|
|
5941
|
+
if (!nameSet.has(required)) {
|
|
5942
|
+
findings.push(
|
|
5943
|
+
failFinding("tool.usage", `Required tool ${required} did not appear.`, runEvidence(context.selectedRun), required, names)
|
|
5944
|
+
);
|
|
5945
|
+
}
|
|
5946
|
+
}
|
|
5947
|
+
const forbidden = new Set(options.forbidden ?? []);
|
|
5948
|
+
const allowed = options.allowed ? new Set(options.allowed) : void 0;
|
|
5949
|
+
for (const event of tools) {
|
|
5950
|
+
const name = toolName(event);
|
|
5951
|
+
if (forbidden.has(name)) {
|
|
5952
|
+
findings.push(
|
|
5953
|
+
failFinding("tool.usage", `Forbidden tool ${name} appeared.`, [eventEvidence(event)], "tool absent", name)
|
|
5954
|
+
);
|
|
5955
|
+
}
|
|
5956
|
+
if (allowed && !allowed.has(name)) {
|
|
5957
|
+
findings.push(
|
|
5958
|
+
failFinding("tool.usage", `Tool ${name} is not in the allowed tool set.`, [eventEvidence(event)], [...allowed].sort(), name)
|
|
5959
|
+
);
|
|
5960
|
+
}
|
|
5961
|
+
}
|
|
5962
|
+
if (options.minCount !== void 0 && tools.length < options.minCount) {
|
|
5963
|
+
findings.push(
|
|
5964
|
+
failFinding("tool.usage", `Tool count ${tools.length} was below minimum ${options.minCount}.`, runEvidence(context.selectedRun), { minCount: options.minCount }, tools.length)
|
|
5965
|
+
);
|
|
5966
|
+
}
|
|
5967
|
+
if (options.maxCount !== void 0 && tools.length > options.maxCount) {
|
|
5968
|
+
findings.push(
|
|
5969
|
+
failFinding("tool.usage", `Tool count ${tools.length} exceeded maximum ${options.maxCount}.`, tools.map((event) => eventEvidence(event)), { maxCount: options.maxCount }, tools.length)
|
|
5970
|
+
);
|
|
5971
|
+
}
|
|
5972
|
+
return findings;
|
|
5973
|
+
}
|
|
5974
|
+
};
|
|
5975
|
+
}
|
|
5976
|
+
function createLlmUsageRule(options) {
|
|
5977
|
+
return {
|
|
5978
|
+
id: "llm.usage",
|
|
5979
|
+
category: "llm",
|
|
5980
|
+
defaultSeverity: "error",
|
|
5981
|
+
evaluate(context) {
|
|
5982
|
+
const llms = finishedEvents(context, "LLM");
|
|
5983
|
+
const findings = [];
|
|
5984
|
+
const allowedModels = options.allowedModels ? new Set(options.allowedModels) : void 0;
|
|
5985
|
+
const allowedProviders = options.allowedProviders ? new Set(options.allowedProviders) : void 0;
|
|
5986
|
+
const finishReasons = options.finishReasons ? new Set(options.finishReasons) : void 0;
|
|
5987
|
+
if (options.maxCalls !== void 0 && llms.length > options.maxCalls) {
|
|
5988
|
+
findings.push(
|
|
5989
|
+
failFinding(
|
|
5990
|
+
"llm.usage",
|
|
5991
|
+
`LLM call count ${llms.length} exceeded ${options.maxCalls}.`,
|
|
5992
|
+
llms.map((event) => eventEvidence(event)),
|
|
5993
|
+
{ maxCalls: options.maxCalls },
|
|
5994
|
+
llms.length
|
|
5995
|
+
)
|
|
5996
|
+
);
|
|
5997
|
+
}
|
|
5998
|
+
for (const event of llms) {
|
|
5999
|
+
const model = llmModel(event);
|
|
6000
|
+
const provider = llmProvider(event);
|
|
6001
|
+
const finishReason = llmFinishReason(event);
|
|
6002
|
+
if (allowedModels && (!model || !allowedModels.has(model))) {
|
|
6003
|
+
findings.push(
|
|
6004
|
+
failFinding("llm.usage", `LLM model ${model ?? "unknown"} is not allowed.`, [eventEvidence(event, "attributes.model")], [...allowedModels].sort(), model ?? "unknown")
|
|
6005
|
+
);
|
|
6006
|
+
}
|
|
6007
|
+
if (allowedProviders && (!provider || !allowedProviders.has(provider))) {
|
|
6008
|
+
findings.push(
|
|
6009
|
+
failFinding("llm.usage", `LLM provider ${provider ?? "unknown"} is not allowed.`, [eventEvidence(event, "attributes.provider")], [...allowedProviders].sort(), provider ?? "unknown")
|
|
6010
|
+
);
|
|
6011
|
+
}
|
|
6012
|
+
if (finishReasons && (!finishReason || !finishReasons.has(finishReason))) {
|
|
6013
|
+
findings.push(
|
|
6014
|
+
failFinding("llm.usage", `LLM finish reason ${finishReason ?? "unknown"} is not allowed.`, [eventEvidence(event, "attributes.finishReason")], [...finishReasons].sort(), finishReason ?? "unknown")
|
|
6015
|
+
);
|
|
6016
|
+
}
|
|
6017
|
+
}
|
|
6018
|
+
const tokenTotals = llms.reduce(
|
|
6019
|
+
(totals, event) => ({
|
|
6020
|
+
input: totals.input + (event.tokenUsage?.input ?? 0),
|
|
6021
|
+
output: totals.output + (event.tokenUsage?.output ?? 0),
|
|
6022
|
+
total: totals.total + (event.tokenUsage?.total ?? 0),
|
|
6023
|
+
cached: totals.cached + (event.tokenUsage?.cached ?? 0)
|
|
6024
|
+
}),
|
|
6025
|
+
{ input: 0, output: 0, total: 0, cached: 0 }
|
|
6026
|
+
);
|
|
6027
|
+
const tokenLimits = [
|
|
6028
|
+
["input", options.maxInputTokens],
|
|
6029
|
+
["output", options.maxOutputTokens],
|
|
6030
|
+
["total", options.maxTotalTokens],
|
|
6031
|
+
["cached", options.maxCachedTokens]
|
|
6032
|
+
];
|
|
6033
|
+
for (const [key, limit] of tokenLimits) {
|
|
6034
|
+
if (limit !== void 0 && tokenTotals[key] > limit) {
|
|
6035
|
+
findings.push(
|
|
6036
|
+
failFinding(
|
|
6037
|
+
"llm.usage",
|
|
6038
|
+
`LLM ${key} token count ${tokenTotals[key]} exceeded ${limit}.`,
|
|
6039
|
+
llms.map((event) => eventEvidence(event, `tokenUsage.${key}`)),
|
|
6040
|
+
{ [`max${key[0].toUpperCase()}${key.slice(1)}Tokens`]: limit },
|
|
6041
|
+
tokenTotals[key]
|
|
6042
|
+
)
|
|
6043
|
+
);
|
|
6044
|
+
}
|
|
6045
|
+
}
|
|
6046
|
+
return findings;
|
|
6047
|
+
}
|
|
6048
|
+
};
|
|
6049
|
+
}
|
|
6050
|
+
function createObservedOutcomeRule(options = {}) {
|
|
6051
|
+
const failOn = options.failOn ?? ["failed"];
|
|
6052
|
+
return {
|
|
6053
|
+
id: "outcome.status",
|
|
6054
|
+
category: "run",
|
|
6055
|
+
defaultSeverity: "error",
|
|
6056
|
+
evaluate(context) {
|
|
6057
|
+
const outcomes = extractOutcomesFromPersistedEvents(context.events);
|
|
6058
|
+
const matching = outcomesMatchingStatus(outcomes, failOn);
|
|
6059
|
+
if (matching.length === 0) return [];
|
|
6060
|
+
return [
|
|
6061
|
+
failFinding(
|
|
6062
|
+
"outcome.status",
|
|
6063
|
+
`Observed outcome count ${matching.length} matched [${failOn.join(", ")}].`,
|
|
6064
|
+
matching.map((outcome) => ({
|
|
6065
|
+
runId: outcome.runId,
|
|
6066
|
+
eventId: outcome.outcomeId,
|
|
6067
|
+
...outcome.parentId !== void 0 ? { parentId: outcome.parentId } : {},
|
|
6068
|
+
kind: "OUTCOME",
|
|
6069
|
+
name: outcome.name,
|
|
6070
|
+
status: outcome.status,
|
|
6071
|
+
path: `outcome.${outcome.name}`
|
|
6072
|
+
})),
|
|
6073
|
+
{ failOn },
|
|
6074
|
+
matching.map((outcome) => ({
|
|
6075
|
+
name: outcome.name,
|
|
6076
|
+
status: outcome.status,
|
|
6077
|
+
expectation: outcome.expectation
|
|
6078
|
+
}))
|
|
6079
|
+
)
|
|
6080
|
+
];
|
|
6081
|
+
}
|
|
6082
|
+
};
|
|
6083
|
+
}
|
|
6084
|
+
function runTraceChecks(input, options = {}) {
|
|
6085
|
+
const selected = resolveSelectedRun(input, options.runId);
|
|
6086
|
+
if (selected.diagnostics.length > 0) {
|
|
6087
|
+
return errorResult(input, selected.diagnostics, selected.run);
|
|
6088
|
+
}
|
|
6089
|
+
const rules = selectRules(options.rules ?? [], options.select);
|
|
6090
|
+
if (rules.diagnostics.length > 0) {
|
|
6091
|
+
return errorResult(input, rules.diagnostics, selected.run);
|
|
6092
|
+
}
|
|
6093
|
+
const facts = buildFacts2(input, selected.run);
|
|
6094
|
+
const context = {
|
|
6095
|
+
...facts,
|
|
6096
|
+
...selected.run ? { selectedRun: selected.run } : {},
|
|
6097
|
+
...input.sourceLabel ? { sourceLabel: input.sourceLabel } : {}
|
|
6098
|
+
};
|
|
6099
|
+
const diagnostics = [];
|
|
6100
|
+
const findings = [];
|
|
6101
|
+
for (const rule of rules.rules) {
|
|
6102
|
+
try {
|
|
6103
|
+
findings.push(...rule.evaluate(context).map((finding) => normalizeFinding(rule, finding)));
|
|
6104
|
+
} catch (error) {
|
|
6105
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6106
|
+
diagnostics.push(
|
|
6107
|
+
diagnostic3("AI_CHECK_INTERNAL_ERROR", `Rule ${rule.id} failed: ${message}`, rule.id)
|
|
6108
|
+
);
|
|
6109
|
+
}
|
|
6110
|
+
}
|
|
6111
|
+
if (diagnostics.length > 0) {
|
|
6112
|
+
return errorResult(input, diagnostics, selected.run);
|
|
6113
|
+
}
|
|
6114
|
+
const eventById = new Map(input.read.events.map((event) => [event.eventId, event]));
|
|
6115
|
+
const sortedFindings = findings.sort(compareFindings(eventById));
|
|
6116
|
+
const summary = summarize(sortedFindings, diagnostics);
|
|
6117
|
+
const status = summary.failed > 0 ? "fail" : "pass";
|
|
6118
|
+
return {
|
|
6119
|
+
ok: status === "pass",
|
|
6120
|
+
status,
|
|
6121
|
+
format: input.read.format,
|
|
6122
|
+
...selected.run ? { runId: selected.run.runId } : {},
|
|
6123
|
+
summary,
|
|
6124
|
+
findings: sortedFindings,
|
|
6125
|
+
diagnostics
|
|
6126
|
+
};
|
|
6127
|
+
}
|
|
6128
|
+
|
|
6129
|
+
// packages/core/src/persisted/token-usage.ts
|
|
6130
|
+
function isRecord8(value) {
|
|
6131
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6132
|
+
}
|
|
6133
|
+
function nonNegativeFinite(value) {
|
|
6134
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
6135
|
+
}
|
|
6136
|
+
function normalizeTokenUsage(value) {
|
|
6137
|
+
if (!isRecord8(value)) return void 0;
|
|
6138
|
+
const input = nonNegativeFinite(value.input);
|
|
6139
|
+
const output = nonNegativeFinite(value.output);
|
|
6140
|
+
const suppliedTotal = nonNegativeFinite(value.total);
|
|
6141
|
+
const cached = nonNegativeFinite(value.cached);
|
|
6142
|
+
const derivedTotal = input !== void 0 && output !== void 0 && Number.isFinite(input + output) ? input + output : void 0;
|
|
6143
|
+
const total = suppliedTotal ?? derivedTotal;
|
|
6144
|
+
if (input === void 0 && output === void 0 && total === void 0 && cached === void 0) {
|
|
6145
|
+
return void 0;
|
|
6146
|
+
}
|
|
6147
|
+
return {
|
|
6148
|
+
...input !== void 0 ? { input } : {},
|
|
6149
|
+
...output !== void 0 ? { output } : {},
|
|
6150
|
+
...total !== void 0 ? { total } : {},
|
|
6151
|
+
...cached !== void 0 ? { cached } : {}
|
|
6152
|
+
};
|
|
6153
|
+
}
|
|
6154
|
+
|
|
6155
|
+
// packages/core/src/persisted/from-trace-event.ts
|
|
6156
|
+
function sanitizeIdPart(value) {
|
|
6157
|
+
return value.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
6158
|
+
}
|
|
6159
|
+
function nodeIdForEvent(event) {
|
|
6160
|
+
switch (event.event) {
|
|
6161
|
+
case "run_started":
|
|
6162
|
+
case "run_completed":
|
|
6163
|
+
return event.runId;
|
|
6164
|
+
case "step_started":
|
|
6165
|
+
case "step_completed":
|
|
6166
|
+
return event.stepId;
|
|
6167
|
+
case "outcome_observed":
|
|
6168
|
+
return event.outcomeId;
|
|
6169
|
+
default:
|
|
6170
|
+
return "unknown";
|
|
6171
|
+
}
|
|
6172
|
+
}
|
|
6173
|
+
function createPersistedEventId(event, eventIndex) {
|
|
6174
|
+
const runId = sanitizeIdPart(event.runId);
|
|
6175
|
+
const ev = sanitizeIdPart(event.event);
|
|
6176
|
+
const node = sanitizeIdPart(nodeIdForEvent(event));
|
|
6177
|
+
return `manual:${runId}:${ev}:${node}:${eventIndex}`;
|
|
6178
|
+
}
|
|
6179
|
+
function toIsoTimestamp(ms) {
|
|
6180
|
+
if (typeof ms !== "number" || !Number.isFinite(ms)) {
|
|
6181
|
+
return { iso: (/* @__PURE__ */ new Date(0)).toISOString(), invalidTimestamp: true };
|
|
6182
|
+
}
|
|
6183
|
+
return { iso: new Date(ms).toISOString(), invalidTimestamp: false };
|
|
6184
|
+
}
|
|
6185
|
+
function buildSource(options) {
|
|
6186
|
+
return {
|
|
6187
|
+
type: "manual",
|
|
6188
|
+
name: options?.sourceName ?? "trace-event",
|
|
6189
|
+
version: options?.sourceVersion ?? "0.1"
|
|
6190
|
+
};
|
|
6191
|
+
}
|
|
6192
|
+
function mapStepTypeToInspectKind(type) {
|
|
6193
|
+
switch (type) {
|
|
6194
|
+
case "run":
|
|
6195
|
+
return "RUN";
|
|
6196
|
+
case "llm":
|
|
6197
|
+
return "LLM";
|
|
6198
|
+
case "tool":
|
|
6199
|
+
return "TOOL";
|
|
6200
|
+
case "decision":
|
|
6201
|
+
return "DECISION";
|
|
6202
|
+
case "logic":
|
|
6203
|
+
case "state":
|
|
6204
|
+
case "custom":
|
|
6205
|
+
return "LOGIC";
|
|
6206
|
+
default:
|
|
6207
|
+
return "LOGIC";
|
|
6208
|
+
}
|
|
6209
|
+
}
|
|
6210
|
+
function mapRunOrStepStatus(status) {
|
|
6211
|
+
return status === "success" ? "ok" : "error";
|
|
6212
|
+
}
|
|
6213
|
+
function mapErrorInfo(error) {
|
|
6214
|
+
if (!error?.message) {
|
|
6215
|
+
return {};
|
|
6216
|
+
}
|
|
6217
|
+
const out = {
|
|
6218
|
+
persisted: {
|
|
6219
|
+
message: error.message,
|
|
6220
|
+
name: "Error"
|
|
6221
|
+
}
|
|
6222
|
+
};
|
|
6223
|
+
if (typeof error.stack === "string" && error.stack.length > 0) {
|
|
6224
|
+
out.errorStack = error.stack;
|
|
6225
|
+
}
|
|
6226
|
+
return out;
|
|
6227
|
+
}
|
|
6228
|
+
function mapTokenUsageFromMetadata(metadata) {
|
|
6229
|
+
return normalizeTokenUsage(metadata?.tokens);
|
|
6230
|
+
}
|
|
6231
|
+
function compactAttributes(entries) {
|
|
6232
|
+
const out = {};
|
|
6233
|
+
for (const [key, value] of Object.entries(entries)) {
|
|
6234
|
+
if (value !== void 0) {
|
|
6235
|
+
out[key] = value;
|
|
6236
|
+
}
|
|
6237
|
+
}
|
|
6238
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
6239
|
+
}
|
|
6240
|
+
function traceEventToPersistedInspectEvent(event, options) {
|
|
6241
|
+
const eventIndex = options?.eventIndex ?? 0;
|
|
6242
|
+
const eventId = createPersistedEventId(event, eventIndex);
|
|
6243
|
+
const source = buildSource(options);
|
|
6244
|
+
const tsMain = toIsoTimestamp(event.timestamp);
|
|
6245
|
+
switch (event.event) {
|
|
6246
|
+
case "run_started": {
|
|
6247
|
+
const tsStart = toIsoTimestamp(event.startTime);
|
|
6248
|
+
const correlation = extractCorrelationMetadata(event.metadata);
|
|
6249
|
+
const attributes = compactAttributes({
|
|
6250
|
+
legacyEvent: "run_started",
|
|
6251
|
+
metadata: event.metadata !== void 0 ? { ...event.metadata } : void 0,
|
|
6252
|
+
correlationId: correlation?.correlationId,
|
|
6253
|
+
requestId: correlation?.requestId,
|
|
6254
|
+
decisionId: correlation?.decisionId,
|
|
6255
|
+
groupId: correlation?.groupId,
|
|
6256
|
+
invalidTimestamp: tsMain.invalidTimestamp || tsStart.invalidTimestamp ? true : void 0
|
|
6257
|
+
});
|
|
6258
|
+
return {
|
|
6259
|
+
schemaVersion: "0.2",
|
|
6260
|
+
eventId,
|
|
6261
|
+
runId: event.runId,
|
|
6262
|
+
kind: "RUN",
|
|
6263
|
+
name: event.name,
|
|
6264
|
+
status: "running",
|
|
6265
|
+
timestamp: tsMain.iso,
|
|
6266
|
+
startedAt: tsStart.iso,
|
|
6267
|
+
confidence: "explicit",
|
|
6268
|
+
source,
|
|
6269
|
+
attributes
|
|
6270
|
+
};
|
|
6271
|
+
}
|
|
6272
|
+
case "run_completed": {
|
|
6273
|
+
const tsEnd = toIsoTimestamp(event.endTime);
|
|
6274
|
+
const { persisted: error, errorStack } = mapErrorInfo(event.error);
|
|
6275
|
+
const attributes = compactAttributes({
|
|
6276
|
+
legacyEvent: "run_completed",
|
|
6277
|
+
errorStack,
|
|
6278
|
+
invalidTimestamp: tsMain.invalidTimestamp || tsEnd.invalidTimestamp ? true : void 0
|
|
6279
|
+
});
|
|
6280
|
+
return {
|
|
6281
|
+
schemaVersion: "0.2",
|
|
6282
|
+
eventId,
|
|
6283
|
+
runId: event.runId,
|
|
6284
|
+
kind: "RUN",
|
|
6285
|
+
name: "run",
|
|
6286
|
+
status: mapRunOrStepStatus(event.status),
|
|
6287
|
+
timestamp: tsMain.iso,
|
|
6288
|
+
endedAt: tsEnd.iso,
|
|
6289
|
+
durationMs: event.durationMs,
|
|
6290
|
+
confidence: "explicit",
|
|
6291
|
+
source,
|
|
6292
|
+
attributes,
|
|
6293
|
+
error
|
|
6294
|
+
};
|
|
6295
|
+
}
|
|
6296
|
+
case "step_started": {
|
|
6297
|
+
const tsStart = toIsoTimestamp(event.startTime);
|
|
6298
|
+
const tokenUsage = mapTokenUsageFromMetadata(event.metadata);
|
|
6299
|
+
const attributes = compactAttributes({
|
|
6300
|
+
legacyEvent: "step_started",
|
|
6301
|
+
stepId: event.stepId,
|
|
6302
|
+
stepType: event.type,
|
|
6303
|
+
metadata: event.metadata !== void 0 ? { ...event.metadata } : void 0,
|
|
6304
|
+
invalidTimestamp: tsMain.invalidTimestamp || tsStart.invalidTimestamp ? true : void 0
|
|
6305
|
+
});
|
|
6306
|
+
const out = {
|
|
6307
|
+
schemaVersion: "0.2",
|
|
6308
|
+
eventId,
|
|
6309
|
+
runId: event.runId,
|
|
6310
|
+
kind: mapStepTypeToInspectKind(event.type),
|
|
6311
|
+
name: event.name,
|
|
6312
|
+
status: "running",
|
|
6313
|
+
timestamp: tsMain.iso,
|
|
6314
|
+
startedAt: tsStart.iso,
|
|
6315
|
+
confidence: "explicit",
|
|
6316
|
+
source,
|
|
6317
|
+
attributes
|
|
6318
|
+
};
|
|
6319
|
+
if (event.parentId !== void 0) {
|
|
6320
|
+
out.parentId = event.parentId;
|
|
6321
|
+
}
|
|
6322
|
+
if (tokenUsage !== void 0) {
|
|
6323
|
+
out.tokenUsage = tokenUsage;
|
|
6324
|
+
}
|
|
6325
|
+
return out;
|
|
6326
|
+
}
|
|
6327
|
+
case "step_completed": {
|
|
6328
|
+
const tsEnd = toIsoTimestamp(event.endTime);
|
|
6329
|
+
const { persisted: error, errorStack } = mapErrorInfo(event.error);
|
|
6330
|
+
const attributes = compactAttributes({
|
|
6331
|
+
legacyEvent: "step_completed",
|
|
6332
|
+
stepId: event.stepId,
|
|
6333
|
+
errorStack,
|
|
6334
|
+
invalidTimestamp: tsMain.invalidTimestamp || tsEnd.invalidTimestamp ? true : void 0
|
|
6335
|
+
});
|
|
6336
|
+
return {
|
|
6337
|
+
schemaVersion: "0.2",
|
|
6338
|
+
eventId,
|
|
6339
|
+
runId: event.runId,
|
|
6340
|
+
kind: "LOGIC",
|
|
6341
|
+
name: event.stepId,
|
|
6342
|
+
status: mapRunOrStepStatus(event.status),
|
|
6343
|
+
timestamp: tsMain.iso,
|
|
6344
|
+
endedAt: tsEnd.iso,
|
|
6345
|
+
durationMs: event.durationMs,
|
|
6346
|
+
confidence: "explicit",
|
|
6347
|
+
source,
|
|
6348
|
+
attributes,
|
|
6349
|
+
error
|
|
6350
|
+
};
|
|
6351
|
+
}
|
|
6352
|
+
case "outcome_observed": {
|
|
6353
|
+
const tsObserved = toIsoTimestamp(event.observedAt);
|
|
6354
|
+
const attributes = compactAttributes({
|
|
6355
|
+
legacyEvent: "outcome_observed",
|
|
6356
|
+
outcomeId: event.outcomeId,
|
|
6357
|
+
outcomeStatus: event.status,
|
|
6358
|
+
expectation: event.expectation,
|
|
6359
|
+
method: event.method,
|
|
6360
|
+
actual: event.actual,
|
|
6361
|
+
evidence: event.evidence,
|
|
6362
|
+
observedAt: tsObserved.iso,
|
|
6363
|
+
invalidTimestamp: tsMain.invalidTimestamp || tsObserved.invalidTimestamp ? true : void 0
|
|
6364
|
+
});
|
|
6365
|
+
const out = {
|
|
6366
|
+
schemaVersion: "0.2",
|
|
6367
|
+
eventId,
|
|
6368
|
+
runId: event.runId,
|
|
6369
|
+
kind: "OUTCOME",
|
|
6370
|
+
name: event.name,
|
|
6371
|
+
status: event.status === "failed" ? "error" : "ok",
|
|
6372
|
+
timestamp: tsMain.iso,
|
|
6373
|
+
confidence: "explicit",
|
|
6374
|
+
source,
|
|
6375
|
+
attributes
|
|
6376
|
+
};
|
|
6377
|
+
if (event.parentId !== void 0) {
|
|
6378
|
+
out.parentId = event.parentId;
|
|
6379
|
+
}
|
|
6380
|
+
if (event.actual !== void 0) {
|
|
6381
|
+
out.outputSummary = event.actual;
|
|
6382
|
+
}
|
|
6383
|
+
return out;
|
|
6384
|
+
}
|
|
6385
|
+
default: {
|
|
6386
|
+
const _exhaustive = event;
|
|
6387
|
+
throw new Error(`Unsupported trace event: ${_exhaustive.event}`);
|
|
6388
|
+
}
|
|
6389
|
+
}
|
|
6390
|
+
}
|
|
6391
|
+
function traceEventsToPersistedInspectEvents(events, options) {
|
|
6392
|
+
return events.map(
|
|
6393
|
+
(event, index) => traceEventToPersistedInspectEvent(event, { ...options, eventIndex: index })
|
|
6394
|
+
);
|
|
6395
|
+
}
|
|
6396
|
+
|
|
6397
|
+
// packages/core/src/logs/tree-builder.ts
|
|
6398
|
+
function inc(map, key) {
|
|
6399
|
+
map[key] = (map[key] ?? 0) + 1;
|
|
6400
|
+
}
|
|
6401
|
+
function computeRunStatus(events) {
|
|
6402
|
+
let hasRunning = false;
|
|
6403
|
+
for (const e of events) {
|
|
6404
|
+
if (e.status === "error") return "error";
|
|
6405
|
+
if (e.status === "running") hasRunning = true;
|
|
6406
|
+
}
|
|
6407
|
+
if (hasRunning) return "running";
|
|
6408
|
+
return "ok";
|
|
6409
|
+
}
|
|
6410
|
+
var TreeBuilder = class {
|
|
6411
|
+
constructor(options) {
|
|
6412
|
+
void options?.config;
|
|
6413
|
+
}
|
|
6414
|
+
build(events) {
|
|
6415
|
+
const byRun = /* @__PURE__ */ new Map();
|
|
6416
|
+
for (const e of events) {
|
|
6417
|
+
if (!byRun.has(e.runId)) byRun.set(e.runId, []);
|
|
6418
|
+
byRun.get(e.runId).push(e);
|
|
6419
|
+
}
|
|
6420
|
+
const out = [];
|
|
6421
|
+
for (const [runId, runEvents] of byRun.entries()) {
|
|
6422
|
+
const sorted = [...runEvents].sort((a, b) => a.timestamp - b.timestamp);
|
|
6423
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
6424
|
+
for (const e of sorted) {
|
|
6425
|
+
nodes.set(e.eventId, { event: e, children: [], depth: 0 });
|
|
6426
|
+
}
|
|
6427
|
+
const roots = [];
|
|
6428
|
+
for (const node of nodes.values()) {
|
|
6429
|
+
const parentId = node.event.parentId;
|
|
6430
|
+
if (parentId && nodes.has(parentId)) {
|
|
6431
|
+
nodes.get(parentId).children.push(node);
|
|
6432
|
+
} else {
|
|
6433
|
+
roots.push(node);
|
|
6434
|
+
}
|
|
6435
|
+
}
|
|
6436
|
+
const assignDepth = (n, depth) => {
|
|
6437
|
+
n.depth = depth;
|
|
6438
|
+
for (const c of n.children) assignDepth(c, depth + 1);
|
|
6439
|
+
};
|
|
6440
|
+
for (const r of roots) assignDepth(r, 0);
|
|
6441
|
+
const confidenceBreakdown = {
|
|
6442
|
+
explicit: 0,
|
|
6443
|
+
correlated: 0,
|
|
6444
|
+
heuristic: 0,
|
|
6445
|
+
unknown: 0
|
|
6446
|
+
};
|
|
6447
|
+
const kinds = {};
|
|
6448
|
+
for (const e of sorted) {
|
|
6449
|
+
inc(confidenceBreakdown, e.confidence);
|
|
6450
|
+
kinds[e.kind] = (kinds[e.kind] ?? 0) + 1;
|
|
6451
|
+
}
|
|
6452
|
+
const startedAt = sorted.length > 0 ? sorted[0].timestamp : void 0;
|
|
6453
|
+
const endedAt = sorted.length > 0 ? sorted[sorted.length - 1].timestamp : void 0;
|
|
6454
|
+
const status = computeRunStatus(sorted);
|
|
6455
|
+
const durationMs2 = startedAt !== void 0 && endedAt !== void 0 && Number.isFinite(startedAt) && Number.isFinite(endedAt) && endedAt >= startedAt && status !== "running" ? endedAt - startedAt : void 0;
|
|
6456
|
+
const name = sorted.find((e) => e.kind === "RUN")?.name;
|
|
6457
|
+
out.push({
|
|
6458
|
+
runId,
|
|
6459
|
+
name,
|
|
6460
|
+
status,
|
|
6461
|
+
startedAt,
|
|
6462
|
+
endedAt: status === "running" ? void 0 : endedAt,
|
|
6463
|
+
durationMs: durationMs2,
|
|
6464
|
+
children: roots,
|
|
6465
|
+
metadata: {
|
|
6466
|
+
totalEvents: sorted.length,
|
|
6467
|
+
confidenceBreakdown,
|
|
6468
|
+
kinds
|
|
6469
|
+
}
|
|
6470
|
+
});
|
|
6471
|
+
}
|
|
6472
|
+
out.sort((a, b) => (b.startedAt ?? 0) - (a.startedAt ?? 0));
|
|
6473
|
+
return out;
|
|
6474
|
+
}
|
|
6475
|
+
};
|
|
6476
|
+
|
|
6477
|
+
// packages/core/src/persisted/to-inspect-event.ts
|
|
6478
|
+
function compactAttributes2(entries) {
|
|
6479
|
+
const out = {};
|
|
6480
|
+
for (const [key, value] of Object.entries(entries)) {
|
|
6481
|
+
if (value !== void 0) {
|
|
6482
|
+
out[key] = value;
|
|
6483
|
+
}
|
|
6484
|
+
}
|
|
6485
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
6486
|
+
}
|
|
6487
|
+
function parseIsoToMs3(iso) {
|
|
6488
|
+
const parsed = Date.parse(iso);
|
|
6489
|
+
if (!Number.isFinite(parsed)) {
|
|
6490
|
+
return { ms: 0, invalidTimestamp: true };
|
|
6491
|
+
}
|
|
6492
|
+
return { ms: parsed, invalidTimestamp: false };
|
|
6493
|
+
}
|
|
6494
|
+
function mapPersistedSourceToInspect(event) {
|
|
6495
|
+
const attrs = event.attributes ?? {};
|
|
6496
|
+
const sourceName = event.source.name;
|
|
6497
|
+
if (sourceName === "pino") {
|
|
6498
|
+
return {
|
|
6499
|
+
type: "pino",
|
|
6500
|
+
file: typeof attrs.sourceFile === "string" ? attrs.sourceFile : void 0,
|
|
6501
|
+
line: typeof attrs.sourceLine === "number" ? attrs.sourceLine : void 0
|
|
6502
|
+
};
|
|
6503
|
+
}
|
|
6504
|
+
if (sourceName === "winston") {
|
|
6505
|
+
return {
|
|
6506
|
+
type: "winston",
|
|
6507
|
+
file: typeof attrs.sourceFile === "string" ? attrs.sourceFile : void 0,
|
|
6508
|
+
line: typeof attrs.sourceLine === "number" ? attrs.sourceLine : void 0
|
|
6509
|
+
};
|
|
6510
|
+
}
|
|
6511
|
+
const mapType = (t) => {
|
|
6512
|
+
switch (t) {
|
|
6513
|
+
case "manual":
|
|
6514
|
+
return "manual";
|
|
6515
|
+
case "json-log":
|
|
6516
|
+
return "json-log";
|
|
6517
|
+
case "log4js":
|
|
6518
|
+
return "log4js";
|
|
6519
|
+
case "adapter":
|
|
6520
|
+
case "ai-sdk":
|
|
6521
|
+
case "otel":
|
|
6522
|
+
return "adapter";
|
|
6523
|
+
default:
|
|
6524
|
+
return "json-log";
|
|
6525
|
+
}
|
|
6526
|
+
};
|
|
6527
|
+
return {
|
|
6528
|
+
type: mapType(event.source.type),
|
|
6529
|
+
file: typeof attrs.sourceFile === "string" ? attrs.sourceFile : void 0,
|
|
6530
|
+
line: typeof attrs.sourceLine === "number" ? attrs.sourceLine : void 0
|
|
6531
|
+
};
|
|
6532
|
+
}
|
|
6533
|
+
function buildInspectAttributes(event) {
|
|
6534
|
+
const attrs = event.attributes !== void 0 ? { ...event.attributes } : {};
|
|
6535
|
+
if (event.inputSummary !== void 0) {
|
|
6536
|
+
attrs.inputSummary = event.inputSummary;
|
|
6537
|
+
}
|
|
6538
|
+
if (event.outputSummary !== void 0) {
|
|
6539
|
+
attrs.outputSummary = event.outputSummary;
|
|
6540
|
+
}
|
|
6541
|
+
if (event.error) {
|
|
6542
|
+
if (event.error.name !== void 0) {
|
|
6543
|
+
attrs.errorName = event.error.name;
|
|
6544
|
+
}
|
|
6545
|
+
attrs.errorMessage = event.error.message;
|
|
6546
|
+
if (event.error.code !== void 0) {
|
|
6547
|
+
attrs.errorCode = event.error.code;
|
|
6548
|
+
}
|
|
6549
|
+
}
|
|
6550
|
+
if (event.tokenUsage) {
|
|
6551
|
+
attrs.tokens = { ...event.tokenUsage };
|
|
6552
|
+
}
|
|
6553
|
+
if (event.source.type === "ai-sdk" || event.source.type === "otel") {
|
|
6554
|
+
attrs.originalSourceType = event.source.type;
|
|
6555
|
+
}
|
|
6556
|
+
if (event.source.name !== void 0) {
|
|
6557
|
+
attrs.sourceName = event.source.name;
|
|
6558
|
+
}
|
|
6559
|
+
if (event.source.version !== void 0) {
|
|
6560
|
+
attrs.sourceVersion = event.source.version;
|
|
6561
|
+
}
|
|
6562
|
+
return attrs;
|
|
6563
|
+
}
|
|
6564
|
+
function persistedInspectEventToInspectEvent(event) {
|
|
6565
|
+
if (!isPersistedInspectEvent(event)) {
|
|
6566
|
+
throw new Error("Invalid PersistedInspectEvent: failed isPersistedInspectEvent");
|
|
6567
|
+
}
|
|
6568
|
+
const ts = parseIsoToMs3(event.timestamp);
|
|
6569
|
+
const attrs = buildInspectAttributes(event);
|
|
6570
|
+
if (ts.invalidTimestamp) {
|
|
6571
|
+
attrs.invalidTimestamp = true;
|
|
6572
|
+
}
|
|
6573
|
+
let status;
|
|
6574
|
+
if (event.status === "running" || event.status === "ok" || event.status === "error") {
|
|
6575
|
+
status = event.status;
|
|
6576
|
+
} else if (event.status === "unknown") {
|
|
6577
|
+
attrs.persistedStatus = "unknown";
|
|
6578
|
+
}
|
|
6579
|
+
const out = {
|
|
6580
|
+
eventId: event.eventId,
|
|
6581
|
+
runId: event.runId,
|
|
6582
|
+
name: event.name,
|
|
6583
|
+
kind: event.kind,
|
|
6584
|
+
timestamp: ts.ms,
|
|
6585
|
+
confidence: event.confidence,
|
|
6586
|
+
source: mapPersistedSourceToInspect(event),
|
|
6587
|
+
attributes: compactAttributes2(attrs)
|
|
6588
|
+
};
|
|
6589
|
+
if (event.parentId !== void 0) {
|
|
6590
|
+
out.parentId = event.parentId;
|
|
6591
|
+
}
|
|
6592
|
+
if (status !== void 0) {
|
|
6593
|
+
out.status = status;
|
|
6594
|
+
}
|
|
6595
|
+
if (event.durationMs !== void 0 && Number.isFinite(event.durationMs) && event.durationMs >= 0) {
|
|
6596
|
+
out.durationMs = event.durationMs;
|
|
6597
|
+
}
|
|
6598
|
+
return out;
|
|
6599
|
+
}
|
|
6600
|
+
function persistedInspectEventsToInspectEvents(events, options) {
|
|
6601
|
+
const skipInvalid = options?.skipInvalid === true;
|
|
6602
|
+
const out = [];
|
|
6603
|
+
for (const event of events) {
|
|
6604
|
+
if (!isPersistedInspectEvent(event)) {
|
|
6605
|
+
if (skipInvalid) {
|
|
6606
|
+
continue;
|
|
6607
|
+
}
|
|
6608
|
+
throw new Error("Invalid PersistedInspectEvent: failed isPersistedInspectEvent");
|
|
6609
|
+
}
|
|
6610
|
+
out.push(persistedInspectEventToInspectEvent(event));
|
|
6611
|
+
}
|
|
6612
|
+
return out;
|
|
6613
|
+
}
|
|
6614
|
+
|
|
6615
|
+
// packages/core/src/persisted/tree-bridge.ts
|
|
6616
|
+
function persistedInspectEventsToRunTrees(events, options) {
|
|
6617
|
+
const inspectEvents = persistedInspectEventsToInspectEvents(events, {
|
|
6618
|
+
skipInvalid: options?.skipInvalid
|
|
6619
|
+
});
|
|
6620
|
+
return new TreeBuilder().build(inspectEvents);
|
|
6621
|
+
}
|
|
6622
|
+
|
|
6623
|
+
// packages/core/src/readers/index.ts
|
|
6624
|
+
var DEFAULT_MAX_TRACE_INPUT_BYTES = 10 * 1024 * 1024;
|
|
6625
|
+
var MIN_DETECTION_CONFIDENCE = 0.5;
|
|
6626
|
+
var AMBIGUOUS_CONFIDENCE_DELTA = 0.05;
|
|
6627
|
+
var resolvedInputCache = /* @__PURE__ */ new WeakMap();
|
|
6628
|
+
var OPENINFERENCE_READER_FORMAT = "openinference-json";
|
|
6629
|
+
var OTLP_READER_FORMAT = "otlp-json";
|
|
6630
|
+
var OPENINFERENCE_SPAN_KEYS = /* @__PURE__ */ new Set([
|
|
6631
|
+
"trace_id",
|
|
6632
|
+
"traceId",
|
|
6633
|
+
"span_id",
|
|
6634
|
+
"spanId",
|
|
6635
|
+
"parent_span_id",
|
|
6636
|
+
"parentSpanId",
|
|
6637
|
+
"name",
|
|
6638
|
+
"start_time_unix_nano",
|
|
6639
|
+
"startTimeUnixNano",
|
|
6640
|
+
"end_time_unix_nano",
|
|
6641
|
+
"endTimeUnixNano",
|
|
6642
|
+
"start_time",
|
|
6643
|
+
"startTime",
|
|
6644
|
+
"end_time",
|
|
6645
|
+
"endTime",
|
|
6646
|
+
"attributes",
|
|
6647
|
+
"status",
|
|
6648
|
+
"kind",
|
|
6649
|
+
"span_kind",
|
|
6650
|
+
"spanKind"
|
|
6651
|
+
]);
|
|
6652
|
+
var OPENINFERENCE_SENSITIVE_ATTRIBUTE_KEYS = [
|
|
6653
|
+
"input.value",
|
|
6654
|
+
"output.value",
|
|
6655
|
+
"input.mime_type",
|
|
6656
|
+
"output.mime_type",
|
|
6657
|
+
"llm.input_messages",
|
|
6658
|
+
"llm.output_messages",
|
|
6659
|
+
"llm.prompts",
|
|
6660
|
+
"llm.completions",
|
|
6661
|
+
"retrieval.documents",
|
|
6662
|
+
"reranker.input_documents",
|
|
6663
|
+
"reranker.output_documents",
|
|
6664
|
+
"document.content",
|
|
6665
|
+
"gen_ai.prompt",
|
|
6666
|
+
"gen_ai.completion",
|
|
6667
|
+
"gen_ai.input.messages",
|
|
6668
|
+
"gen_ai.output.messages"
|
|
6669
|
+
];
|
|
6670
|
+
var OTLP_SPAN_KEYS = /* @__PURE__ */ new Set([
|
|
6671
|
+
"traceId",
|
|
6672
|
+
"spanId",
|
|
6673
|
+
"parentSpanId",
|
|
6674
|
+
"name",
|
|
6675
|
+
"kind",
|
|
6676
|
+
"startTimeUnixNano",
|
|
6677
|
+
"endTimeUnixNano",
|
|
6678
|
+
"attributes",
|
|
6679
|
+
"events",
|
|
6680
|
+
"status",
|
|
6681
|
+
"droppedAttributesCount",
|
|
6682
|
+
"droppedEventsCount",
|
|
6683
|
+
"droppedLinksCount",
|
|
6684
|
+
"links",
|
|
6685
|
+
"flags"
|
|
6686
|
+
]);
|
|
6687
|
+
var TraceReadError = class extends Error {
|
|
6688
|
+
code;
|
|
6689
|
+
warnings;
|
|
6690
|
+
constructor(code, message, warnings = []) {
|
|
6691
|
+
super(message);
|
|
6692
|
+
this.name = "TraceReadError";
|
|
6693
|
+
this.code = code;
|
|
6694
|
+
this.warnings = warnings;
|
|
6695
|
+
}
|
|
6696
|
+
};
|
|
6697
|
+
function normalizeCandidate(reader, candidate) {
|
|
6698
|
+
const confidence = Number.isFinite(candidate.confidence) ? Math.max(0, Math.min(1, candidate.confidence)) : 0;
|
|
6699
|
+
return {
|
|
6700
|
+
...candidate,
|
|
6701
|
+
format: candidate.format || reader.format,
|
|
6702
|
+
confidence,
|
|
6703
|
+
readerName: candidate.readerName ?? reader.name
|
|
6704
|
+
};
|
|
6705
|
+
}
|
|
6706
|
+
function sortCandidates(candidates) {
|
|
6707
|
+
return [...candidates].sort((a, b) => {
|
|
6708
|
+
if (b.confidence !== a.confidence) return b.confidence - a.confidence;
|
|
6709
|
+
return a.format.localeCompare(b.format);
|
|
6710
|
+
});
|
|
6711
|
+
}
|
|
6712
|
+
function collectWarnings(candidates) {
|
|
6713
|
+
return candidates.flatMap((candidate) => candidate.warnings ?? []);
|
|
6714
|
+
}
|
|
6715
|
+
function dedupeWarnings(warnings) {
|
|
6716
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6717
|
+
const out = [];
|
|
6718
|
+
for (const warning of warnings) {
|
|
6719
|
+
const key = [
|
|
6720
|
+
warning.code,
|
|
6721
|
+
warning.message,
|
|
6722
|
+
warning.severity ?? "",
|
|
6723
|
+
warning.sourceFile ?? "",
|
|
6724
|
+
warning.line ?? "",
|
|
6725
|
+
warning.field ?? ""
|
|
6726
|
+
].join("\0");
|
|
6727
|
+
if (seen.has(key)) continue;
|
|
6728
|
+
seen.add(key);
|
|
6729
|
+
out.push(warning);
|
|
6730
|
+
}
|
|
6731
|
+
return out;
|
|
6732
|
+
}
|
|
6733
|
+
function attachSingleSourceFile(warnings, resolved) {
|
|
6734
|
+
if (resolved.sourceFiles.length !== 1) return [...warnings];
|
|
6735
|
+
const [sourceFile] = resolved.sourceFiles;
|
|
6736
|
+
return warnings.map((warning) => ({
|
|
6737
|
+
...warning,
|
|
6738
|
+
sourceFile: warning.sourceFile ?? sourceFile
|
|
6739
|
+
}));
|
|
6740
|
+
}
|
|
6741
|
+
function findReaderByFormat(format, readers) {
|
|
6742
|
+
return readers.find((reader) => reader.format === format);
|
|
6743
|
+
}
|
|
6744
|
+
async function jsonlFilesInDirectory(dirPath) {
|
|
6745
|
+
const entries = await promises.readdir(dirPath, { withFileTypes: true });
|
|
6746
|
+
return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map((entry) => path__default.default.join(dirPath, entry.name)).sort((a, b) => a.localeCompare(b));
|
|
6747
|
+
}
|
|
6748
|
+
async function resolveInput(input) {
|
|
6749
|
+
const cached = resolvedInputCache.get(input);
|
|
6750
|
+
if (cached) return cached;
|
|
6751
|
+
const promise = resolveInputUncached(input);
|
|
6752
|
+
resolvedInputCache.set(input, promise);
|
|
6753
|
+
return promise;
|
|
6754
|
+
}
|
|
6755
|
+
function assertInputWithinBounds(content, sourceFile) {
|
|
6756
|
+
const bytes = Buffer.byteLength(content, "utf8");
|
|
6757
|
+
if (bytes <= DEFAULT_MAX_TRACE_INPUT_BYTES) return;
|
|
6758
|
+
throw new TraceReadError("unsupported_format", "Trace input exceeds the local reader size limit.", [
|
|
6759
|
+
{
|
|
6760
|
+
code: "input_too_large",
|
|
6761
|
+
message: `Trace input is ${bytes} bytes; max is ${DEFAULT_MAX_TRACE_INPUT_BYTES} bytes.`,
|
|
6762
|
+
severity: "error",
|
|
6763
|
+
...sourceFile !== void 0 ? { sourceFile } : {}
|
|
6764
|
+
}
|
|
6765
|
+
]);
|
|
6766
|
+
}
|
|
6767
|
+
async function resolveInputUncached(input) {
|
|
6768
|
+
if (input.type === "string") {
|
|
6769
|
+
assertInputWithinBounds(input.content);
|
|
6770
|
+
return { content: input.content, sourceFiles: [] };
|
|
6771
|
+
}
|
|
6772
|
+
if (input.type === "buffer") {
|
|
6773
|
+
const content = input.content.toString("utf-8");
|
|
6774
|
+
assertInputWithinBounds(content);
|
|
6775
|
+
return { content, sourceFiles: [] };
|
|
6776
|
+
}
|
|
6777
|
+
if (input.type === "file") {
|
|
6778
|
+
const content = await promises.readFile(input.path, "utf-8");
|
|
6779
|
+
assertInputWithinBounds(content, input.path);
|
|
6780
|
+
return { content, sourceFiles: [input.path] };
|
|
6781
|
+
}
|
|
6782
|
+
if (input.type === "directory") {
|
|
6783
|
+
const files = await jsonlFilesInDirectory(input.path);
|
|
6784
|
+
const parts = await Promise.all(
|
|
6785
|
+
files.map(async (file) => (await promises.readFile(file, "utf-8")).trimEnd())
|
|
6786
|
+
);
|
|
6787
|
+
const content = parts.filter((part) => part.trim() !== "").join("\n");
|
|
6788
|
+
assertInputWithinBounds(content, input.path);
|
|
6789
|
+
return {
|
|
6790
|
+
content,
|
|
6791
|
+
sourceFiles: files
|
|
6792
|
+
};
|
|
6793
|
+
}
|
|
6794
|
+
return void 0;
|
|
6795
|
+
}
|
|
6796
|
+
function detectJsonlFormat(content) {
|
|
6797
|
+
let saw01 = false;
|
|
6798
|
+
let saw02 = false;
|
|
6799
|
+
let saw10 = false;
|
|
6800
|
+
let validRows = 0;
|
|
6801
|
+
let invalidJsonRows = 0;
|
|
6802
|
+
let unknownSchemaRows = 0;
|
|
6803
|
+
let firstInvalidJsonLine;
|
|
6804
|
+
let firstUnknownSchemaLine;
|
|
6805
|
+
let lineNumber = 0;
|
|
6806
|
+
for (const line of content.split(/\r?\n/)) {
|
|
6807
|
+
lineNumber += 1;
|
|
6808
|
+
const trimmed = line.trim();
|
|
6809
|
+
if (trimmed === "") continue;
|
|
6810
|
+
let parsed;
|
|
6811
|
+
try {
|
|
6812
|
+
parsed = JSON.parse(trimmed);
|
|
6813
|
+
} catch {
|
|
6814
|
+
invalidJsonRows += 1;
|
|
6815
|
+
firstInvalidJsonLine ??= lineNumber;
|
|
6816
|
+
continue;
|
|
6817
|
+
}
|
|
6818
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) && "schemaVersion" in parsed) {
|
|
6819
|
+
const version = parsed.schemaVersion;
|
|
6820
|
+
if (version === "0.1") {
|
|
6821
|
+
saw01 = true;
|
|
6822
|
+
validRows += 1;
|
|
6823
|
+
continue;
|
|
6824
|
+
}
|
|
6825
|
+
if (version === "0.2") {
|
|
6826
|
+
saw02 = true;
|
|
6827
|
+
validRows += 1;
|
|
6828
|
+
continue;
|
|
6829
|
+
}
|
|
6830
|
+
if (version === "1.0") {
|
|
6831
|
+
saw10 = true;
|
|
6832
|
+
validRows += 1;
|
|
6833
|
+
continue;
|
|
6834
|
+
}
|
|
6835
|
+
}
|
|
6836
|
+
unknownSchemaRows += 1;
|
|
6837
|
+
firstUnknownSchemaLine ??= lineNumber;
|
|
6838
|
+
}
|
|
6839
|
+
const warnings = [];
|
|
6840
|
+
if (invalidJsonRows > 0) {
|
|
6841
|
+
warnings.push({
|
|
6842
|
+
code: "invalid_jsonl_rows",
|
|
6843
|
+
message: `Skipped ${invalidJsonRows} invalid JSONL row(s) during format detection.`,
|
|
6844
|
+
severity: "warning",
|
|
6845
|
+
...firstInvalidJsonLine !== void 0 ? { line: firstInvalidJsonLine } : {}
|
|
6846
|
+
});
|
|
6847
|
+
}
|
|
6848
|
+
if (unknownSchemaRows > 0) {
|
|
6849
|
+
warnings.push({
|
|
6850
|
+
code: "unknown_schema_rows",
|
|
6851
|
+
message: `Skipped ${unknownSchemaRows} row(s) with unknown schemaVersion during format detection.`,
|
|
6852
|
+
severity: "warning",
|
|
6853
|
+
...firstUnknownSchemaLine !== void 0 ? { line: firstUnknownSchemaLine } : {}
|
|
6854
|
+
});
|
|
6855
|
+
}
|
|
6856
|
+
let format = "empty";
|
|
6857
|
+
const seenFormats = [saw01, saw02, saw10].filter(Boolean).length;
|
|
6858
|
+
if (seenFormats > 1) format = "mixed";
|
|
6859
|
+
else if (saw01) format = "0.1";
|
|
6860
|
+
else if (saw02) format = "0.2";
|
|
6861
|
+
else if (saw10) format = "1.0";
|
|
6862
|
+
return { format, validRows, warnings };
|
|
6863
|
+
}
|
|
6864
|
+
function agentInspectFormatLabel(format) {
|
|
6865
|
+
switch (format) {
|
|
6866
|
+
case "0.1":
|
|
6867
|
+
return "agent-inspect-v0.1-jsonl";
|
|
6868
|
+
case "0.2":
|
|
6869
|
+
return "agent-inspect-v0.2-jsonl";
|
|
6870
|
+
case "1.0":
|
|
6871
|
+
return "agent-inspect-v1.0-jsonl";
|
|
6872
|
+
case "mixed":
|
|
6873
|
+
return "agent-inspect-mixed-jsonl";
|
|
6874
|
+
default:
|
|
6875
|
+
return "agent-inspect-jsonl";
|
|
6876
|
+
}
|
|
6877
|
+
}
|
|
6878
|
+
function persistedEventsForParsedTrace(parsed) {
|
|
6879
|
+
if ((parsed.format === "0.2" || parsed.format === "1.0") && parsed.persisted.length > 0) {
|
|
6880
|
+
return [...parsed.persisted];
|
|
6881
|
+
}
|
|
6882
|
+
if (parsed.format === "mixed" && parsed.rows.length > 0) {
|
|
6883
|
+
return parsed.rows.map((row, index) => {
|
|
6884
|
+
if (row.format === "0.2" || row.format === "1.0") return row.event;
|
|
6885
|
+
return traceEventToPersistedInspectEvent(row.event, {
|
|
6886
|
+
eventIndex: index,
|
|
6887
|
+
sourceName: "agent-inspect-jsonl-reader"
|
|
6888
|
+
});
|
|
6889
|
+
});
|
|
6890
|
+
}
|
|
6891
|
+
return traceEventsToPersistedInspectEvents(parsed.events, {
|
|
6892
|
+
sourceName: "agent-inspect-jsonl-reader"
|
|
6893
|
+
});
|
|
6894
|
+
}
|
|
6895
|
+
function isRecord9(value) {
|
|
6896
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6897
|
+
}
|
|
6898
|
+
function isNonEmptyString4(value) {
|
|
6899
|
+
return typeof value === "string" && value.trim() !== "";
|
|
6900
|
+
}
|
|
6901
|
+
function readStringField(record, keys) {
|
|
6902
|
+
for (const key of keys) {
|
|
6903
|
+
const value = record[key];
|
|
6904
|
+
if (isNonEmptyString4(value)) return value;
|
|
6905
|
+
}
|
|
6906
|
+
return void 0;
|
|
6907
|
+
}
|
|
6908
|
+
function readRecordField(record, key) {
|
|
6909
|
+
const value = record[key];
|
|
6910
|
+
return isRecord9(value) ? value : void 0;
|
|
6911
|
+
}
|
|
6912
|
+
function parseJsonDocument(content) {
|
|
6913
|
+
return JSON.parse(content);
|
|
6914
|
+
}
|
|
6915
|
+
function looksLikeOpenInferenceSpan(value) {
|
|
6916
|
+
if (!isRecord9(value)) return false;
|
|
6917
|
+
const attributes = readRecordField(value, "attributes");
|
|
6918
|
+
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);
|
|
6919
|
+
}
|
|
6920
|
+
function extractOpenInferenceDocument(root) {
|
|
6921
|
+
const warnings = [];
|
|
6922
|
+
const unsupportedFields = [];
|
|
6923
|
+
if (Array.isArray(root)) {
|
|
6924
|
+
const spans = root.filter(looksLikeOpenInferenceSpan);
|
|
6925
|
+
if (spans.length === 0) return void 0;
|
|
6926
|
+
if (spans.length !== root.length) {
|
|
6927
|
+
warnings.push({
|
|
6928
|
+
code: "openinference_skipped_items",
|
|
6929
|
+
message: "Skipped non-span item(s) in OpenInference span array.",
|
|
6930
|
+
severity: "warning"
|
|
6931
|
+
});
|
|
6932
|
+
}
|
|
6933
|
+
return {
|
|
6934
|
+
spans,
|
|
6935
|
+
confidence: 0.82,
|
|
6936
|
+
description: "OpenInference span array",
|
|
6937
|
+
warnings,
|
|
6938
|
+
unsupportedFields
|
|
6939
|
+
};
|
|
6940
|
+
}
|
|
6941
|
+
if (!isRecord9(root)) return void 0;
|
|
6942
|
+
const rootFormat = root.format;
|
|
6943
|
+
const rootCompatibility = root.compatibility;
|
|
6944
|
+
const version = typeof root.version === "string" && root.version.trim() !== "" ? root.version : void 0;
|
|
6945
|
+
if (Array.isArray(root.spans)) {
|
|
6946
|
+
const spans = root.spans.filter(looksLikeOpenInferenceSpan);
|
|
6947
|
+
if (spans.length === 0 && (rootFormat === "openinference" || rootCompatibility === "openinference-compatible")) {
|
|
6948
|
+
warnings.push({
|
|
6949
|
+
code: "openinference_no_valid_spans",
|
|
6950
|
+
message: "OpenInference document did not contain any valid spans.",
|
|
6951
|
+
severity: "error"
|
|
6952
|
+
});
|
|
6953
|
+
return {
|
|
6954
|
+
spans,
|
|
6955
|
+
confidence: 0.7,
|
|
6956
|
+
description: "Malformed OpenInference document",
|
|
6957
|
+
version,
|
|
6958
|
+
warnings,
|
|
6959
|
+
unsupportedFields
|
|
6960
|
+
};
|
|
6961
|
+
}
|
|
6962
|
+
if (spans.length === 0) return void 0;
|
|
6963
|
+
if (spans.length !== root.spans.length) {
|
|
6964
|
+
warnings.push({
|
|
6965
|
+
code: "openinference_skipped_spans",
|
|
6966
|
+
message: "Skipped invalid OpenInference span item(s).",
|
|
6967
|
+
severity: "warning"
|
|
6968
|
+
});
|
|
6969
|
+
}
|
|
6970
|
+
return {
|
|
6971
|
+
spans,
|
|
6972
|
+
confidence: rootFormat === "openinference" || rootCompatibility === "openinference-compatible" ? 0.9 : 0.84,
|
|
6973
|
+
description: rootFormat === "openinference" || rootCompatibility === "openinference-compatible" ? "OpenInference document" : "OpenInference spans document",
|
|
6974
|
+
version,
|
|
6975
|
+
warnings,
|
|
6976
|
+
unsupportedFields
|
|
6977
|
+
};
|
|
6978
|
+
}
|
|
6979
|
+
if (Array.isArray(root.data)) {
|
|
6980
|
+
const spans = root.data.filter(looksLikeOpenInferenceSpan);
|
|
6981
|
+
if (spans.length === 0) return void 0;
|
|
6982
|
+
if (spans.length !== root.data.length) {
|
|
6983
|
+
warnings.push({
|
|
6984
|
+
code: "openinference_skipped_data_items",
|
|
6985
|
+
message: "Skipped non-span item(s) in OpenInference data array.",
|
|
6986
|
+
severity: "warning"
|
|
6987
|
+
});
|
|
6988
|
+
}
|
|
6989
|
+
return {
|
|
6990
|
+
spans,
|
|
6991
|
+
confidence: 0.8,
|
|
6992
|
+
description: "OpenInference data document",
|
|
6993
|
+
version,
|
|
6994
|
+
warnings,
|
|
6995
|
+
unsupportedFields
|
|
6996
|
+
};
|
|
6997
|
+
}
|
|
6998
|
+
if (looksLikeOpenInferenceSpan(root)) {
|
|
6999
|
+
return {
|
|
7000
|
+
spans: [root],
|
|
7001
|
+
confidence: 0.76,
|
|
7002
|
+
description: "OpenInference single span",
|
|
7003
|
+
version,
|
|
7004
|
+
warnings,
|
|
7005
|
+
unsupportedFields
|
|
7006
|
+
};
|
|
7007
|
+
}
|
|
7008
|
+
if (rootFormat === "openinference" || rootCompatibility === "openinference-compatible") {
|
|
7009
|
+
warnings.push({
|
|
7010
|
+
code: "openinference_missing_spans",
|
|
7011
|
+
message: "OpenInference document is missing a spans array.",
|
|
7012
|
+
severity: "error"
|
|
7013
|
+
});
|
|
7014
|
+
return {
|
|
7015
|
+
spans: [],
|
|
7016
|
+
confidence: 0.7,
|
|
7017
|
+
description: "Malformed OpenInference document",
|
|
7018
|
+
version,
|
|
7019
|
+
warnings,
|
|
7020
|
+
unsupportedFields
|
|
7021
|
+
};
|
|
7022
|
+
}
|
|
7023
|
+
return void 0;
|
|
7024
|
+
}
|
|
7025
|
+
function parseUnixNanoToIso(value) {
|
|
7026
|
+
if (typeof value === "bigint" && value >= 0n) {
|
|
7027
|
+
return new Date(Number(value / 1000000n)).toISOString();
|
|
7028
|
+
}
|
|
7029
|
+
if (typeof value === "number" && Number.isFinite(value) && value >= 0) {
|
|
7030
|
+
return new Date(Math.floor(value / 1e6)).toISOString();
|
|
7031
|
+
}
|
|
7032
|
+
if (typeof value === "string" && /^\d+$/.test(value)) {
|
|
7033
|
+
return new Date(Number(BigInt(value) / 1000000n)).toISOString();
|
|
7034
|
+
}
|
|
7035
|
+
return void 0;
|
|
7036
|
+
}
|
|
7037
|
+
function parseIsoTime(value) {
|
|
7038
|
+
if (!isNonEmptyString4(value)) return void 0;
|
|
7039
|
+
const ms = Date.parse(value);
|
|
7040
|
+
if (!Number.isFinite(ms)) return void 0;
|
|
7041
|
+
return new Date(ms).toISOString();
|
|
7042
|
+
}
|
|
7043
|
+
function readOpenInferenceTimestamp(span, nanoKeys, isoKeys) {
|
|
7044
|
+
for (const key of nanoKeys) {
|
|
7045
|
+
const iso = parseUnixNanoToIso(span[key]);
|
|
7046
|
+
if (iso !== void 0) return iso;
|
|
7047
|
+
}
|
|
7048
|
+
for (const key of isoKeys) {
|
|
7049
|
+
const iso = parseIsoTime(span[key]);
|
|
7050
|
+
if (iso !== void 0) return iso;
|
|
7051
|
+
}
|
|
7052
|
+
return void 0;
|
|
7053
|
+
}
|
|
7054
|
+
function durationBetweenIso(startedAt, endedAt) {
|
|
7055
|
+
if (startedAt === void 0 || endedAt === void 0) return void 0;
|
|
7056
|
+
const startMs = Date.parse(startedAt);
|
|
7057
|
+
const endMs = Date.parse(endedAt);
|
|
7058
|
+
if (!Number.isFinite(startMs) || !Number.isFinite(endMs) || endMs < startMs) {
|
|
7059
|
+
return void 0;
|
|
7060
|
+
}
|
|
7061
|
+
return endMs - startMs;
|
|
7062
|
+
}
|
|
7063
|
+
function isSensitiveOpenInferenceAttribute(key) {
|
|
7064
|
+
return OPENINFERENCE_SENSITIVE_ATTRIBUTE_KEYS.some(
|
|
7065
|
+
(sensitiveKey) => key === sensitiveKey || key.startsWith(`${sensitiveKey}.`) || key.endsWith(".message.content") || key.endsWith(".document.content")
|
|
7066
|
+
);
|
|
7067
|
+
}
|
|
7068
|
+
function summarizeAttributeValue(value) {
|
|
7069
|
+
if (typeof value === "string") {
|
|
7070
|
+
return { type: "string", length: value.length };
|
|
7071
|
+
}
|
|
7072
|
+
if (typeof value === "number") {
|
|
7073
|
+
return { type: "number", finite: Number.isFinite(value) };
|
|
7074
|
+
}
|
|
7075
|
+
if (typeof value === "boolean") {
|
|
7076
|
+
return { type: "boolean" };
|
|
7077
|
+
}
|
|
7078
|
+
if (Array.isArray(value)) {
|
|
7079
|
+
return { type: "array", length: value.length };
|
|
7080
|
+
}
|
|
7081
|
+
if (isRecord9(value)) {
|
|
7082
|
+
return { type: "object", keyCount: Object.keys(value).length };
|
|
7083
|
+
}
|
|
7084
|
+
if (value === null) {
|
|
7085
|
+
return { type: "null" };
|
|
7086
|
+
}
|
|
7087
|
+
return { type: typeof value };
|
|
7088
|
+
}
|
|
7089
|
+
function sanitizeOpenInferenceAttributes(attributes, pathPrefix) {
|
|
7090
|
+
const out = {};
|
|
7091
|
+
const warnings = [];
|
|
7092
|
+
const unsupportedFields = [];
|
|
7093
|
+
const summarizedKeys = [];
|
|
7094
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
7095
|
+
if (isSensitiveOpenInferenceAttribute(key)) {
|
|
7096
|
+
summarizedKeys.push(key);
|
|
7097
|
+
out[`${key}.summary`] = summarizeAttributeValue(value);
|
|
7098
|
+
unsupportedFields.push(`${pathPrefix}.attributes.${key}`);
|
|
7099
|
+
continue;
|
|
7100
|
+
}
|
|
7101
|
+
out[key] = value;
|
|
7102
|
+
}
|
|
7103
|
+
if (summarizedKeys.length > 0) {
|
|
7104
|
+
out["openinference.summarized_attributes"] = summarizedKeys;
|
|
7105
|
+
warnings.push({
|
|
7106
|
+
code: "openinference_sensitive_attribute_summarized",
|
|
7107
|
+
message: "OpenInference prompt/output/document attribute(s) were summarized instead of copied verbatim.",
|
|
7108
|
+
severity: "warning"
|
|
7109
|
+
});
|
|
7110
|
+
}
|
|
7111
|
+
return { attributes: out, warnings, unsupportedFields };
|
|
7112
|
+
}
|
|
7113
|
+
function mapOpenInferenceKind(span, attributes, pathPrefix) {
|
|
7114
|
+
const warnings = [];
|
|
7115
|
+
const agentInspectKind = attributes["agent_inspect.kind"];
|
|
7116
|
+
if (agentInspectKind === "RUN" || agentInspectKind === "AGENT" || agentInspectKind === "LLM" || agentInspectKind === "TOOL" || agentInspectKind === "CHAIN" || agentInspectKind === "RETRIEVER" || agentInspectKind === "DECISION" || agentInspectKind === "RESULT" || agentInspectKind === "ERROR" || agentInspectKind === "LOGIC" || agentInspectKind === "LOG" || agentInspectKind === "OUTCOME") {
|
|
7117
|
+
return { kind: agentInspectKind, warnings };
|
|
7118
|
+
}
|
|
7119
|
+
const rawKind = readStringField(span, ["kind", "span_kind", "spanKind"]) ?? (typeof attributes["openinference.span.kind"] === "string" ? attributes["openinference.span.kind"] : void 0);
|
|
7120
|
+
const normalized = rawKind?.toUpperCase();
|
|
7121
|
+
switch (normalized) {
|
|
7122
|
+
case "LLM":
|
|
7123
|
+
return { kind: "LLM", warnings };
|
|
7124
|
+
case "TOOL":
|
|
7125
|
+
return { kind: "TOOL", warnings };
|
|
7126
|
+
case "CHAIN":
|
|
7127
|
+
return { kind: "CHAIN", warnings };
|
|
7128
|
+
case "RETRIEVER":
|
|
7129
|
+
return { kind: "RETRIEVER", warnings };
|
|
7130
|
+
case "AGENT":
|
|
7131
|
+
return { kind: "AGENT", warnings };
|
|
7132
|
+
case "EMBEDDING":
|
|
7133
|
+
warnings.push({
|
|
7134
|
+
code: "openinference_kind_semantic_loss",
|
|
7135
|
+
message: "OpenInference EMBEDDING span kind mapped to AgentInspect LLM.",
|
|
7136
|
+
severity: "warning",
|
|
7137
|
+
field: `${pathPrefix}.attributes.openinference.span.kind`
|
|
7138
|
+
});
|
|
7139
|
+
return { kind: "LLM", warnings };
|
|
7140
|
+
case "RERANKER":
|
|
7141
|
+
warnings.push({
|
|
7142
|
+
code: "openinference_kind_semantic_loss",
|
|
7143
|
+
message: "OpenInference RERANKER span kind mapped to AgentInspect RETRIEVER.",
|
|
7144
|
+
severity: "warning",
|
|
7145
|
+
field: `${pathPrefix}.attributes.openinference.span.kind`
|
|
7146
|
+
});
|
|
7147
|
+
return { kind: "RETRIEVER", warnings };
|
|
7148
|
+
case "UNKNOWN":
|
|
7149
|
+
case void 0:
|
|
7150
|
+
warnings.push({
|
|
7151
|
+
code: "openinference_kind_unknown",
|
|
7152
|
+
message: "OpenInference span kind was missing or unknown; mapped to AgentInspect LOGIC.",
|
|
7153
|
+
severity: "warning",
|
|
7154
|
+
field: `${pathPrefix}.attributes.openinference.span.kind`
|
|
7155
|
+
});
|
|
7156
|
+
return { kind: "LOGIC", warnings };
|
|
7157
|
+
default:
|
|
7158
|
+
warnings.push({
|
|
7159
|
+
code: "openinference_kind_unsupported",
|
|
7160
|
+
message: `Unsupported OpenInference span kind "${rawKind}" mapped to AgentInspect LOGIC.`,
|
|
7161
|
+
severity: "warning",
|
|
7162
|
+
field: `${pathPrefix}.attributes.openinference.span.kind`
|
|
7163
|
+
});
|
|
7164
|
+
return { kind: "LOGIC", warnings };
|
|
7165
|
+
}
|
|
7166
|
+
}
|
|
7167
|
+
function mapOpenInferenceStatus(status) {
|
|
7168
|
+
if (!isRecord9(status)) return void 0;
|
|
7169
|
+
const rawCode = status.code;
|
|
7170
|
+
if (typeof rawCode !== "string") return void 0;
|
|
7171
|
+
switch (rawCode.toUpperCase()) {
|
|
7172
|
+
case "OK":
|
|
7173
|
+
return "ok";
|
|
7174
|
+
case "ERROR":
|
|
7175
|
+
return "error";
|
|
7176
|
+
case "UNSET":
|
|
7177
|
+
return "unknown";
|
|
7178
|
+
default:
|
|
7179
|
+
return "unknown";
|
|
7180
|
+
}
|
|
7181
|
+
}
|
|
7182
|
+
function readOpenInferenceTokenUsage(attributes) {
|
|
7183
|
+
const prompt = attributes["llm.token_count.prompt"];
|
|
7184
|
+
const completion = attributes["llm.token_count.completion"];
|
|
7185
|
+
const total = attributes["llm.token_count.total"];
|
|
7186
|
+
const cached = attributes["llm.token_count.prompt_details.cache_read"];
|
|
7187
|
+
const usage = {};
|
|
7188
|
+
if (typeof prompt === "number" && Number.isFinite(prompt) && prompt >= 0) {
|
|
7189
|
+
usage.input = prompt;
|
|
7190
|
+
}
|
|
7191
|
+
if (typeof completion === "number" && Number.isFinite(completion) && completion >= 0) {
|
|
7192
|
+
usage.output = completion;
|
|
7193
|
+
}
|
|
7194
|
+
if (typeof total === "number" && Number.isFinite(total) && total >= 0) {
|
|
7195
|
+
usage.total = total;
|
|
7196
|
+
}
|
|
7197
|
+
if (typeof cached === "number" && Number.isFinite(cached) && cached >= 0) {
|
|
7198
|
+
usage.cached = cached;
|
|
7199
|
+
}
|
|
7200
|
+
if (usage.total === void 0 && usage.input !== void 0 && usage.output !== void 0) {
|
|
7201
|
+
usage.total = usage.input + usage.output;
|
|
7202
|
+
}
|
|
7203
|
+
return Object.keys(usage).length > 0 ? usage : void 0;
|
|
7204
|
+
}
|
|
7205
|
+
function readOpenInferenceConfidence(attributes) {
|
|
7206
|
+
const confidence = attributes["agent_inspect.confidence"];
|
|
7207
|
+
if (confidence === "explicit" || confidence === "correlated" || confidence === "heuristic" || confidence === "unknown") {
|
|
7208
|
+
return confidence;
|
|
7209
|
+
}
|
|
7210
|
+
return "correlated";
|
|
7211
|
+
}
|
|
7212
|
+
function mapOpenInferenceSpan(span, index, version) {
|
|
7213
|
+
const pathPrefix = `spans[${index}]`;
|
|
7214
|
+
const warnings = [];
|
|
7215
|
+
const unsupportedFields = [];
|
|
7216
|
+
const rawAttributes = readRecordField(span, "attributes") ?? {};
|
|
7217
|
+
const sanitized = sanitizeOpenInferenceAttributes(rawAttributes, pathPrefix);
|
|
7218
|
+
warnings.push(...sanitized.warnings);
|
|
7219
|
+
unsupportedFields.push(...sanitized.unsupportedFields);
|
|
7220
|
+
const attributes = { ...sanitized.attributes };
|
|
7221
|
+
for (const [key, value] of Object.entries(span)) {
|
|
7222
|
+
if (OPENINFERENCE_SPAN_KEYS.has(key)) continue;
|
|
7223
|
+
unsupportedFields.push(`${pathPrefix}.${key}`);
|
|
7224
|
+
if (value === null || typeof value !== "object") {
|
|
7225
|
+
attributes[`openinference.${key}`] = value;
|
|
7226
|
+
} else {
|
|
7227
|
+
attributes[`openinference.${key}.summary`] = summarizeAttributeValue(value);
|
|
7228
|
+
warnings.push({
|
|
7229
|
+
code: "openinference_unsupported_field_summarized",
|
|
7230
|
+
message: `Unsupported OpenInference span field "${key}" was summarized.`,
|
|
7231
|
+
severity: "warning",
|
|
7232
|
+
field: `${pathPrefix}.${key}`
|
|
7233
|
+
});
|
|
7234
|
+
}
|
|
7235
|
+
}
|
|
7236
|
+
const traceId = readStringField(span, ["trace_id", "traceId"]) ?? `trace-${index}`;
|
|
7237
|
+
const spanId = readStringField(span, ["span_id", "spanId"]) ?? `span-${index}`;
|
|
7238
|
+
const parentSpanId = readStringField(span, ["parent_span_id", "parentSpanId"]);
|
|
7239
|
+
const name = readStringField(span, ["name"]) ?? spanId;
|
|
7240
|
+
const startedAt = readOpenInferenceTimestamp(
|
|
7241
|
+
span,
|
|
7242
|
+
["start_time_unix_nano", "startTimeUnixNano"],
|
|
7243
|
+
["start_time", "startTime"]
|
|
7244
|
+
);
|
|
7245
|
+
const endedAt = readOpenInferenceTimestamp(
|
|
7246
|
+
span,
|
|
7247
|
+
["end_time_unix_nano", "endTimeUnixNano"],
|
|
7248
|
+
["end_time", "endTime"]
|
|
7249
|
+
);
|
|
7250
|
+
const timestamp = startedAt ?? "1970-01-01T00:00:00.000Z";
|
|
7251
|
+
if (startedAt === void 0) {
|
|
7252
|
+
warnings.push({
|
|
7253
|
+
code: "openinference_missing_start_time",
|
|
7254
|
+
message: "OpenInference span is missing a valid start time; using Unix epoch.",
|
|
7255
|
+
severity: "warning",
|
|
7256
|
+
field: `${pathPrefix}.start_time_unix_nano`
|
|
7257
|
+
});
|
|
7258
|
+
unsupportedFields.push(`${pathPrefix}.start_time_unix_nano`);
|
|
7259
|
+
}
|
|
7260
|
+
const { kind, warnings: kindWarnings } = mapOpenInferenceKind(
|
|
7261
|
+
span,
|
|
7262
|
+
rawAttributes,
|
|
7263
|
+
pathPrefix
|
|
7264
|
+
);
|
|
7265
|
+
warnings.push(...kindWarnings);
|
|
7266
|
+
const status = mapOpenInferenceStatus(span.status);
|
|
7267
|
+
const tokenUsage = readOpenInferenceTokenUsage(rawAttributes);
|
|
7268
|
+
const errorMessage = isRecord9(span.status) && typeof span.status.message === "string" ? span.status.message : void 0;
|
|
7269
|
+
const event = {
|
|
7270
|
+
schemaVersion: "0.2",
|
|
7271
|
+
eventId: typeof rawAttributes["agent_inspect.event_id"] === "string" ? rawAttributes["agent_inspect.event_id"] : spanId,
|
|
7272
|
+
runId: typeof rawAttributes["agent_inspect.run_id"] === "string" ? rawAttributes["agent_inspect.run_id"] : traceId,
|
|
7273
|
+
kind,
|
|
7274
|
+
name,
|
|
7275
|
+
timestamp,
|
|
7276
|
+
confidence: readOpenInferenceConfidence(rawAttributes),
|
|
7277
|
+
source: {
|
|
7278
|
+
type: "otel",
|
|
7279
|
+
name: "openinference",
|
|
7280
|
+
...version !== void 0 ? { version } : {}
|
|
7281
|
+
},
|
|
7282
|
+
attributes,
|
|
7283
|
+
trace: {
|
|
7284
|
+
traceId,
|
|
7285
|
+
spanId,
|
|
7286
|
+
...parentSpanId !== void 0 ? { parentSpanId } : {}
|
|
7287
|
+
}
|
|
7288
|
+
};
|
|
7289
|
+
if (status !== void 0) {
|
|
7290
|
+
event.status = status;
|
|
7291
|
+
}
|
|
7292
|
+
if (startedAt !== void 0) {
|
|
7293
|
+
event.startedAt = startedAt;
|
|
7294
|
+
}
|
|
7295
|
+
if (endedAt !== void 0) {
|
|
7296
|
+
event.endedAt = endedAt;
|
|
7297
|
+
}
|
|
7298
|
+
const durationMs2 = durationBetweenIso(startedAt, endedAt);
|
|
7299
|
+
if (durationMs2 !== void 0) {
|
|
7300
|
+
event.durationMs = durationMs2;
|
|
7301
|
+
}
|
|
7302
|
+
if (tokenUsage !== void 0) {
|
|
7303
|
+
event.tokenUsage = tokenUsage;
|
|
7304
|
+
}
|
|
7305
|
+
if (status === "error") {
|
|
7306
|
+
event.error = {
|
|
7307
|
+
message: errorMessage !== void 0 && errorMessage.trim() !== "" ? errorMessage : "OpenInference span error"
|
|
7308
|
+
};
|
|
7309
|
+
}
|
|
7310
|
+
return {
|
|
7311
|
+
event,
|
|
7312
|
+
warnings,
|
|
7313
|
+
unsupportedFields,
|
|
7314
|
+
spanId,
|
|
7315
|
+
...parentSpanId !== void 0 ? { parentSpanId } : {}
|
|
7316
|
+
};
|
|
7317
|
+
}
|
|
7318
|
+
function mapOpenInferenceEvents(document) {
|
|
7319
|
+
const mapped = document.spans.map(
|
|
7320
|
+
(span, index) => mapOpenInferenceSpan(span, index, document.version)
|
|
7321
|
+
);
|
|
7322
|
+
const spanIdToEventId = new Map(
|
|
7323
|
+
mapped.map((span) => [span.spanId, span.event.eventId])
|
|
7324
|
+
);
|
|
7325
|
+
for (const span of mapped) {
|
|
7326
|
+
if (span.parentSpanId === void 0) continue;
|
|
7327
|
+
span.event.parentId = spanIdToEventId.get(span.parentSpanId) ?? span.parentSpanId;
|
|
7328
|
+
}
|
|
7329
|
+
return {
|
|
7330
|
+
events: mapped.map((span) => span.event),
|
|
7331
|
+
warnings: mapped.flatMap((span) => span.warnings),
|
|
7332
|
+
unsupportedFields: mapped.flatMap((span) => span.unsupportedFields)
|
|
7333
|
+
};
|
|
7334
|
+
}
|
|
7335
|
+
var openInferenceJsonReader = {
|
|
7336
|
+
format: OPENINFERENCE_READER_FORMAT,
|
|
7337
|
+
name: "OpenInference JSON",
|
|
7338
|
+
async detect(input) {
|
|
7339
|
+
const resolved = await resolveInput(input);
|
|
7340
|
+
if (!resolved) return void 0;
|
|
7341
|
+
let parsed;
|
|
7342
|
+
try {
|
|
7343
|
+
parsed = parseJsonDocument(resolved.content);
|
|
7344
|
+
} catch {
|
|
7345
|
+
return void 0;
|
|
7346
|
+
}
|
|
7347
|
+
const document = extractOpenInferenceDocument(parsed);
|
|
7348
|
+
if (!document) return void 0;
|
|
7349
|
+
return {
|
|
7350
|
+
format: OPENINFERENCE_READER_FORMAT,
|
|
7351
|
+
confidence: document.confidence,
|
|
7352
|
+
readerName: "OpenInference JSON",
|
|
7353
|
+
description: document.description,
|
|
7354
|
+
warnings: attachSingleSourceFile(document.warnings, resolved)
|
|
7355
|
+
};
|
|
7356
|
+
},
|
|
7357
|
+
async read(input) {
|
|
7358
|
+
const resolved = await resolveInput(input);
|
|
7359
|
+
if (!resolved) {
|
|
7360
|
+
throw new TraceReadError(
|
|
7361
|
+
"unsupported_format",
|
|
7362
|
+
"OpenInference JSON reader requires file, string, or buffer input."
|
|
7363
|
+
);
|
|
7364
|
+
}
|
|
7365
|
+
let parsed;
|
|
7366
|
+
try {
|
|
7367
|
+
parsed = parseJsonDocument(resolved.content);
|
|
7368
|
+
} catch {
|
|
7369
|
+
throw new TraceReadError("unsupported_format", "OpenInference JSON input is not valid JSON.", [
|
|
7370
|
+
{
|
|
7371
|
+
code: "openinference_invalid_json",
|
|
7372
|
+
message: "OpenInference JSON reader could not parse the input as JSON.",
|
|
7373
|
+
severity: "error"
|
|
7374
|
+
}
|
|
7375
|
+
]);
|
|
7376
|
+
}
|
|
7377
|
+
const document = extractOpenInferenceDocument(parsed);
|
|
7378
|
+
if (!document || document.spans.length === 0) {
|
|
7379
|
+
throw new TraceReadError(
|
|
7380
|
+
"unsupported_format",
|
|
7381
|
+
"No valid OpenInference spans found.",
|
|
7382
|
+
attachSingleSourceFile(
|
|
7383
|
+
document?.warnings ?? [
|
|
7384
|
+
{
|
|
7385
|
+
code: "openinference_no_valid_spans",
|
|
7386
|
+
message: "OpenInference JSON input did not contain valid spans.",
|
|
7387
|
+
severity: "error"
|
|
7388
|
+
}
|
|
7389
|
+
],
|
|
7390
|
+
resolved
|
|
7391
|
+
)
|
|
7392
|
+
);
|
|
7393
|
+
}
|
|
7394
|
+
const mapped = mapOpenInferenceEvents(document);
|
|
7395
|
+
const warnings = attachSingleSourceFile(
|
|
7396
|
+
[...document.warnings, ...mapped.warnings],
|
|
7397
|
+
resolved
|
|
7398
|
+
);
|
|
7399
|
+
const unsupportedFields = [
|
|
7400
|
+
...document.unsupportedFields,
|
|
7401
|
+
...mapped.unsupportedFields
|
|
7402
|
+
].sort((a, b) => a.localeCompare(b));
|
|
7403
|
+
return {
|
|
7404
|
+
format: OPENINFERENCE_READER_FORMAT,
|
|
7405
|
+
events: mapped.events,
|
|
7406
|
+
runs: persistedInspectEventsToRunTrees(mapped.events, { skipInvalid: true }),
|
|
7407
|
+
warnings,
|
|
7408
|
+
unsupportedFields,
|
|
7409
|
+
sourceFiles: resolved.sourceFiles
|
|
7410
|
+
};
|
|
7411
|
+
}
|
|
7412
|
+
};
|
|
7413
|
+
function parseOtlpAnyValue(value, field, warnings, unsupportedFields) {
|
|
7414
|
+
if (!isRecord9(value)) {
|
|
7415
|
+
unsupportedFields.push(field);
|
|
7416
|
+
warnings.push({
|
|
7417
|
+
code: "otlp_attribute_value_invalid",
|
|
7418
|
+
message: "OTLP attribute value was not an AnyValue object.",
|
|
7419
|
+
severity: "warning",
|
|
7420
|
+
field
|
|
7421
|
+
});
|
|
7422
|
+
return void 0;
|
|
7423
|
+
}
|
|
7424
|
+
if (typeof value.stringValue === "string") return value.stringValue;
|
|
7425
|
+
if (typeof value.boolValue === "boolean") return value.boolValue;
|
|
7426
|
+
if (typeof value.intValue === "number" && Number.isFinite(value.intValue)) {
|
|
7427
|
+
return value.intValue;
|
|
7428
|
+
}
|
|
7429
|
+
if (typeof value.intValue === "string" && value.intValue.trim() !== "") {
|
|
7430
|
+
const n = Number(value.intValue);
|
|
7431
|
+
if (Number.isFinite(n)) return n;
|
|
7432
|
+
}
|
|
7433
|
+
if (typeof value.doubleValue === "number" && Number.isFinite(value.doubleValue)) {
|
|
7434
|
+
return value.doubleValue;
|
|
7435
|
+
}
|
|
7436
|
+
if (isRecord9(value.arrayValue) && Array.isArray(value.arrayValue.values)) {
|
|
7437
|
+
return value.arrayValue.values.map(
|
|
7438
|
+
(item, index) => parseOtlpAnyValue(item, `${field}.arrayValue.values[${index}]`, warnings, unsupportedFields)
|
|
7439
|
+
);
|
|
7440
|
+
}
|
|
7441
|
+
if (isRecord9(value.kvlistValue) && Array.isArray(value.kvlistValue.values)) {
|
|
7442
|
+
const out = {};
|
|
7443
|
+
for (const [index, item] of value.kvlistValue.values.entries()) {
|
|
7444
|
+
if (!isRecord9(item) || typeof item.key !== "string") {
|
|
7445
|
+
unsupportedFields.push(`${field}.kvlistValue.values[${index}]`);
|
|
7446
|
+
continue;
|
|
7447
|
+
}
|
|
7448
|
+
out[item.key] = parseOtlpAnyValue(
|
|
7449
|
+
item.value,
|
|
7450
|
+
`${field}.kvlistValue.values[${index}].value`,
|
|
7451
|
+
warnings,
|
|
7452
|
+
unsupportedFields
|
|
7453
|
+
);
|
|
7454
|
+
}
|
|
7455
|
+
return out;
|
|
7456
|
+
}
|
|
7457
|
+
if (typeof value.bytesValue === "string") {
|
|
7458
|
+
unsupportedFields.push(field);
|
|
7459
|
+
warnings.push({
|
|
7460
|
+
code: "otlp_bytes_value_summarized",
|
|
7461
|
+
message: "OTLP bytesValue attribute was summarized instead of decoded.",
|
|
7462
|
+
severity: "warning",
|
|
7463
|
+
field
|
|
7464
|
+
});
|
|
7465
|
+
return { type: "bytes", length: value.bytesValue.length };
|
|
7466
|
+
}
|
|
7467
|
+
unsupportedFields.push(field);
|
|
7468
|
+
warnings.push({
|
|
7469
|
+
code: "otlp_attribute_value_unsupported",
|
|
7470
|
+
message: "OTLP attribute value used an unsupported AnyValue shape.",
|
|
7471
|
+
severity: "warning",
|
|
7472
|
+
field
|
|
7473
|
+
});
|
|
7474
|
+
return void 0;
|
|
7475
|
+
}
|
|
7476
|
+
function parseOtlpAttributes(value, pathPrefix) {
|
|
7477
|
+
const attributes = {};
|
|
7478
|
+
const warnings = [];
|
|
7479
|
+
const unsupportedFields = [];
|
|
7480
|
+
if (value === void 0) {
|
|
7481
|
+
return { attributes, warnings, unsupportedFields };
|
|
7482
|
+
}
|
|
7483
|
+
if (!Array.isArray(value)) {
|
|
7484
|
+
unsupportedFields.push(pathPrefix);
|
|
7485
|
+
warnings.push({
|
|
7486
|
+
code: "otlp_attributes_invalid",
|
|
7487
|
+
message: "OTLP attributes field was not an array.",
|
|
7488
|
+
severity: "warning",
|
|
7489
|
+
field: pathPrefix
|
|
7490
|
+
});
|
|
7491
|
+
return { attributes, warnings, unsupportedFields };
|
|
7492
|
+
}
|
|
7493
|
+
for (const [index, item] of value.entries()) {
|
|
7494
|
+
const field = `${pathPrefix}[${index}]`;
|
|
7495
|
+
if (!isRecord9(item) || typeof item.key !== "string") {
|
|
7496
|
+
unsupportedFields.push(field);
|
|
7497
|
+
warnings.push({
|
|
7498
|
+
code: "otlp_attribute_invalid",
|
|
7499
|
+
message: "Skipped OTLP attribute without a string key.",
|
|
7500
|
+
severity: "warning",
|
|
7501
|
+
field
|
|
7502
|
+
});
|
|
7503
|
+
continue;
|
|
7504
|
+
}
|
|
7505
|
+
const parsed = parseOtlpAnyValue(
|
|
7506
|
+
item.value,
|
|
7507
|
+
`${field}.value`,
|
|
7508
|
+
warnings,
|
|
7509
|
+
unsupportedFields
|
|
7510
|
+
);
|
|
7511
|
+
if (parsed !== void 0) {
|
|
7512
|
+
attributes[item.key] = parsed;
|
|
7513
|
+
}
|
|
7514
|
+
}
|
|
7515
|
+
return { attributes, warnings, unsupportedFields };
|
|
7516
|
+
}
|
|
7517
|
+
function looksLikeOtlpSpan(value) {
|
|
7518
|
+
return isRecord9(value) && readStringField(value, ["traceId"]) !== void 0 && readStringField(value, ["spanId"]) !== void 0 && readStringField(value, ["name"]) !== void 0;
|
|
7519
|
+
}
|
|
7520
|
+
function extractOtlpDocument(root) {
|
|
7521
|
+
if (!isRecord9(root) || !Array.isArray(root.resourceSpans)) return void 0;
|
|
7522
|
+
const spans = [];
|
|
7523
|
+
const warnings = [];
|
|
7524
|
+
const unsupportedFields = [];
|
|
7525
|
+
for (const [resourceIndex, resourceSpan] of root.resourceSpans.entries()) {
|
|
7526
|
+
const resourcePath = `resourceSpans[${resourceIndex}]`;
|
|
7527
|
+
if (!isRecord9(resourceSpan)) {
|
|
7528
|
+
unsupportedFields.push(resourcePath);
|
|
7529
|
+
continue;
|
|
7530
|
+
}
|
|
7531
|
+
const resource = readRecordField(resourceSpan, "resource");
|
|
7532
|
+
const resourceParsed = parseOtlpAttributes(
|
|
7533
|
+
resource?.attributes,
|
|
7534
|
+
`${resourcePath}.resource.attributes`
|
|
7535
|
+
);
|
|
7536
|
+
warnings.push(...resourceParsed.warnings);
|
|
7537
|
+
unsupportedFields.push(...resourceParsed.unsupportedFields);
|
|
7538
|
+
if (!Array.isArray(resourceSpan.scopeSpans)) {
|
|
7539
|
+
unsupportedFields.push(`${resourcePath}.scopeSpans`);
|
|
7540
|
+
warnings.push({
|
|
7541
|
+
code: "otlp_scope_spans_missing",
|
|
7542
|
+
message: "OTLP resourceSpans entry did not contain a scopeSpans array.",
|
|
7543
|
+
severity: "warning",
|
|
7544
|
+
field: `${resourcePath}.scopeSpans`
|
|
7545
|
+
});
|
|
7546
|
+
continue;
|
|
7547
|
+
}
|
|
7548
|
+
for (const [scopeIndex, scopeSpan] of resourceSpan.scopeSpans.entries()) {
|
|
7549
|
+
const scopePath = `${resourcePath}.scopeSpans[${scopeIndex}]`;
|
|
7550
|
+
if (!isRecord9(scopeSpan)) {
|
|
7551
|
+
unsupportedFields.push(scopePath);
|
|
7552
|
+
continue;
|
|
7553
|
+
}
|
|
7554
|
+
const scope = readRecordField(scopeSpan, "scope");
|
|
7555
|
+
const scopeParsed = parseOtlpAttributes(
|
|
7556
|
+
scope?.attributes,
|
|
7557
|
+
`${scopePath}.scope.attributes`
|
|
7558
|
+
);
|
|
7559
|
+
warnings.push(...scopeParsed.warnings);
|
|
7560
|
+
unsupportedFields.push(...scopeParsed.unsupportedFields);
|
|
7561
|
+
if (!Array.isArray(scopeSpan.spans)) {
|
|
7562
|
+
unsupportedFields.push(`${scopePath}.spans`);
|
|
7563
|
+
warnings.push({
|
|
7564
|
+
code: "otlp_spans_missing",
|
|
7565
|
+
message: "OTLP scopeSpans entry did not contain a spans array.",
|
|
7566
|
+
severity: "warning",
|
|
7567
|
+
field: `${scopePath}.spans`
|
|
7568
|
+
});
|
|
7569
|
+
continue;
|
|
7570
|
+
}
|
|
7571
|
+
for (const [spanIndex, span] of scopeSpan.spans.entries()) {
|
|
7572
|
+
const spanPath = `${scopePath}.spans[${spanIndex}]`;
|
|
7573
|
+
if (!looksLikeOtlpSpan(span)) {
|
|
7574
|
+
unsupportedFields.push(spanPath);
|
|
7575
|
+
warnings.push({
|
|
7576
|
+
code: "otlp_invalid_span",
|
|
7577
|
+
message: "Skipped OTLP span without required traceId, spanId, or name.",
|
|
7578
|
+
severity: "warning",
|
|
7579
|
+
field: spanPath
|
|
7580
|
+
});
|
|
7581
|
+
continue;
|
|
7582
|
+
}
|
|
7583
|
+
spans.push({
|
|
7584
|
+
span,
|
|
7585
|
+
resourceAttributes: resourceParsed.attributes,
|
|
7586
|
+
scopeAttributes: scopeParsed.attributes,
|
|
7587
|
+
scopeName: readStringField(scope ?? {}, ["name"]),
|
|
7588
|
+
scopeVersion: readStringField(scope ?? {}, ["version"]),
|
|
7589
|
+
pathPrefix: spanPath
|
|
7590
|
+
});
|
|
7591
|
+
}
|
|
7592
|
+
}
|
|
7593
|
+
}
|
|
7594
|
+
if (spans.length === 0) {
|
|
7595
|
+
warnings.push({
|
|
7596
|
+
code: "otlp_no_valid_spans",
|
|
7597
|
+
message: "OTLP JSON payload did not contain any valid spans.",
|
|
7598
|
+
severity: "error"
|
|
7599
|
+
});
|
|
7600
|
+
return {
|
|
7601
|
+
spans,
|
|
7602
|
+
confidence: 0.7,
|
|
7603
|
+
description: "Malformed OTLP JSON trace payload",
|
|
7604
|
+
warnings,
|
|
7605
|
+
unsupportedFields
|
|
7606
|
+
};
|
|
7607
|
+
}
|
|
7608
|
+
return {
|
|
7609
|
+
spans,
|
|
7610
|
+
confidence: 0.93,
|
|
7611
|
+
description: "OTLP JSON trace payload",
|
|
7612
|
+
warnings,
|
|
7613
|
+
unsupportedFields
|
|
7614
|
+
};
|
|
7615
|
+
}
|
|
7616
|
+
function mapOtlpStatus(status) {
|
|
7617
|
+
if (!isRecord9(status)) return void 0;
|
|
7618
|
+
const rawCode = status.code;
|
|
7619
|
+
if (typeof rawCode !== "string") return void 0;
|
|
7620
|
+
switch (rawCode.toUpperCase()) {
|
|
7621
|
+
case "STATUS_CODE_OK":
|
|
7622
|
+
case "OK":
|
|
7623
|
+
return "ok";
|
|
7624
|
+
case "STATUS_CODE_ERROR":
|
|
7625
|
+
case "ERROR":
|
|
7626
|
+
return "error";
|
|
7627
|
+
case "STATUS_CODE_UNSET":
|
|
7628
|
+
case "UNSET":
|
|
7629
|
+
return "unknown";
|
|
7630
|
+
default:
|
|
7631
|
+
return "unknown";
|
|
7632
|
+
}
|
|
7633
|
+
}
|
|
7634
|
+
function readOtlpKind(attributes, pathPrefix) {
|
|
7635
|
+
const warnings = [];
|
|
7636
|
+
const agentInspectKind = attributes["agent_inspect.kind"];
|
|
7637
|
+
if (agentInspectKind === "RUN" || agentInspectKind === "AGENT" || agentInspectKind === "LLM" || agentInspectKind === "TOOL" || agentInspectKind === "CHAIN" || agentInspectKind === "RETRIEVER" || agentInspectKind === "DECISION" || agentInspectKind === "RESULT" || agentInspectKind === "ERROR" || agentInspectKind === "LOGIC" || agentInspectKind === "LOG" || agentInspectKind === "OUTCOME") {
|
|
7638
|
+
return { kind: agentInspectKind, warnings };
|
|
7639
|
+
}
|
|
7640
|
+
const operation = attributes["gen_ai.operation.name"];
|
|
7641
|
+
if (typeof operation === "string") {
|
|
7642
|
+
switch (operation) {
|
|
7643
|
+
case "generate_content":
|
|
7644
|
+
case "chat":
|
|
7645
|
+
return { kind: "LLM", warnings };
|
|
7646
|
+
case "execute_tool":
|
|
7647
|
+
return { kind: "TOOL", warnings };
|
|
7648
|
+
case "invoke_agent":
|
|
7649
|
+
return { kind: "AGENT", warnings };
|
|
7650
|
+
default:
|
|
7651
|
+
warnings.push({
|
|
7652
|
+
code: "otlp_gen_ai_operation_semantic_loss",
|
|
7653
|
+
message: `OTLP GenAI operation "${operation}" mapped to AgentInspect LOGIC.`,
|
|
7654
|
+
severity: "warning",
|
|
7655
|
+
field: `${pathPrefix}.attributes.gen_ai.operation.name`
|
|
7656
|
+
});
|
|
7657
|
+
return { kind: "LOGIC", warnings };
|
|
7658
|
+
}
|
|
7659
|
+
}
|
|
7660
|
+
warnings.push({
|
|
7661
|
+
code: "otlp_kind_unknown",
|
|
7662
|
+
message: "OTLP span had no AgentInspect kind or GenAI operation; mapped to LOGIC.",
|
|
7663
|
+
severity: "warning",
|
|
7664
|
+
field: `${pathPrefix}.attributes`
|
|
7665
|
+
});
|
|
7666
|
+
return { kind: "LOGIC", warnings };
|
|
7667
|
+
}
|
|
7668
|
+
function readOtlpTokenUsage(attributes) {
|
|
7669
|
+
const input = attributes["gen_ai.usage.input_tokens"];
|
|
7670
|
+
const output = attributes["gen_ai.usage.output_tokens"];
|
|
7671
|
+
const usage = {};
|
|
7672
|
+
if (typeof input === "number" && Number.isFinite(input) && input >= 0) {
|
|
7673
|
+
usage.input = input;
|
|
7674
|
+
}
|
|
7675
|
+
if (typeof output === "number" && Number.isFinite(output) && output >= 0) {
|
|
7676
|
+
usage.output = output;
|
|
7677
|
+
}
|
|
7678
|
+
if (usage.input !== void 0 && usage.output !== void 0) {
|
|
7679
|
+
usage.total = usage.input + usage.output;
|
|
7680
|
+
}
|
|
7681
|
+
return Object.keys(usage).length > 0 ? usage : void 0;
|
|
7682
|
+
}
|
|
7683
|
+
function readOtlpConfidence(attributes) {
|
|
7684
|
+
return readOpenInferenceConfidence(attributes);
|
|
7685
|
+
}
|
|
7686
|
+
function sanitizeOtlpAttributes(attributes, pathPrefix) {
|
|
7687
|
+
const ownerPath = pathPrefix.endsWith(".attributes") ? pathPrefix.slice(0, -".attributes".length) : pathPrefix;
|
|
7688
|
+
const sanitized = sanitizeOpenInferenceAttributes(attributes, ownerPath);
|
|
7689
|
+
return {
|
|
7690
|
+
...sanitized,
|
|
7691
|
+
warnings: sanitized.warnings.map(
|
|
7692
|
+
(warning) => warning.code === "openinference_sensitive_attribute_summarized" ? {
|
|
7693
|
+
...warning,
|
|
7694
|
+
code: "otlp_sensitive_attribute_summarized",
|
|
7695
|
+
message: "OTLP prompt/output/document attribute(s) were summarized instead of copied verbatim."
|
|
7696
|
+
} : warning
|
|
7697
|
+
)
|
|
7698
|
+
};
|
|
7699
|
+
}
|
|
7700
|
+
function mapOtlpEvents(value, pathPrefix) {
|
|
7701
|
+
const warnings = [];
|
|
7702
|
+
const unsupportedFields = [];
|
|
7703
|
+
if (value === void 0) return { warnings, unsupportedFields };
|
|
7704
|
+
if (!Array.isArray(value)) {
|
|
7705
|
+
unsupportedFields.push(pathPrefix);
|
|
7706
|
+
warnings.push({
|
|
7707
|
+
code: "otlp_events_invalid",
|
|
7708
|
+
message: "OTLP events field was not an array.",
|
|
7709
|
+
severity: "warning",
|
|
7710
|
+
field: pathPrefix
|
|
7711
|
+
});
|
|
7712
|
+
return { warnings, unsupportedFields };
|
|
7713
|
+
}
|
|
7714
|
+
const events = [];
|
|
7715
|
+
for (const [index, event] of value.entries()) {
|
|
7716
|
+
const eventPath = `${pathPrefix}[${index}]`;
|
|
7717
|
+
if (!isRecord9(event)) {
|
|
7718
|
+
unsupportedFields.push(eventPath);
|
|
7719
|
+
continue;
|
|
7720
|
+
}
|
|
7721
|
+
const parsedAttributes = parseOtlpAttributes(
|
|
7722
|
+
event.attributes,
|
|
7723
|
+
`${eventPath}.attributes`
|
|
7724
|
+
);
|
|
7725
|
+
warnings.push(...parsedAttributes.warnings);
|
|
7726
|
+
unsupportedFields.push(...parsedAttributes.unsupportedFields);
|
|
7727
|
+
const sanitized = sanitizeOtlpAttributes(
|
|
7728
|
+
parsedAttributes.attributes,
|
|
7729
|
+
`${eventPath}.attributes`
|
|
7730
|
+
);
|
|
7731
|
+
warnings.push(...sanitized.warnings);
|
|
7732
|
+
unsupportedFields.push(...sanitized.unsupportedFields);
|
|
7733
|
+
const out = {};
|
|
7734
|
+
const name = readStringField(event, ["name"]);
|
|
7735
|
+
if (name !== void 0) {
|
|
7736
|
+
out.name = name;
|
|
7737
|
+
}
|
|
7738
|
+
const timestamp = parseUnixNanoToIso(event.timeUnixNano);
|
|
7739
|
+
if (timestamp !== void 0) {
|
|
7740
|
+
out.timestamp = timestamp;
|
|
7741
|
+
} else if (event.timeUnixNano !== void 0) {
|
|
7742
|
+
unsupportedFields.push(`${eventPath}.timeUnixNano`);
|
|
7743
|
+
warnings.push({
|
|
7744
|
+
code: "otlp_event_timestamp_invalid",
|
|
7745
|
+
message: "OTLP event timeUnixNano could not be parsed.",
|
|
7746
|
+
severity: "warning",
|
|
7747
|
+
field: `${eventPath}.timeUnixNano`
|
|
7748
|
+
});
|
|
7749
|
+
}
|
|
7750
|
+
if (Object.keys(sanitized.attributes).length > 0) {
|
|
7751
|
+
out.attributes = sanitized.attributes;
|
|
7752
|
+
}
|
|
7753
|
+
events.push(out);
|
|
7754
|
+
}
|
|
7755
|
+
return {
|
|
7756
|
+
events: events.length > 0 ? events : void 0,
|
|
7757
|
+
warnings,
|
|
7758
|
+
unsupportedFields
|
|
7759
|
+
};
|
|
7760
|
+
}
|
|
7761
|
+
function mapOtlpSpan(context) {
|
|
7762
|
+
const { span, pathPrefix } = context;
|
|
7763
|
+
const warnings = [];
|
|
7764
|
+
const unsupportedFields = [];
|
|
7765
|
+
const parsedSpanAttributes = parseOtlpAttributes(
|
|
7766
|
+
span.attributes,
|
|
7767
|
+
`${pathPrefix}.attributes`
|
|
7768
|
+
);
|
|
7769
|
+
warnings.push(...parsedSpanAttributes.warnings);
|
|
7770
|
+
unsupportedFields.push(...parsedSpanAttributes.unsupportedFields);
|
|
7771
|
+
const sanitizedSpanAttributes = sanitizeOtlpAttributes(
|
|
7772
|
+
parsedSpanAttributes.attributes,
|
|
7773
|
+
`${pathPrefix}.attributes`
|
|
7774
|
+
);
|
|
7775
|
+
warnings.push(...sanitizedSpanAttributes.warnings);
|
|
7776
|
+
unsupportedFields.push(...sanitizedSpanAttributes.unsupportedFields);
|
|
7777
|
+
const attributes = {
|
|
7778
|
+
...sanitizedSpanAttributes.attributes
|
|
7779
|
+
};
|
|
7780
|
+
for (const [key, value] of Object.entries(context.resourceAttributes)) {
|
|
7781
|
+
attributes[`resource.${key}`] = value;
|
|
7782
|
+
}
|
|
7783
|
+
for (const [key, value] of Object.entries(context.scopeAttributes)) {
|
|
7784
|
+
attributes[`scope.${key}`] = value;
|
|
7785
|
+
}
|
|
7786
|
+
if (context.scopeName !== void 0) {
|
|
7787
|
+
attributes["scope.name"] = context.scopeName;
|
|
7788
|
+
}
|
|
7789
|
+
if (context.scopeVersion !== void 0) {
|
|
7790
|
+
attributes["scope.version"] = context.scopeVersion;
|
|
7791
|
+
}
|
|
7792
|
+
for (const [key, value] of Object.entries(span)) {
|
|
7793
|
+
if (OTLP_SPAN_KEYS.has(key)) continue;
|
|
7794
|
+
unsupportedFields.push(`${pathPrefix}.${key}`);
|
|
7795
|
+
if (value === null || typeof value !== "object") {
|
|
7796
|
+
attributes[`otlp.${key}`] = value;
|
|
7797
|
+
} else {
|
|
7798
|
+
attributes[`otlp.${key}.summary`] = summarizeAttributeValue(value);
|
|
7799
|
+
warnings.push({
|
|
7800
|
+
code: "otlp_unsupported_field_summarized",
|
|
7801
|
+
message: `Unsupported OTLP span field "${key}" was summarized.`,
|
|
7802
|
+
severity: "warning",
|
|
7803
|
+
field: `${pathPrefix}.${key}`
|
|
7804
|
+
});
|
|
7805
|
+
}
|
|
7806
|
+
}
|
|
7807
|
+
for (const key of [
|
|
7808
|
+
"droppedAttributesCount",
|
|
7809
|
+
"droppedEventsCount",
|
|
7810
|
+
"droppedLinksCount",
|
|
7811
|
+
"links"
|
|
7812
|
+
]) {
|
|
7813
|
+
if (span[key] !== void 0) {
|
|
7814
|
+
unsupportedFields.push(`${pathPrefix}.${key}`);
|
|
7815
|
+
warnings.push({
|
|
7816
|
+
code: "otlp_span_field_not_mapped",
|
|
7817
|
+
message: `OTLP span field "${key}" is not represented in AgentInspect events.`,
|
|
7818
|
+
severity: "warning",
|
|
7819
|
+
field: `${pathPrefix}.${key}`
|
|
7820
|
+
});
|
|
7821
|
+
}
|
|
7822
|
+
}
|
|
7823
|
+
const events = mapOtlpEvents(span.events, `${pathPrefix}.events`);
|
|
7824
|
+
warnings.push(...events.warnings);
|
|
7825
|
+
unsupportedFields.push(...events.unsupportedFields);
|
|
7826
|
+
if (events.events !== void 0) {
|
|
7827
|
+
attributes["otlp.events"] = events.events;
|
|
7828
|
+
}
|
|
7829
|
+
const traceId = readStringField(span, ["traceId"]) ?? "trace-unknown";
|
|
7830
|
+
const spanId = readStringField(span, ["spanId"]) ?? "span-unknown";
|
|
7831
|
+
const parentSpanId = readStringField(span, ["parentSpanId"]);
|
|
7832
|
+
const startedAt = readOpenInferenceTimestamp(
|
|
7833
|
+
span,
|
|
7834
|
+
["startTimeUnixNano"],
|
|
7835
|
+
[]
|
|
7836
|
+
);
|
|
7837
|
+
const endedAt = readOpenInferenceTimestamp(span, ["endTimeUnixNano"], []);
|
|
7838
|
+
const timestamp = startedAt ?? "1970-01-01T00:00:00.000Z";
|
|
7839
|
+
if (startedAt === void 0) {
|
|
7840
|
+
unsupportedFields.push(`${pathPrefix}.startTimeUnixNano`);
|
|
7841
|
+
warnings.push({
|
|
7842
|
+
code: "otlp_missing_start_time",
|
|
7843
|
+
message: "OTLP span is missing a valid startTimeUnixNano; using Unix epoch.",
|
|
7844
|
+
severity: "warning",
|
|
7845
|
+
field: `${pathPrefix}.startTimeUnixNano`
|
|
7846
|
+
});
|
|
7847
|
+
}
|
|
7848
|
+
const { kind, warnings: kindWarnings } = readOtlpKind(
|
|
7849
|
+
parsedSpanAttributes.attributes,
|
|
7850
|
+
pathPrefix
|
|
7851
|
+
);
|
|
7852
|
+
warnings.push(...kindWarnings);
|
|
7853
|
+
const status = mapOtlpStatus(span.status);
|
|
7854
|
+
const tokenUsage = readOtlpTokenUsage(parsedSpanAttributes.attributes);
|
|
7855
|
+
const errorMessage = isRecord9(span.status) && typeof span.status.message === "string" ? span.status.message : void 0;
|
|
7856
|
+
const event = {
|
|
7857
|
+
schemaVersion: "0.2",
|
|
7858
|
+
eventId: typeof parsedSpanAttributes.attributes["agent_inspect.event_id"] === "string" ? parsedSpanAttributes.attributes["agent_inspect.event_id"] : spanId,
|
|
7859
|
+
runId: typeof parsedSpanAttributes.attributes["agent_inspect.run_id"] === "string" ? parsedSpanAttributes.attributes["agent_inspect.run_id"] : traceId,
|
|
7860
|
+
kind,
|
|
7861
|
+
name: readStringField(span, ["name"]) ?? spanId,
|
|
7862
|
+
timestamp,
|
|
7863
|
+
confidence: readOtlpConfidence(parsedSpanAttributes.attributes),
|
|
7864
|
+
source: {
|
|
7865
|
+
type: "otel",
|
|
7866
|
+
name: context.scopeName ?? (typeof context.resourceAttributes["service.name"] === "string" ? context.resourceAttributes["service.name"] : "otlp-json"),
|
|
7867
|
+
...context.scopeVersion !== void 0 ? { version: context.scopeVersion } : {}
|
|
7868
|
+
},
|
|
7869
|
+
attributes,
|
|
7870
|
+
trace: {
|
|
7871
|
+
traceId,
|
|
7872
|
+
spanId,
|
|
7873
|
+
...parentSpanId !== void 0 ? { parentSpanId } : {}
|
|
7874
|
+
}
|
|
7875
|
+
};
|
|
7876
|
+
if (status !== void 0) {
|
|
7877
|
+
event.status = status;
|
|
7878
|
+
}
|
|
7879
|
+
if (startedAt !== void 0) {
|
|
7880
|
+
event.startedAt = startedAt;
|
|
7881
|
+
}
|
|
7882
|
+
if (endedAt !== void 0) {
|
|
7883
|
+
event.endedAt = endedAt;
|
|
7884
|
+
}
|
|
7885
|
+
const durationMs2 = durationBetweenIso(startedAt, endedAt);
|
|
7886
|
+
if (durationMs2 !== void 0) {
|
|
7887
|
+
event.durationMs = durationMs2;
|
|
7888
|
+
}
|
|
7889
|
+
if (tokenUsage !== void 0) {
|
|
7890
|
+
event.tokenUsage = tokenUsage;
|
|
7891
|
+
}
|
|
7892
|
+
if (status === "error") {
|
|
7893
|
+
event.error = {
|
|
7894
|
+
message: errorMessage !== void 0 && errorMessage.trim() !== "" ? errorMessage : "OTLP span error"
|
|
7895
|
+
};
|
|
7896
|
+
}
|
|
7897
|
+
return {
|
|
7898
|
+
event,
|
|
7899
|
+
warnings,
|
|
7900
|
+
unsupportedFields,
|
|
7901
|
+
spanId,
|
|
7902
|
+
...parentSpanId !== void 0 ? { parentSpanId } : {}
|
|
7903
|
+
};
|
|
7904
|
+
}
|
|
7905
|
+
function mapOtlpEventsToPersisted(document) {
|
|
7906
|
+
const mapped = document.spans.map((span) => mapOtlpSpan(span));
|
|
7907
|
+
const spanIdToEventId = new Map(
|
|
7908
|
+
mapped.map((span) => [span.spanId, span.event.eventId])
|
|
7909
|
+
);
|
|
7910
|
+
for (const span of mapped) {
|
|
7911
|
+
if (span.parentSpanId === void 0) continue;
|
|
7912
|
+
span.event.parentId = spanIdToEventId.get(span.parentSpanId) ?? span.parentSpanId;
|
|
7913
|
+
}
|
|
7914
|
+
return {
|
|
7915
|
+
events: mapped.map((span) => span.event),
|
|
7916
|
+
warnings: mapped.flatMap((span) => span.warnings),
|
|
7917
|
+
unsupportedFields: mapped.flatMap((span) => span.unsupportedFields)
|
|
7918
|
+
};
|
|
7919
|
+
}
|
|
7920
|
+
var otlpJsonReader = {
|
|
7921
|
+
format: OTLP_READER_FORMAT,
|
|
7922
|
+
name: "OTLP JSON",
|
|
7923
|
+
async detect(input) {
|
|
7924
|
+
const resolved = await resolveInput(input);
|
|
7925
|
+
if (!resolved) return void 0;
|
|
7926
|
+
let parsed;
|
|
7927
|
+
try {
|
|
7928
|
+
parsed = parseJsonDocument(resolved.content);
|
|
7929
|
+
} catch {
|
|
7930
|
+
return void 0;
|
|
7931
|
+
}
|
|
7932
|
+
const document = extractOtlpDocument(parsed);
|
|
7933
|
+
if (!document) return void 0;
|
|
7934
|
+
return {
|
|
7935
|
+
format: OTLP_READER_FORMAT,
|
|
7936
|
+
confidence: document.confidence,
|
|
7937
|
+
readerName: "OTLP JSON",
|
|
7938
|
+
description: document.description,
|
|
7939
|
+
warnings: attachSingleSourceFile(document.warnings, resolved)
|
|
7940
|
+
};
|
|
7941
|
+
},
|
|
7942
|
+
async read(input) {
|
|
7943
|
+
const resolved = await resolveInput(input);
|
|
7944
|
+
if (!resolved) {
|
|
7945
|
+
throw new TraceReadError(
|
|
7946
|
+
"unsupported_format",
|
|
7947
|
+
"OTLP JSON reader requires file, string, or buffer input."
|
|
7948
|
+
);
|
|
7949
|
+
}
|
|
7950
|
+
let parsed;
|
|
7951
|
+
try {
|
|
7952
|
+
parsed = parseJsonDocument(resolved.content);
|
|
7953
|
+
} catch {
|
|
7954
|
+
throw new TraceReadError("unsupported_format", "OTLP JSON input is not valid JSON.", [
|
|
7955
|
+
{
|
|
7956
|
+
code: "otlp_invalid_json",
|
|
7957
|
+
message: "OTLP JSON reader could not parse the input as JSON.",
|
|
7958
|
+
severity: "error"
|
|
7959
|
+
}
|
|
7960
|
+
]);
|
|
7961
|
+
}
|
|
7962
|
+
const document = extractOtlpDocument(parsed);
|
|
7963
|
+
if (!document || document.spans.length === 0) {
|
|
7964
|
+
throw new TraceReadError(
|
|
7965
|
+
"unsupported_format",
|
|
7966
|
+
"No valid OTLP spans found.",
|
|
7967
|
+
attachSingleSourceFile(
|
|
7968
|
+
document?.warnings ?? [
|
|
7969
|
+
{
|
|
7970
|
+
code: "otlp_no_valid_spans",
|
|
7971
|
+
message: "OTLP JSON input did not contain valid spans.",
|
|
7972
|
+
severity: "error"
|
|
7973
|
+
}
|
|
7974
|
+
],
|
|
7975
|
+
resolved
|
|
7976
|
+
)
|
|
7977
|
+
);
|
|
7978
|
+
}
|
|
7979
|
+
const mapped = mapOtlpEventsToPersisted(document);
|
|
7980
|
+
const warnings = attachSingleSourceFile(
|
|
7981
|
+
[...document.warnings, ...mapped.warnings],
|
|
7982
|
+
resolved
|
|
7983
|
+
);
|
|
7984
|
+
const unsupportedFields = [
|
|
7985
|
+
...document.unsupportedFields,
|
|
7986
|
+
...mapped.unsupportedFields
|
|
7987
|
+
].sort((a, b) => a.localeCompare(b));
|
|
7988
|
+
return {
|
|
7989
|
+
format: OTLP_READER_FORMAT,
|
|
7990
|
+
events: mapped.events,
|
|
7991
|
+
runs: persistedInspectEventsToRunTrees(mapped.events, { skipInvalid: true }),
|
|
7992
|
+
warnings,
|
|
7993
|
+
unsupportedFields,
|
|
7994
|
+
sourceFiles: resolved.sourceFiles
|
|
7995
|
+
};
|
|
7996
|
+
}
|
|
7997
|
+
};
|
|
7998
|
+
var agentInspectJsonlReader = {
|
|
7999
|
+
format: "agent-inspect-jsonl",
|
|
8000
|
+
name: "AgentInspect JSONL",
|
|
8001
|
+
async detect(input) {
|
|
8002
|
+
const resolved = await resolveInput(input);
|
|
8003
|
+
if (!resolved) return void 0;
|
|
8004
|
+
const detected = detectJsonlFormat(resolved.content);
|
|
8005
|
+
if (detected.validRows === 0 || detected.format === "empty") {
|
|
8006
|
+
return void 0;
|
|
8007
|
+
}
|
|
8008
|
+
return {
|
|
8009
|
+
format: "agent-inspect-jsonl",
|
|
8010
|
+
confidence: 0.95,
|
|
8011
|
+
readerName: "AgentInspect JSONL",
|
|
8012
|
+
description: agentInspectFormatLabel(detected.format),
|
|
8013
|
+
warnings: attachSingleSourceFile(detected.warnings, resolved)
|
|
8014
|
+
};
|
|
8015
|
+
},
|
|
8016
|
+
async read(input) {
|
|
8017
|
+
const resolved = await resolveInput(input);
|
|
8018
|
+
if (!resolved) {
|
|
8019
|
+
throw new Error("AgentInspect JSONL reader requires file, directory, string, or buffer input.");
|
|
8020
|
+
}
|
|
8021
|
+
const parsed = parseTraceJsonl(resolved.content, { warnings: false });
|
|
8022
|
+
if (parsed.sourceEventCount === 0) {
|
|
8023
|
+
throw new Error("No valid AgentInspect JSONL events found.");
|
|
8024
|
+
}
|
|
8025
|
+
const events = persistedEventsForParsedTrace(parsed);
|
|
8026
|
+
return {
|
|
8027
|
+
format: agentInspectFormatLabel(parsed.format),
|
|
8028
|
+
events,
|
|
8029
|
+
runs: persistedInspectEventsToRunTrees(events, { skipInvalid: true }),
|
|
8030
|
+
warnings: parsed.format === "mixed" ? attachSingleSourceFile(
|
|
8031
|
+
[
|
|
8032
|
+
{
|
|
8033
|
+
code: "mixed_agent_inspect_jsonl",
|
|
8034
|
+
message: "Trace input mixes schemaVersion 0.1 and 0.2 rows; events were normalized for reading.",
|
|
8035
|
+
severity: "warning"
|
|
8036
|
+
}
|
|
8037
|
+
],
|
|
8038
|
+
resolved
|
|
8039
|
+
) : [],
|
|
8040
|
+
unsupportedFields: [],
|
|
8041
|
+
sourceFiles: resolved.sourceFiles
|
|
8042
|
+
};
|
|
8043
|
+
}
|
|
8044
|
+
};
|
|
8045
|
+
var DEFAULT_TRACE_READERS = [
|
|
8046
|
+
agentInspectJsonlReader,
|
|
8047
|
+
openInferenceJsonReader,
|
|
8048
|
+
otlpJsonReader
|
|
8049
|
+
];
|
|
8050
|
+
async function detectTraceFormat(input, options = {}) {
|
|
8051
|
+
const readers = options.readers ?? DEFAULT_TRACE_READERS;
|
|
8052
|
+
if (options.format !== void 0) {
|
|
8053
|
+
const reader = findReaderByFormat(options.format, readers);
|
|
8054
|
+
if (!reader) {
|
|
8055
|
+
return {
|
|
8056
|
+
status: "unsupported",
|
|
8057
|
+
candidates: [],
|
|
8058
|
+
warnings: [
|
|
8059
|
+
{
|
|
8060
|
+
code: "unsupported_format",
|
|
8061
|
+
message: `No trace reader is registered for format "${options.format}".`,
|
|
8062
|
+
severity: "error"
|
|
8063
|
+
}
|
|
8064
|
+
]
|
|
8065
|
+
};
|
|
8066
|
+
}
|
|
8067
|
+
return {
|
|
8068
|
+
status: "detected",
|
|
8069
|
+
format: reader.format,
|
|
8070
|
+
candidates: [
|
|
8071
|
+
{
|
|
8072
|
+
format: reader.format,
|
|
8073
|
+
confidence: 1,
|
|
8074
|
+
readerName: reader.name,
|
|
8075
|
+
description: "Explicit format override"
|
|
8076
|
+
}
|
|
8077
|
+
],
|
|
8078
|
+
warnings: []
|
|
8079
|
+
};
|
|
8080
|
+
}
|
|
8081
|
+
const candidates = [];
|
|
8082
|
+
const warnings = [];
|
|
8083
|
+
for (const reader of readers) {
|
|
8084
|
+
try {
|
|
8085
|
+
const candidate = await reader.detect(input);
|
|
8086
|
+
if (candidate !== void 0) {
|
|
8087
|
+
candidates.push(normalizeCandidate(reader, candidate));
|
|
8088
|
+
}
|
|
8089
|
+
} catch (error) {
|
|
8090
|
+
if (error instanceof TraceReadError) {
|
|
8091
|
+
warnings.push(...error.warnings);
|
|
8092
|
+
continue;
|
|
8093
|
+
}
|
|
8094
|
+
warnings.push({
|
|
8095
|
+
code: "reader_detect_failed",
|
|
8096
|
+
message: error instanceof Error && error.message.trim() !== "" ? error.message : `Trace reader "${reader.format}" failed during detection.`,
|
|
8097
|
+
severity: "warning"
|
|
8098
|
+
});
|
|
8099
|
+
}
|
|
8100
|
+
}
|
|
8101
|
+
const sorted = sortCandidates(
|
|
8102
|
+
candidates.filter((candidate) => candidate.confidence >= MIN_DETECTION_CONFIDENCE)
|
|
8103
|
+
);
|
|
8104
|
+
const candidateWarnings = collectWarnings(sorted);
|
|
8105
|
+
const lowConfidenceWarnings = candidates.length > sorted.length ? [
|
|
8106
|
+
{
|
|
8107
|
+
code: "low_confidence_candidates",
|
|
8108
|
+
message: `Ignored ${candidates.length - sorted.length} low-confidence format candidate(s).`,
|
|
8109
|
+
severity: "info"
|
|
8110
|
+
}
|
|
8111
|
+
] : [];
|
|
8112
|
+
const allWarnings = dedupeWarnings([
|
|
8113
|
+
...warnings,
|
|
8114
|
+
...candidateWarnings,
|
|
8115
|
+
...lowConfidenceWarnings
|
|
8116
|
+
]);
|
|
8117
|
+
if (sorted.length === 0) {
|
|
8118
|
+
return {
|
|
8119
|
+
status: "unsupported",
|
|
8120
|
+
candidates: [],
|
|
8121
|
+
warnings: allWarnings
|
|
8122
|
+
};
|
|
8123
|
+
}
|
|
8124
|
+
const [best, second] = sorted;
|
|
8125
|
+
if (second !== void 0 && best.confidence - second.confidence <= AMBIGUOUS_CONFIDENCE_DELTA) {
|
|
8126
|
+
return {
|
|
8127
|
+
status: "ambiguous",
|
|
8128
|
+
candidates: sorted,
|
|
8129
|
+
warnings: [
|
|
8130
|
+
...allWarnings,
|
|
8131
|
+
{
|
|
8132
|
+
code: "ambiguous_format_candidates",
|
|
8133
|
+
message: `Top trace format candidates are within ${AMBIGUOUS_CONFIDENCE_DELTA} confidence.`,
|
|
8134
|
+
severity: "warning"
|
|
8135
|
+
}
|
|
8136
|
+
]
|
|
8137
|
+
};
|
|
8138
|
+
}
|
|
8139
|
+
return {
|
|
8140
|
+
status: "detected",
|
|
8141
|
+
format: best.format,
|
|
8142
|
+
candidates: sorted,
|
|
8143
|
+
warnings: allWarnings
|
|
8144
|
+
};
|
|
8145
|
+
}
|
|
8146
|
+
async function readTrace(input, options = {}) {
|
|
8147
|
+
const readers = options.readers ?? DEFAULT_TRACE_READERS;
|
|
8148
|
+
const detection = await detectTraceFormat(input, options);
|
|
8149
|
+
if (detection.status === "unsupported" || detection.format === void 0) {
|
|
8150
|
+
throw new TraceReadError(
|
|
8151
|
+
"unsupported_format",
|
|
8152
|
+
"No trace reader could detect the input format.",
|
|
8153
|
+
detection.warnings
|
|
8154
|
+
);
|
|
8155
|
+
}
|
|
8156
|
+
if (detection.status === "ambiguous") {
|
|
8157
|
+
throw new TraceReadError(
|
|
8158
|
+
"ambiguous_format",
|
|
8159
|
+
"Multiple trace readers matched the input with equal confidence.",
|
|
8160
|
+
detection.warnings
|
|
8161
|
+
);
|
|
8162
|
+
}
|
|
8163
|
+
const reader = findReaderByFormat(detection.format, readers);
|
|
8164
|
+
if (!reader) {
|
|
8165
|
+
throw new TraceReadError(
|
|
8166
|
+
"unsupported_format",
|
|
8167
|
+
`No trace reader is registered for format "${detection.format}".`,
|
|
8168
|
+
detection.warnings
|
|
8169
|
+
);
|
|
8170
|
+
}
|
|
8171
|
+
try {
|
|
8172
|
+
const result = await reader.read(input, { format: detection.format });
|
|
8173
|
+
return {
|
|
8174
|
+
...result,
|
|
8175
|
+
format: result.format || detection.format,
|
|
8176
|
+
warnings: [...detection.warnings, ...result.warnings]
|
|
8177
|
+
};
|
|
8178
|
+
} catch (error) {
|
|
8179
|
+
if (error instanceof TraceReadError) {
|
|
8180
|
+
throw new TraceReadError(
|
|
8181
|
+
error.code,
|
|
8182
|
+
error.message,
|
|
8183
|
+
dedupeWarnings([...detection.warnings, ...error.warnings])
|
|
8184
|
+
);
|
|
8185
|
+
}
|
|
8186
|
+
throw new TraceReadError(
|
|
8187
|
+
"reader_failed",
|
|
8188
|
+
error instanceof Error && error.message.trim() !== "" ? error.message : `Trace reader "${reader.format}" failed.`,
|
|
8189
|
+
detection.warnings
|
|
8190
|
+
);
|
|
8191
|
+
}
|
|
8192
|
+
}
|
|
8193
|
+
function openTrace(input, options = {}) {
|
|
8194
|
+
return readTrace(input, options);
|
|
8195
|
+
}
|
|
8196
|
+
|
|
8197
|
+
// packages/core/src/suite/run.ts
|
|
8198
|
+
function diagnostic4(code, message, severity = "error", caseId) {
|
|
8199
|
+
return { code, message, severity, ...caseId !== void 0 ? { caseId } : {} };
|
|
8200
|
+
}
|
|
8201
|
+
function buildCaseRules(suiteCase, config) {
|
|
8202
|
+
const rules = [];
|
|
8203
|
+
const select = new Set(config.checks?.select ?? []);
|
|
8204
|
+
if (select.has("run.status")) {
|
|
8205
|
+
rules.push(createRunStatusRule());
|
|
8206
|
+
}
|
|
8207
|
+
const requiredTools = [
|
|
8208
|
+
...config.checks?.tool?.required ?? [],
|
|
8209
|
+
...suiteCase.requireTools ?? []
|
|
8210
|
+
];
|
|
8211
|
+
const forbiddenTools = [
|
|
8212
|
+
...config.checks?.tool?.forbidden ?? [],
|
|
8213
|
+
...suiteCase.forbidTools ?? []
|
|
8214
|
+
];
|
|
8215
|
+
if (requiredTools.length > 0 || forbiddenTools.length > 0) {
|
|
8216
|
+
rules.push(
|
|
8217
|
+
createToolUsageRule({
|
|
8218
|
+
required: requiredTools.length > 0 ? requiredTools : void 0,
|
|
8219
|
+
forbidden: forbiddenTools.length > 0 ? forbiddenTools : void 0
|
|
8220
|
+
})
|
|
8221
|
+
);
|
|
8222
|
+
select.add("tool.usage");
|
|
8223
|
+
}
|
|
8224
|
+
const maxDurationMs = suiteCase.maxDurationMs ?? config.checks?.run?.maxDurationMs ?? config.eval?.maxDurationMs;
|
|
8225
|
+
if (maxDurationMs !== void 0) {
|
|
8226
|
+
rules.push(createRunDurationRule({ maxDurationMs }));
|
|
8227
|
+
select.add("run.duration");
|
|
8228
|
+
}
|
|
8229
|
+
const llm = config.checks?.llm;
|
|
8230
|
+
if (llm?.allowedModels !== void 0 || llm?.maxTotalTokens !== void 0) {
|
|
8231
|
+
rules.push(createLlmUsageRule(llm));
|
|
8232
|
+
select.add("llm.usage");
|
|
8233
|
+
}
|
|
8234
|
+
if (select.has("outcome.status")) {
|
|
8235
|
+
rules.push(createObservedOutcomeRule({ failOn: ["failed"] }));
|
|
8236
|
+
}
|
|
8237
|
+
return { rules, select: [...select] };
|
|
8238
|
+
}
|
|
8239
|
+
function outcomesFromRead(read) {
|
|
8240
|
+
const persistedOutcomes = extractOutcomesFromPersistedEvents(
|
|
8241
|
+
read.events.filter((event) => event.kind === "OUTCOME")
|
|
8242
|
+
);
|
|
8243
|
+
if (persistedOutcomes.length > 0) return persistedOutcomes;
|
|
8244
|
+
const traceEvents = [];
|
|
8245
|
+
for (const event of read.events) {
|
|
8246
|
+
const legacy = event;
|
|
8247
|
+
if (typeof legacy === "object" && legacy !== null && "event" in legacy && legacy.event === "outcome_observed") {
|
|
8248
|
+
traceEvents.push(legacy);
|
|
8249
|
+
}
|
|
8250
|
+
}
|
|
8251
|
+
return traceEvents.length > 0 ? extractOutcomesFromTraceEvents(traceEvents) : [];
|
|
8252
|
+
}
|
|
8253
|
+
function validateExpectedObservations(suiteCase, read) {
|
|
8254
|
+
const expected = suiteCase.expectedObservations ?? [];
|
|
8255
|
+
if (expected.length === 0) return { ok: true, diagnostics: [] };
|
|
8256
|
+
const outcomes = outcomesFromRead(read);
|
|
8257
|
+
const diagnostics = [];
|
|
8258
|
+
for (const name of expected) {
|
|
8259
|
+
const match = outcomes.find((outcome) => outcome.name === name);
|
|
8260
|
+
if (!match) {
|
|
8261
|
+
diagnostics.push(
|
|
8262
|
+
diagnostic4(
|
|
8263
|
+
"AI_SUITE_CASE_OBSERVATION_FAILED",
|
|
8264
|
+
`Expected observation "${name}" was not recorded.`,
|
|
8265
|
+
"error",
|
|
8266
|
+
suiteCase.id
|
|
8267
|
+
)
|
|
8268
|
+
);
|
|
8269
|
+
continue;
|
|
8270
|
+
}
|
|
8271
|
+
if (match.status !== "passed") {
|
|
8272
|
+
diagnostics.push(
|
|
8273
|
+
diagnostic4(
|
|
8274
|
+
"AI_SUITE_CASE_OBSERVATION_FAILED",
|
|
8275
|
+
`Observation "${name}" has status "${match.status}", expected "passed".`,
|
|
8276
|
+
"error",
|
|
8277
|
+
suiteCase.id
|
|
8278
|
+
)
|
|
8279
|
+
);
|
|
8280
|
+
}
|
|
8281
|
+
}
|
|
8282
|
+
return { ok: diagnostics.length === 0, diagnostics };
|
|
8283
|
+
}
|
|
8284
|
+
async function runSuiteCase(suiteCase, config, options) {
|
|
8285
|
+
const resolved = await resolveSuiteCaseTrace(suiteCase, options);
|
|
8286
|
+
if (resolved.missing || resolved.tracePath === void 0) {
|
|
8287
|
+
return {
|
|
8288
|
+
id: suiteCase.id,
|
|
8289
|
+
status: "skipped",
|
|
8290
|
+
...resolved.runId !== void 0 ? { runId: resolved.runId } : {},
|
|
8291
|
+
message: resolved.reason,
|
|
8292
|
+
diagnostics: [
|
|
8293
|
+
diagnostic4(
|
|
8294
|
+
"AI_SUITE_CASE_TRACE_MISSING",
|
|
8295
|
+
resolved.reason ?? "Trace not found.",
|
|
8296
|
+
"warning",
|
|
8297
|
+
suiteCase.id
|
|
8298
|
+
)
|
|
8299
|
+
]
|
|
8300
|
+
};
|
|
8301
|
+
}
|
|
8302
|
+
let read;
|
|
8303
|
+
try {
|
|
8304
|
+
read = await openTrace({ type: "file", path: resolved.tracePath });
|
|
8305
|
+
} catch (error) {
|
|
8306
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8307
|
+
return {
|
|
8308
|
+
id: suiteCase.id,
|
|
8309
|
+
status: "error",
|
|
8310
|
+
tracePath: resolved.tracePath,
|
|
8311
|
+
...resolved.runId !== void 0 ? { runId: resolved.runId } : {},
|
|
8312
|
+
message,
|
|
8313
|
+
diagnostics: [
|
|
8314
|
+
diagnostic4("AI_SUITE_TRACE_UNREADABLE", message, "error", suiteCase.id)
|
|
8315
|
+
]
|
|
8316
|
+
};
|
|
8317
|
+
}
|
|
8318
|
+
const { rules, select } = buildCaseRules(suiteCase, config);
|
|
8319
|
+
const checkResult = rules.length > 0 ? runTraceChecks({ read }, { rules, select }) : {
|
|
8320
|
+
ok: true,
|
|
8321
|
+
status: "pass",
|
|
8322
|
+
format: read.format,
|
|
8323
|
+
findings: [],
|
|
8324
|
+
diagnostics: []
|
|
8325
|
+
};
|
|
8326
|
+
const observationResult = validateExpectedObservations(suiteCase, read);
|
|
8327
|
+
const diagnostics = [
|
|
8328
|
+
...checkResult.diagnostics.map(
|
|
8329
|
+
(item) => diagnostic4(
|
|
8330
|
+
"AI_SUITE_CASE_CHECK_FAILED",
|
|
8331
|
+
item.message,
|
|
8332
|
+
item.severity,
|
|
8333
|
+
suiteCase.id
|
|
8334
|
+
)
|
|
8335
|
+
),
|
|
8336
|
+
...checkResult.findings.filter((finding) => finding.status === "fail").map(
|
|
8337
|
+
(finding) => diagnostic4(
|
|
8338
|
+
"AI_SUITE_CASE_CHECK_FAILED",
|
|
8339
|
+
finding.message,
|
|
8340
|
+
finding.severity,
|
|
8341
|
+
suiteCase.id
|
|
8342
|
+
)
|
|
8343
|
+
),
|
|
8344
|
+
...observationResult.diagnostics
|
|
8345
|
+
];
|
|
8346
|
+
const checkOk = checkResult.ok;
|
|
8347
|
+
const observationsOk = observationResult.ok;
|
|
8348
|
+
const ok = checkOk && observationsOk;
|
|
8349
|
+
const status = ok ? "pass" : checkResult.status === "error" ? "error" : "fail";
|
|
8350
|
+
return {
|
|
8351
|
+
id: suiteCase.id,
|
|
8352
|
+
status,
|
|
8353
|
+
tracePath: resolved.tracePath,
|
|
8354
|
+
...resolved.runId !== void 0 ? { runId: resolved.runId } : {},
|
|
8355
|
+
checkOk,
|
|
8356
|
+
observationsOk,
|
|
8357
|
+
diagnostics,
|
|
8358
|
+
...ok ? {} : {
|
|
8359
|
+
message: diagnostics.map((item) => item.message).join("; ") || checkResult.findings.map((item) => item.message).join("; ")
|
|
8360
|
+
}
|
|
8361
|
+
};
|
|
8362
|
+
}
|
|
8363
|
+
async function runSuite(options = {}) {
|
|
8364
|
+
const startedAt = new Date(options.nowMs ?? Date.now()).toISOString();
|
|
8365
|
+
const { config, configPath, configDir } = await loadSuiteConfig(options);
|
|
8366
|
+
const tracesDir = path__default.default.resolve(configDir, config.traces);
|
|
8367
|
+
const cases = [];
|
|
8368
|
+
const diagnostics = [];
|
|
8369
|
+
for (const suiteCase of config.cases) {
|
|
8370
|
+
cases.push(
|
|
8371
|
+
await runSuiteCase(suiteCase, config, {
|
|
8372
|
+
configDir,
|
|
8373
|
+
tracesDir
|
|
8374
|
+
})
|
|
8375
|
+
);
|
|
8376
|
+
}
|
|
8377
|
+
const summary = {
|
|
8378
|
+
passed: cases.filter((item) => item.status === "pass").length,
|
|
8379
|
+
failed: cases.filter((item) => item.status === "fail").length,
|
|
8380
|
+
errors: cases.filter((item) => item.status === "error").length,
|
|
8381
|
+
skipped: cases.filter((item) => item.status === "skipped").length
|
|
8382
|
+
};
|
|
8383
|
+
const finishedAt = new Date(options.nowMs ?? Date.now()).toISOString();
|
|
8384
|
+
const ok = summary.failed === 0 && summary.errors === 0;
|
|
8385
|
+
const status = summary.errors > 0 ? "error" : summary.failed > 0 || !ok ? "fail" : "pass";
|
|
8386
|
+
return {
|
|
8387
|
+
ok,
|
|
8388
|
+
status,
|
|
8389
|
+
suiteName: config.name,
|
|
8390
|
+
configPath,
|
|
8391
|
+
tracesDir,
|
|
8392
|
+
startedAt,
|
|
8393
|
+
finishedAt,
|
|
8394
|
+
summary,
|
|
8395
|
+
cases,
|
|
8396
|
+
diagnostics
|
|
8397
|
+
};
|
|
8398
|
+
}
|
|
8399
|
+
|
|
8400
|
+
// packages/core/src/suite/report.ts
|
|
8401
|
+
function statusLabel(status) {
|
|
8402
|
+
return status.toUpperCase();
|
|
8403
|
+
}
|
|
8404
|
+
function renderSuiteReportMarkdown(result) {
|
|
8405
|
+
const lines = [];
|
|
8406
|
+
lines.push(`# Suite: ${result.suiteName}`);
|
|
8407
|
+
lines.push("");
|
|
8408
|
+
lines.push(`Status: **${statusLabel(result.status)}**`);
|
|
8409
|
+
lines.push(`Config: \`${result.configPath}\``);
|
|
8410
|
+
lines.push(`Traces: \`${result.tracesDir}\``);
|
|
8411
|
+
lines.push(`Started: ${result.startedAt}`);
|
|
8412
|
+
lines.push(`Finished: ${result.finishedAt}`);
|
|
8413
|
+
lines.push("");
|
|
8414
|
+
lines.push("## Summary");
|
|
8415
|
+
lines.push(`- Passed: ${result.summary.passed}`);
|
|
8416
|
+
lines.push(`- Failed: ${result.summary.failed}`);
|
|
8417
|
+
lines.push(`- Errors: ${result.summary.errors}`);
|
|
8418
|
+
lines.push(`- Skipped: ${result.summary.skipped}`);
|
|
8419
|
+
lines.push("");
|
|
8420
|
+
lines.push("## Cases");
|
|
8421
|
+
for (const suiteCase of result.cases) {
|
|
8422
|
+
lines.push(`### ${suiteCase.id} \u2014 ${statusLabel(suiteCase.status)}`);
|
|
8423
|
+
if (suiteCase.tracePath !== void 0) {
|
|
8424
|
+
lines.push(`- Trace: \`${suiteCase.tracePath}\``);
|
|
8425
|
+
}
|
|
8426
|
+
if (suiteCase.runId !== void 0) {
|
|
8427
|
+
lines.push(`- Run: \`${suiteCase.runId}\``);
|
|
8428
|
+
}
|
|
8429
|
+
if (suiteCase.message !== void 0 && suiteCase.message.trim() !== "") {
|
|
8430
|
+
lines.push(`- ${suiteCase.message}`);
|
|
8431
|
+
}
|
|
8432
|
+
if (suiteCase.diagnostics.length > 0) {
|
|
8433
|
+
for (const item of suiteCase.diagnostics) {
|
|
8434
|
+
lines.push(`- [${item.severity}] ${item.message}`);
|
|
8435
|
+
}
|
|
8436
|
+
}
|
|
8437
|
+
lines.push("");
|
|
8438
|
+
}
|
|
8439
|
+
return lines.join("\n").trimEnd();
|
|
8440
|
+
}
|
|
8441
|
+
function renderSuiteReport(result, options = {}) {
|
|
8442
|
+
const format = options.format ?? "markdown";
|
|
8443
|
+
if (format === "json") {
|
|
8444
|
+
return JSON.stringify(result, null, 2);
|
|
8445
|
+
}
|
|
8446
|
+
return renderSuiteReportMarkdown(result);
|
|
8447
|
+
}
|
|
8448
|
+
|
|
5263
8449
|
// packages/core/src/inspect-run.ts
|
|
5264
8450
|
function normalizeRunName(name) {
|
|
5265
8451
|
if (typeof name !== "string" || name.trim() === "") {
|
|
@@ -5393,6 +8579,8 @@ async function maybeInspectRun(name, fn, options) {
|
|
|
5393
8579
|
exports.DEFAULT_MAX_EVENT_BYTES = DEFAULT_MAX_EVENT_BYTES;
|
|
5394
8580
|
exports.DEFAULT_MAX_METADATA_VALUE_LENGTH = DEFAULT_MAX_METADATA_VALUE_LENGTH;
|
|
5395
8581
|
exports.DEFAULT_MAX_PREVIEW_LENGTH = DEFAULT_MAX_PREVIEW_LENGTH;
|
|
8582
|
+
exports.DEFAULT_SUITE_ARTIFACTS_DIR = DEFAULT_SUITE_ARTIFACTS_DIR;
|
|
8583
|
+
exports.DEFAULT_SUITE_CONFIG_NAMES = DEFAULT_SUITE_CONFIG_NAMES;
|
|
5396
8584
|
exports.DEFAULT_TRACE_DIR_NAME = DEFAULT_TRACE_DIR_NAME;
|
|
5397
8585
|
exports.FALLBACK_TRACE_DIR = FALLBACK_TRACE_DIR;
|
|
5398
8586
|
exports.MAX_NAME_LENGTH = MAX_NAME_LENGTH;
|
|
@@ -5420,6 +8608,7 @@ exports.createInspectorRuntime = createInspectorRuntime;
|
|
|
5420
8608
|
exports.createRunId = createRunId;
|
|
5421
8609
|
exports.createStepId = createStepId;
|
|
5422
8610
|
exports.defaultBundleOutputPath = defaultBundleOutputPath;
|
|
8611
|
+
exports.defaultSuiteConfigTemplate = defaultSuiteConfigTemplate;
|
|
5423
8612
|
exports.deriveSessionStatus = deriveSessionStatus;
|
|
5424
8613
|
exports.enrichSessionRunRecord = enrichSessionRunRecord;
|
|
5425
8614
|
exports.enrichSessionSummary = enrichSessionSummary;
|
|
@@ -5456,9 +8645,11 @@ exports.isStepType = isStepType;
|
|
|
5456
8645
|
exports.isTraceEvent = isTraceEvent;
|
|
5457
8646
|
exports.listTraceFiles = listTraceFiles;
|
|
5458
8647
|
exports.loadSessionRunRecords = loadSessionRunRecords;
|
|
8648
|
+
exports.loadSuiteConfig = loadSuiteConfig;
|
|
5459
8649
|
exports.loadTraceMetadataList = loadTraceMetadataList;
|
|
5460
8650
|
exports.maybeInspectRun = maybeInspectRun;
|
|
5461
8651
|
exports.normalizeBundleOutputPath = normalizeBundleOutputPath;
|
|
8652
|
+
exports.normalizeSuiteConfig = normalizeSuiteConfig;
|
|
5462
8653
|
exports.parseDuration = parseDuration;
|
|
5463
8654
|
exports.parseDurationFilter = parseDurationFilter;
|
|
5464
8655
|
exports.parseTraceJsonl = parseTraceJsonl;
|
|
@@ -5477,12 +8668,17 @@ exports.renderErrorLine = renderErrorLine;
|
|
|
5477
8668
|
exports.renderRunSummary = renderRunSummary;
|
|
5478
8669
|
exports.renderRunWhat = renderRunWhat;
|
|
5479
8670
|
exports.renderStepLine = renderStepLine;
|
|
8671
|
+
exports.renderSuiteReport = renderSuiteReport;
|
|
8672
|
+
exports.renderSuiteReportMarkdown = renderSuiteReportMarkdown;
|
|
5480
8673
|
exports.renderTimeline = renderTimeline;
|
|
5481
8674
|
exports.renderTraceStats = renderTraceStats;
|
|
5482
8675
|
exports.resolveBundleRunIds = resolveBundleRunIds;
|
|
5483
8676
|
exports.resolveRedactionProfile = resolveRedactionProfile;
|
|
8677
|
+
exports.resolveSuiteCaseTrace = resolveSuiteCaseTrace;
|
|
8678
|
+
exports.resolveSuiteConfigPath = resolveSuiteConfigPath;
|
|
5484
8679
|
exports.resolveTraceDir = resolveTraceDir;
|
|
5485
8680
|
exports.resolveTraceSafetyOptions = resolveTraceSafetyOptions;
|
|
8681
|
+
exports.runSuite = runSuite;
|
|
5486
8682
|
exports.runWithContext = runWithContext;
|
|
5487
8683
|
exports.runWithStepContext = runWithStepContext;
|
|
5488
8684
|
exports.searchTraces = searchTraces;
|
|
@@ -5493,6 +8689,7 @@ exports.traceMetasToSessionRunRecords = traceMetasToSessionRunRecords;
|
|
|
5493
8689
|
exports.truncateName = truncateName;
|
|
5494
8690
|
exports.unknownTraceFormatMessage = unknownTraceFormatMessage;
|
|
5495
8691
|
exports.validateEvent = validateEvent;
|
|
8692
|
+
exports.validateSuiteConfig = validateSuiteConfig;
|
|
5496
8693
|
exports.warn = warn;
|
|
5497
8694
|
exports.writeTraceEvent = writeTraceEvent;
|
|
5498
8695
|
//# sourceMappingURL=advanced.cjs.map
|