agent-inspect 4.3.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.
Files changed (102) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/docs/API.md +3 -0
  3. package/docs/CLI.md +37 -0
  4. package/docs/SCHEMA.md +27 -1
  5. package/package.json +1 -1
  6. package/packages/cli/dist/chunk-TB45H3QT.mjs +7881 -0
  7. package/packages/cli/dist/chunk-TB45H3QT.mjs.map +1 -0
  8. package/packages/cli/dist/index.cjs +10461 -9188
  9. package/packages/cli/dist/index.cjs.map +1 -1
  10. package/packages/cli/dist/index.mjs +2619 -5688
  11. package/packages/cli/dist/index.mjs.map +1 -1
  12. package/packages/cli/dist/{src-YFMPWEIS.mjs → src-ZRFSYML6.mjs} +3 -3
  13. package/packages/cli/dist/{src-YFMPWEIS.mjs.map → src-ZRFSYML6.mjs.map} +1 -1
  14. package/packages/core/dist/advanced.cjs +3410 -21
  15. package/packages/core/dist/advanced.cjs.map +1 -1
  16. package/packages/core/dist/advanced.d.cts +147 -9
  17. package/packages/core/dist/advanced.d.ts +147 -9
  18. package/packages/core/dist/advanced.mjs +603 -20
  19. package/packages/core/dist/advanced.mjs.map +1 -1
  20. package/packages/core/dist/checks.cjs +117 -18
  21. package/packages/core/dist/checks.cjs.map +1 -1
  22. package/packages/core/dist/checks.d.cts +11 -3
  23. package/packages/core/dist/checks.d.ts +11 -3
  24. package/packages/core/dist/checks.mjs +7 -1612
  25. package/packages/core/dist/checks.mjs.map +1 -1
  26. package/packages/core/dist/{chunk-2ZXEMWGK.mjs → chunk-3USJVBLA.mjs} +3 -3
  27. package/packages/core/dist/{chunk-2ZXEMWGK.mjs.map → chunk-3USJVBLA.mjs.map} +1 -1
  28. package/packages/core/dist/{chunk-HY7H3CQM.mjs → chunk-CAWPF22J.mjs} +4 -3
  29. package/packages/core/dist/chunk-CAWPF22J.mjs.map +1 -0
  30. package/packages/core/dist/{chunk-XUNJMAPD.mjs → chunk-KNYL56KZ.mjs} +38 -3
  31. package/packages/core/dist/chunk-KNYL56KZ.mjs.map +1 -0
  32. package/packages/core/dist/{chunk-IZBJAZGF.mjs → chunk-P366CCL2.mjs} +34 -3
  33. package/packages/core/dist/chunk-P366CCL2.mjs.map +1 -0
  34. package/packages/core/dist/chunk-QLYOCCEF.mjs +1650 -0
  35. package/packages/core/dist/chunk-QLYOCCEF.mjs.map +1 -0
  36. package/packages/core/dist/{chunk-4JVGFNQH.mjs → chunk-ULCGIRTC.mjs} +95 -8
  37. package/packages/core/dist/chunk-ULCGIRTC.mjs.map +1 -0
  38. package/packages/core/dist/chunk-VFO76UH3.mjs +228 -0
  39. package/packages/core/dist/chunk-VFO76UH3.mjs.map +1 -0
  40. package/packages/core/dist/{chunk-NQZPZ2FN.mjs → chunk-WRE4CNPF.mjs} +6 -3
  41. package/packages/core/dist/chunk-WRE4CNPF.mjs.map +1 -0
  42. package/packages/core/dist/chunk-X2FLDF7M.mjs +1581 -0
  43. package/packages/core/dist/chunk-X2FLDF7M.mjs.map +1 -0
  44. package/packages/core/dist/{chunk-A4V2H3YU.mjs → chunk-ZWA5RWMV.mjs} +40 -4
  45. package/packages/core/dist/chunk-ZWA5RWMV.mjs.map +1 -0
  46. package/packages/core/dist/{context-DjY-jvQk.d.cts → context-BbfkMYjz.d.cts} +5 -3
  47. package/packages/core/dist/{context-BDZmi53V.d.ts → context-Cp8NRbBX.d.ts} +5 -3
  48. package/packages/core/dist/diff.cjs.map +1 -1
  49. package/packages/core/dist/diff.d.cts +3 -3
  50. package/packages/core/dist/diff.d.ts +3 -3
  51. package/packages/core/dist/diff.mjs +1 -1
  52. package/packages/core/dist/exporters.cjs +137 -1
  53. package/packages/core/dist/exporters.cjs.map +1 -1
  54. package/packages/core/dist/exporters.d.cts +6 -3
  55. package/packages/core/dist/exporters.d.ts +6 -3
  56. package/packages/core/dist/exporters.mjs +71 -6
  57. package/packages/core/dist/exporters.mjs.map +1 -1
  58. package/packages/core/dist/index.cjs +181 -6
  59. package/packages/core/dist/index.cjs.map +1 -1
  60. package/packages/core/dist/index.d.cts +14 -7
  61. package/packages/core/dist/index.d.ts +14 -7
  62. package/packages/core/dist/index.mjs +51 -7
  63. package/packages/core/dist/index.mjs.map +1 -1
  64. package/packages/core/dist/{inspect-event-CYAV7Hxm.d.cts → inspect-event-IqwOHbGo.d.cts} +1 -1
  65. package/packages/core/dist/{inspect-event-CYAV7Hxm.d.ts → inspect-event-IqwOHbGo.d.ts} +1 -1
  66. package/packages/core/dist/{log-config-BNQ9UTmP.d.cts → log-config-CW8w5YQ9.d.cts} +1 -1
  67. package/packages/core/dist/{log-config-D3Yp1SZN.d.ts → log-config-SRA4xTp8.d.ts} +1 -1
  68. package/packages/core/dist/logs.d.cts +3 -3
  69. package/packages/core/dist/logs.d.ts +3 -3
  70. package/packages/core/dist/{persisted-inspect-event-BLvb0jSX.d.ts → persisted-inspect-event-DruwqARZ.d.ts} +1 -1
  71. package/packages/core/dist/{persisted-inspect-event-D5SjBz9b.d.cts → persisted-inspect-event-Gd-W8vB7.d.cts} +1 -1
  72. package/packages/core/dist/persisted.cjs +67 -1
  73. package/packages/core/dist/persisted.cjs.map +1 -1
  74. package/packages/core/dist/persisted.d.cts +5 -5
  75. package/packages/core/dist/persisted.d.ts +5 -5
  76. package/packages/core/dist/persisted.mjs +3 -3
  77. package/packages/core/dist/readers.cjs +72 -3
  78. package/packages/core/dist/readers.cjs.map +1 -1
  79. package/packages/core/dist/readers.d.cts +2 -2
  80. package/packages/core/dist/readers.d.ts +2 -2
  81. package/packages/core/dist/readers.mjs +4 -1580
  82. package/packages/core/dist/readers.mjs.map +1 -1
  83. package/packages/core/dist/{types-CQun9LW6.d.ts → types-B1X87dUW.d.cts} +16 -2
  84. package/packages/core/dist/{types-C-llnPH0.d.cts → types-BVkjSd0q.d.ts} +16 -2
  85. package/packages/core/dist/types-CvsKGweN.d.cts +12 -0
  86. package/packages/core/dist/types-CvsKGweN.d.ts +12 -0
  87. package/packages/core/dist/writers.cjs +2 -1
  88. package/packages/core/dist/writers.cjs.map +1 -1
  89. package/packages/core/dist/writers.d.cts +2 -2
  90. package/packages/core/dist/writers.d.ts +2 -2
  91. package/packages/core/dist/writers.mjs +3 -4
  92. package/packages/core/dist/writers.mjs.map +1 -1
  93. package/packages/cli/dist/chunk-BS5LSKZ3.mjs +0 -3671
  94. package/packages/cli/dist/chunk-BS5LSKZ3.mjs.map +0 -1
  95. package/packages/core/dist/chunk-4JVGFNQH.mjs.map +0 -1
  96. package/packages/core/dist/chunk-A4V2H3YU.mjs.map +0 -1
  97. package/packages/core/dist/chunk-EDTQHZPM.mjs +0 -88
  98. package/packages/core/dist/chunk-EDTQHZPM.mjs.map +0 -1
  99. package/packages/core/dist/chunk-HY7H3CQM.mjs.map +0 -1
  100. package/packages/core/dist/chunk-IZBJAZGF.mjs.map +0 -1
  101. package/packages/core/dist/chunk-NQZPZ2FN.mjs.map +0 -1
  102. package/packages/core/dist/chunk-XUNJMAPD.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 asString = typeof value === "string" ? value : typeof value === "number" || typeof value === "boolean" || typeof value === "bigint" ? String(value) : void 0;
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 (asString === void 0) return "[REDACTED]";
129
+ if (asString2 === void 0) return "[REDACTED]";
129
130
  const keep = Math.max(0, Math.floor(rule.keep));
130
- return asString.length <= keep ? `${asString}\u2026` : `${asString.slice(0, keep)}\u2026`;
131
+ return asString2.length <= keep ? `${asString2}\u2026` : `${asString2.slice(0, keep)}\u2026`;
131
132
  }
132
133
  if (rule.strategy === "hash") {
133
- if (asString === void 0) return "[HASH:unknown]";
134
- return `[HASH:${stableHash(asString)}]`;
134
+ if (asString2 === void 0) return "[HASH:unknown]";
135
+ return `[HASH:${stableHash(asString2)}]`;
135
136
  }
136
137
  return this.#redactNested(value);
137
138
  }
@@ -275,6 +276,9 @@ function isTraceEvent(value) {
275
276
  case "step_completed": {
276
277
  return typeof value.runId === "string" && typeof value.stepId === "string" && (value.status === "success" || value.status === "error") && typeof value.endTime === "number" && typeof value.durationMs === "number";
277
278
  }
279
+ case "outcome_observed": {
280
+ return typeof value.runId === "string" && typeof value.outcomeId === "string" && typeof value.name === "string" && typeof value.expectation === "string" && (value.status === "passed" || value.status === "failed" || value.status === "unknown" || value.status === "skipped") && typeof value.observedAt === "number";
281
+ }
278
282
  default:
279
283
  return false;
280
284
  }
@@ -292,7 +296,8 @@ var INSPECT_KINDS = [
292
296
  "RESULT",
293
297
  "ERROR",
294
298
  "LOGIC",
295
- "LOG"
299
+ "LOG",
300
+ "OUTCOME"
296
301
  ];
297
302
  var ATTRIBUTION_CONFIDENCES = [
298
303
  "explicit",
@@ -584,6 +589,32 @@ function fromLegacyStepCompleted(event) {
584
589
  if (error) out.error = error;
585
590
  return out;
586
591
  }
592
+ function fromLegacyOutcomeObserved(event) {
593
+ const attrs = event.attributes ?? {};
594
+ const observedAtRaw = attrs.observedAt;
595
+ const observedAt = typeof observedAtRaw === "string" ? Date.parse(observedAtRaw) : typeof observedAtRaw === "number" && Number.isFinite(observedAtRaw) ? observedAtRaw : resolveTimes(event).timestamp;
596
+ const status = attrs.outcomeStatus;
597
+ const out = {
598
+ schemaVersion: "0.1",
599
+ event: "outcome_observed",
600
+ timestamp: observedAt,
601
+ runId: event.runId,
602
+ outcomeId: typeof attrs.outcomeId === "string" ? attrs.outcomeId : event.eventId,
603
+ name: event.name,
604
+ expectation: typeof attrs.expectation === "string" ? attrs.expectation : event.name,
605
+ status: status === "passed" || status === "failed" || status === "unknown" || status === "skipped" ? status : "unknown",
606
+ observedAt
607
+ };
608
+ if (event.parentId !== void 0) out.parentId = event.parentId;
609
+ if (typeof attrs.method === "string") out.method = attrs.method;
610
+ if (attrs.actual !== void 0) out.actual = attrs.actual;
611
+ if (event.outputSummary !== void 0) out.actual = event.outputSummary;
612
+ if (attrs.evidence !== void 0) out.evidence = attrs.evidence;
613
+ return out;
614
+ }
615
+ function fromNativeOutcome(event) {
616
+ return [fromLegacyOutcomeObserved(event)];
617
+ }
587
618
  function fromNativeRun(event) {
588
619
  const { timestamp, startTime, endTime } = resolveTimes(event);
589
620
  const runStatus = mapPersistedStatusToRunStatus(event.status);
@@ -671,9 +702,13 @@ function persistedInspectEventToTraceEvents(event) {
671
702
  if (legacyEvent === "run_completed") return [fromLegacyRunCompleted(event)];
672
703
  if (legacyEvent === "step_started") return [fromLegacyStepStarted(event)];
673
704
  if (legacyEvent === "step_completed") return [fromLegacyStepCompleted(event)];
705
+ if (legacyEvent === "outcome_observed") return [fromLegacyOutcomeObserved(event)];
674
706
  if (event.kind === "RUN") {
675
707
  return fromNativeRun(event);
676
708
  }
709
+ if (event.kind === "OUTCOME") {
710
+ return fromNativeOutcome(event);
711
+ }
677
712
  return fromNativeStep(event);
678
713
  }
679
714
 
@@ -1012,6 +1047,9 @@ function validateEvent(event) {
1012
1047
  case "step_completed": {
1013
1048
  return nonEmptyString(event.runId) && nonEmptyString(event.stepId) && (event.status === "success" || event.status === "error") && finiteNumber(event.endTime) && finiteNumber(event.durationMs) && optionalErrorInfo(event.error);
1014
1049
  }
1050
+ case "outcome_observed": {
1051
+ return nonEmptyString(event.runId) && nonEmptyString(event.outcomeId) && nonEmptyString(event.name) && nonEmptyString(event.expectation) && (event.status === "passed" || event.status === "failed" || event.status === "unknown" || event.status === "skipped") && finiteNumber(event.observedAt);
1052
+ }
1015
1053
  default:
1016
1054
  return false;
1017
1055
  }
@@ -1956,9 +1994,155 @@ function createInspectorRuntime(options = {}) {
1956
1994
  return runtime;
1957
1995
  }
1958
1996
 
1997
+ // packages/core/src/outcomes/types.ts
1998
+ var OBSERVED_OUTCOME_STATUSES = [
1999
+ "passed",
2000
+ "failed",
2001
+ "unknown",
2002
+ "skipped"
2003
+ ];
2004
+ var OBSERVED_OUTCOME_METHODS = [
2005
+ "dom",
2006
+ "accessibility",
2007
+ "snapshot",
2008
+ "network",
2009
+ "storage",
2010
+ "filesystem",
2011
+ "database",
2012
+ "queue",
2013
+ "custom"
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";
2019
+ var OUTCOME_LEGACY_EVENT = "outcome_observed";
2020
+
2021
+ // packages/core/src/outcomes/validate.ts
2022
+ function normalizeName(name) {
2023
+ const trimmed = name.trim();
2024
+ if (trimmed === "") {
2025
+ throw new Error("observeOutcome requires a non-empty name.");
2026
+ }
2027
+ return trimmed.slice(0, 100);
2028
+ }
2029
+ function normalizeExpectation(value) {
2030
+ const trimmed = value.trim();
2031
+ if (trimmed === "") {
2032
+ throw new Error("observeOutcome requires a non-empty expectation.");
2033
+ }
2034
+ return trimmed.slice(0, 500);
2035
+ }
2036
+ function parseObservedOutcomeStatus(value) {
2037
+ const trimmed = value.trim().toLowerCase();
2038
+ if (OBSERVED_OUTCOME_STATUSES.includes(trimmed)) {
2039
+ return trimmed;
2040
+ }
2041
+ throw new Error(
2042
+ `Unsupported observation status "${value}". Use passed, failed, unknown, or skipped.`
2043
+ );
2044
+ }
2045
+ function parseMethod(value) {
2046
+ if (value === void 0 || value.trim() === "") return void 0;
2047
+ const trimmed = value.trim().toLowerCase();
2048
+ if (OBSERVED_OUTCOME_METHODS.includes(trimmed)) {
2049
+ return trimmed;
2050
+ }
2051
+ throw new Error(`Unsupported observation method "${value}".`);
2052
+ }
2053
+ function parseObservedAt(value, fallback) {
2054
+ if (value === void 0) return fallback;
2055
+ if (typeof value === "number" && Number.isFinite(value)) return value;
2056
+ if (typeof value === "string") {
2057
+ const parsed = Date.parse(value);
2058
+ if (Number.isFinite(parsed)) return parsed;
2059
+ }
2060
+ return fallback;
2061
+ }
2062
+ function normalizeObserveOutcomeInput(name, options, nowMs = Date.now()) {
2063
+ return {
2064
+ name: normalizeName(name),
2065
+ expectation: normalizeExpectation(options.expectation),
2066
+ status: options.status,
2067
+ ...options.method !== void 0 ? { method: parseMethod(options.method) } : {},
2068
+ ...options.actual !== void 0 ? { actual: options.actual } : {},
2069
+ ...options.evidence !== void 0 ? { evidence: options.evidence } : {},
2070
+ observedAt: parseObservedAt(options.observedAt, nowMs)
2071
+ };
2072
+ }
2073
+
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
+ }
2082
+ function fromOutcomeObservedEvent(event) {
2083
+ return {
2084
+ outcomeId: event.outcomeId,
2085
+ runId: event.runId,
2086
+ ...event.parentId !== void 0 ? { parentId: event.parentId } : {},
2087
+ name: event.name,
2088
+ expectation: event.expectation,
2089
+ status: event.status,
2090
+ ...event.method !== void 0 ? { method: event.method } : {},
2091
+ ...event.actual !== void 0 ? { actual: event.actual } : {},
2092
+ ...event.evidence !== void 0 ? { evidence: event.evidence } : {},
2093
+ observedAt: event.observedAt
2094
+ };
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
+ }
2117
+ function extractOutcomesFromTraceEvents(events) {
2118
+ const out = [];
2119
+ for (const event of events) {
2120
+ if (event.event === OUTCOME_LEGACY_EVENT) {
2121
+ out.push(fromOutcomeObservedEvent(event));
2122
+ }
2123
+ }
2124
+ return out.sort((a, b) => a.observedAt - b.observedAt || a.name.localeCompare(b.name));
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
+ }
2138
+ function parseObservationFilter(value) {
2139
+ if (value === void 0 || value.trim() === "") return void 0;
2140
+ return parseObservedOutcomeStatus(value);
2141
+ }
2142
+
1959
2143
  // packages/core/src/inspector.ts
1960
2144
  var INSPECTOR_PERSISTED_SCHEMA_VERSION = "1.0";
1961
- function normalizeName(name, fallback) {
2145
+ function normalizeName2(name, fallback) {
1962
2146
  if (typeof name !== "string" || name.trim() === "") return fallback;
1963
2147
  return truncateName(name.trim(), 100);
1964
2148
  }
@@ -2059,7 +2243,7 @@ function createInspector(options = {}) {
2059
2243
  if (options.enabled === false) {
2060
2244
  return Promise.resolve(fn());
2061
2245
  }
2062
- const runName = normalizeName(name, "unnamed-run");
2246
+ const runName = normalizeName2(name, "unnamed-run");
2063
2247
  const runId = runOptions.runId ?? createRunId();
2064
2248
  const traceDir = runOptions.traceDir ?? options.traceDir ?? getDefaultTraceDir();
2065
2249
  const metadata = mergeMetadata(options.metadata, runOptions.metadata);
@@ -2139,7 +2323,7 @@ function createInspector(options = {}) {
2139
2323
  return Promise.resolve(fn());
2140
2324
  }
2141
2325
  const context = runtime.getCurrentContext();
2142
- const stepName = normalizeName(name, "unnamed-step");
2326
+ const stepName = normalizeName2(name, "unnamed-step");
2143
2327
  const stepId = createStepId();
2144
2328
  const parentId = runtime.getCurrentStepId();
2145
2329
  const stepType = stepOptions.type ?? "logic";
@@ -2219,18 +2403,18 @@ function createInspector(options = {}) {
2219
2403
  run,
2220
2404
  step,
2221
2405
  tool(name, fn, toolOptions) {
2222
- const toolName = normalizeName(name, "unknown-tool");
2223
- return step(`tool:${toolName}`, fn, {
2406
+ const toolName2 = normalizeName2(name, "unknown-tool");
2407
+ return step(`tool:${toolName2}`, fn, {
2224
2408
  ...toolOptions,
2225
2409
  type: "tool",
2226
2410
  metadata: {
2227
2411
  ...toolOptions?.metadata ?? {},
2228
- toolName
2412
+ toolName: toolName2
2229
2413
  }
2230
2414
  });
2231
2415
  },
2232
2416
  llm(name, fn, llmOptions) {
2233
- const model = normalizeName(name, "unknown-model");
2417
+ const model = normalizeName2(name, "unknown-model");
2234
2418
  return step(`llm:${model}`, fn, {
2235
2419
  ...llmOptions,
2236
2420
  type: "llm",
@@ -2243,6 +2427,41 @@ function createInspector(options = {}) {
2243
2427
  observe(name, fn, observeOptions) {
2244
2428
  return async (...args) => step(name, () => Promise.resolve(fn(...args)), observeOptions);
2245
2429
  },
2430
+ async observeOutcome(name, outcomeOptions) {
2431
+ const ctx = runtime.getCurrentContext();
2432
+ if (!ctx) return;
2433
+ let normalized;
2434
+ try {
2435
+ normalized = normalizeObserveOutcomeInput(name, outcomeOptions);
2436
+ } catch {
2437
+ return;
2438
+ }
2439
+ const outcomeId = createStepId();
2440
+ const parentId = runtime.getCurrentStepId();
2441
+ await write({
2442
+ schemaVersion: INSPECTOR_PERSISTED_SCHEMA_VERSION,
2443
+ eventId: outcomeId,
2444
+ runId: ctx.runId,
2445
+ ...parentId !== void 0 ? { parentId } : {},
2446
+ kind: "OUTCOME",
2447
+ name: normalized.name,
2448
+ status: normalized.status === "failed" ? "error" : "ok",
2449
+ timestamp: new Date(normalized.observedAt).toISOString(),
2450
+ confidence: "explicit",
2451
+ source: { type: "manual", name: "createInspector" },
2452
+ attributes: {
2453
+ legacyEvent: "outcome_observed",
2454
+ outcomeId,
2455
+ outcomeStatus: normalized.status,
2456
+ expectation: normalized.expectation,
2457
+ ...normalized.method !== void 0 ? { method: normalized.method } : {},
2458
+ ...normalized.actual !== void 0 ? { actual: normalized.actual } : {},
2459
+ ...normalized.evidence !== void 0 ? { evidence: normalized.evidence } : {},
2460
+ observedAt: new Date(normalized.observedAt).toISOString()
2461
+ },
2462
+ ...normalized.actual !== void 0 ? { outputSummary: normalized.actual } : {}
2463
+ });
2464
+ },
2246
2465
  getDiagnostics() {
2247
2466
  return runtime.getDiagnostics();
2248
2467
  },
@@ -3867,8 +4086,9 @@ async function searchTraces(metas, options) {
3867
4086
  }
3868
4087
  const limit = options.limit ?? 50;
3869
4088
  const sessionId = options.session?.trim();
4089
+ const observationStatus = parseObservationFilter(options.observation);
3870
4090
  const hasContentFilter = Boolean(
3871
- options.status || stepTypeFilter || nameQuery || toolQuery || durationFilter
4091
+ options.status || stepTypeFilter || nameQuery || toolQuery || durationFilter || observationStatus
3872
4092
  );
3873
4093
  const results = [];
3874
4094
  const sessionLabel = sessionId && sessionId !== "" ? sessionId : void 0;
@@ -3913,6 +4133,22 @@ async function searchTraces(metas, options) {
3913
4133
  statusFilter: options.status
3914
4134
  });
3915
4135
  results.push(...stepMatches);
4136
+ if (observationStatus) {
4137
+ const outcomes = extractOutcomesFromTraceEvents(events);
4138
+ const matched = outcomes.filter((outcome) => outcome.status === observationStatus);
4139
+ for (const outcome of matched) {
4140
+ results.push({
4141
+ runId: m.runId,
4142
+ runName: m.name,
4143
+ runStatus: m.status,
4144
+ stepName: outcome.name,
4145
+ timestamp: outcome.observedAt,
4146
+ matchReason: `observation status=${outcome.status}`,
4147
+ matchedFields: ["outcome.status", "outcome.name"],
4148
+ filePath: m.filePath
4149
+ });
4150
+ }
4151
+ }
3916
4152
  }
3917
4153
  results.sort((a, b) => {
3918
4154
  const ta = a.timestamp ?? 0;
@@ -3982,8 +4218,8 @@ function matchStepLevel(m, events, opts) {
3982
4218
  fields.push("step.name");
3983
4219
  }
3984
4220
  if (opts.toolQuery) {
3985
- const toolName = typeof s.metadata?.toolName === "string" ? s.metadata.toolName : s.name;
3986
- if (!nameMatches(toolName, opts.toolQuery)) continue;
4221
+ const toolName2 = typeof s.metadata?.toolName === "string" ? s.metadata.toolName : s.name;
4222
+ if (!nameMatches(toolName2, opts.toolQuery)) continue;
3987
4223
  fields.push("step.tool");
3988
4224
  }
3989
4225
  if (opts.durationFilter) {
@@ -4301,9 +4537,10 @@ function buildActivitySummary(index, options = {}) {
4301
4537
  entries
4302
4538
  };
4303
4539
  }
4304
- function renderActivitySummaryHuman(summary) {
4540
+ function renderActivitySummaryHuman(summary, options) {
4305
4541
  const lines = [];
4306
- const todayStart = /* @__PURE__ */ new Date();
4542
+ const nowMs = options?.nowMs ?? Date.now();
4543
+ const todayStart = new Date(nowMs);
4307
4544
  todayStart.setHours(0, 0, 0, 0);
4308
4545
  const todayMs = todayStart.getTime();
4309
4546
  const today = summary.entries.filter(
@@ -4734,12 +4971,12 @@ function buildCriticalPath(runs, handoffs) {
4734
4971
  handoffs.filter((edge) => edge.confidence === "explicit").map((edge) => edge.from)
4735
4972
  );
4736
4973
  const ordered = [...runs].sort(compareRuns);
4737
- const path6 = [];
4974
+ const path11 = [];
4738
4975
  const visited = /* @__PURE__ */ new Set();
4739
4976
  const pushRun = (run, confidence, source) => {
4740
4977
  if (visited.has(run.runId)) return;
4741
4978
  visited.add(run.runId);
4742
- path6.push({
4979
+ path11.push({
4743
4980
  runId: run.runId,
4744
4981
  name: run.name,
4745
4982
  startedAt: run.startedAt,
@@ -4764,7 +5001,7 @@ function buildCriticalPath(runs, handoffs) {
4764
5001
  const confidence = explicitTargets.has(run.runId) || explicitSources.has(run.runId) ? "explicit" : "correlated";
4765
5002
  pushRun(run, confidence, confidence === "explicit" ? "manual" : "inferred");
4766
5003
  }
4767
- return path6;
5004
+ return path11;
4768
5005
  }
4769
5006
  function metaRunIdMatches(run, token, runById) {
4770
5007
  const meta = extractSessionWorkflowMetadata(run.metadata);
@@ -5068,6 +5305,3147 @@ function defaultBundleOutputPath(runIds) {
5068
5305
  return path__default.default.resolve(`agent-inspect-bundle-${label}-${stamp}`);
5069
5306
  }
5070
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
+
5071
8449
  // packages/core/src/inspect-run.ts
5072
8450
  function normalizeRunName(name) {
5073
8451
  if (typeof name !== "string" || name.trim() === "") {
@@ -5201,6 +8579,8 @@ async function maybeInspectRun(name, fn, options) {
5201
8579
  exports.DEFAULT_MAX_EVENT_BYTES = DEFAULT_MAX_EVENT_BYTES;
5202
8580
  exports.DEFAULT_MAX_METADATA_VALUE_LENGTH = DEFAULT_MAX_METADATA_VALUE_LENGTH;
5203
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;
5204
8584
  exports.DEFAULT_TRACE_DIR_NAME = DEFAULT_TRACE_DIR_NAME;
5205
8585
  exports.FALLBACK_TRACE_DIR = FALLBACK_TRACE_DIR;
5206
8586
  exports.MAX_NAME_LENGTH = MAX_NAME_LENGTH;
@@ -5228,6 +8608,7 @@ exports.createInspectorRuntime = createInspectorRuntime;
5228
8608
  exports.createRunId = createRunId;
5229
8609
  exports.createStepId = createStepId;
5230
8610
  exports.defaultBundleOutputPath = defaultBundleOutputPath;
8611
+ exports.defaultSuiteConfigTemplate = defaultSuiteConfigTemplate;
5231
8612
  exports.deriveSessionStatus = deriveSessionStatus;
5232
8613
  exports.enrichSessionRunRecord = enrichSessionRunRecord;
5233
8614
  exports.enrichSessionSummary = enrichSessionSummary;
@@ -5264,9 +8645,11 @@ exports.isStepType = isStepType;
5264
8645
  exports.isTraceEvent = isTraceEvent;
5265
8646
  exports.listTraceFiles = listTraceFiles;
5266
8647
  exports.loadSessionRunRecords = loadSessionRunRecords;
8648
+ exports.loadSuiteConfig = loadSuiteConfig;
5267
8649
  exports.loadTraceMetadataList = loadTraceMetadataList;
5268
8650
  exports.maybeInspectRun = maybeInspectRun;
5269
8651
  exports.normalizeBundleOutputPath = normalizeBundleOutputPath;
8652
+ exports.normalizeSuiteConfig = normalizeSuiteConfig;
5270
8653
  exports.parseDuration = parseDuration;
5271
8654
  exports.parseDurationFilter = parseDurationFilter;
5272
8655
  exports.parseTraceJsonl = parseTraceJsonl;
@@ -5285,12 +8668,17 @@ exports.renderErrorLine = renderErrorLine;
5285
8668
  exports.renderRunSummary = renderRunSummary;
5286
8669
  exports.renderRunWhat = renderRunWhat;
5287
8670
  exports.renderStepLine = renderStepLine;
8671
+ exports.renderSuiteReport = renderSuiteReport;
8672
+ exports.renderSuiteReportMarkdown = renderSuiteReportMarkdown;
5288
8673
  exports.renderTimeline = renderTimeline;
5289
8674
  exports.renderTraceStats = renderTraceStats;
5290
8675
  exports.resolveBundleRunIds = resolveBundleRunIds;
5291
8676
  exports.resolveRedactionProfile = resolveRedactionProfile;
8677
+ exports.resolveSuiteCaseTrace = resolveSuiteCaseTrace;
8678
+ exports.resolveSuiteConfigPath = resolveSuiteConfigPath;
5292
8679
  exports.resolveTraceDir = resolveTraceDir;
5293
8680
  exports.resolveTraceSafetyOptions = resolveTraceSafetyOptions;
8681
+ exports.runSuite = runSuite;
5294
8682
  exports.runWithContext = runWithContext;
5295
8683
  exports.runWithStepContext = runWithStepContext;
5296
8684
  exports.searchTraces = searchTraces;
@@ -5301,6 +8689,7 @@ exports.traceMetasToSessionRunRecords = traceMetasToSessionRunRecords;
5301
8689
  exports.truncateName = truncateName;
5302
8690
  exports.unknownTraceFormatMessage = unknownTraceFormatMessage;
5303
8691
  exports.validateEvent = validateEvent;
8692
+ exports.validateSuiteConfig = validateSuiteConfig;
5304
8693
  exports.warn = warn;
5305
8694
  exports.writeTraceEvent = writeTraceEvent;
5306
8695
  //# sourceMappingURL=advanced.cjs.map