@agent-inspect/mcp-server 4.4.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -7,6 +7,7 @@ import os from 'os';
7
7
  import 'nanoid';
8
8
  import 'chalk';
9
9
  import 'fs';
10
+ import 'url';
10
11
 
11
12
  // packages/mcp-server/src/index.ts
12
13
 
@@ -1524,7 +1525,7 @@ function summarize(findings, diagnostics) {
1524
1525
  errors: diagnostics.filter((item) => item.severity === "error").length
1525
1526
  };
1526
1527
  }
1527
- function eventEvidence(event, path8) {
1528
+ function eventEvidence(event, path12) {
1528
1529
  return {
1529
1530
  runId: event.runId,
1530
1531
  eventId: event.eventId,
@@ -1635,1059 +1636,492 @@ function runTraceChecks(input, options = {}) {
1635
1636
  };
1636
1637
  }
1637
1638
 
1638
- // packages/core/src/diff/comparable.ts
1639
- function extractOutputPreview(meta) {
1640
- if (meta === void 0) return void 0;
1641
- if ("outputPreview" in meta) return meta.outputPreview;
1642
- if ("resultPreview" in meta) return meta.resultPreview;
1643
- return void 0;
1639
+ // packages/core/src/persisted/token-usage.ts
1640
+ function isRecord5(value) {
1641
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1644
1642
  }
1645
- function mapStepStatus(s) {
1646
- if (s === void 0) return "running";
1647
- return s;
1643
+ function nonNegativeFinite(value) {
1644
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
1648
1645
  }
1649
- function manualTraceEventsToComparableRun(events) {
1650
- const started = events.find((e) => e.event === "run_started");
1651
- if (!started || started.event !== "run_started") {
1652
- throw new Error("Invalid trace: missing run_started");
1653
- }
1654
- const rs = started;
1655
- const runId = rs.runId;
1656
- const completedAll = events.filter((e) => e.event === "run_completed");
1657
- const lastCompleted = completedAll[completedAll.length - 1];
1658
- let runStatus;
1659
- if (lastCompleted === void 0) runStatus = "running";
1660
- else runStatus = lastCompleted.status;
1661
- const durationMs = lastCompleted !== void 0 && Number.isFinite(lastCompleted.durationMs) ? lastCompleted.durationMs : void 0;
1662
- const steps = /* @__PURE__ */ new Map();
1663
- let order = 0;
1664
- for (const e of events) {
1665
- if (e.event !== "step_started") continue;
1666
- const s = e;
1667
- const meta = s.metadata ? { ...s.metadata } : void 0;
1668
- steps.set(s.stepId, {
1669
- id: s.stepId,
1670
- parentId: s.parentId,
1671
- name: s.name,
1672
- type: s.type,
1673
- order: order++,
1674
- timestamp: s.timestamp,
1675
- metadata: meta
1676
- });
1677
- }
1678
- for (const e of events) {
1679
- if (e.event !== "step_completed") continue;
1680
- const acc = steps.get(e.stepId);
1681
- if (!acc) continue;
1682
- acc.status = e.status;
1683
- acc.durationMs = e.durationMs;
1684
- if (e.error?.message) acc.errorMsg = e.error.message;
1685
- const extra = e;
1686
- if (extra.metadata !== void 0 && typeof extra.metadata === "object") {
1687
- acc.metadata = { ...acc.metadata ?? {}, ...extra.metadata };
1688
- }
1689
- }
1690
- const nodes = /* @__PURE__ */ new Map();
1691
- for (const acc of steps.values()) {
1692
- let meta = acc.metadata ? { ...acc.metadata } : void 0;
1693
- if (acc.parentId !== void 0 && !steps.has(acc.parentId)) {
1694
- meta = { ...meta ?? {}, agent_inspect_diff_parent_missing: true };
1695
- }
1696
- const outputPreview = extractOutputPreview(meta);
1697
- const sc = {
1698
- id: acc.id,
1699
- name: acc.name,
1700
- type: acc.type,
1701
- status: mapStepStatus(acc.status),
1702
- durationMs: acc.durationMs,
1703
- error: acc.errorMsg,
1704
- metadata: meta && Object.keys(meta).length > 0 ? meta : void 0,
1705
- outputPreview,
1706
- children: []
1707
- };
1708
- nodes.set(acc.id, sc);
1709
- }
1710
- const roots = [];
1711
- const sortByOrder = (a, b) => {
1712
- const oa = steps.get(a.id)?.order ?? 0;
1713
- const ob = steps.get(b.id)?.order ?? 0;
1714
- return oa - ob;
1715
- };
1716
- for (const acc of steps.values()) {
1717
- const node = nodes.get(acc.id);
1718
- if (acc.parentId !== void 0 && nodes.has(acc.parentId)) {
1719
- nodes.get(acc.parentId).children.push(node);
1720
- } else {
1721
- roots.push(node);
1722
- }
1723
- }
1724
- roots.sort(sortByOrder);
1725
- for (const n of nodes.values()) {
1726
- n.children.sort(sortByOrder);
1646
+ function normalizeTokenUsage(value) {
1647
+ if (!isRecord5(value)) return void 0;
1648
+ const input = nonNegativeFinite(value.input);
1649
+ const output = nonNegativeFinite(value.output);
1650
+ const suppliedTotal = nonNegativeFinite(value.total);
1651
+ const cached = nonNegativeFinite(value.cached);
1652
+ const derivedTotal = input !== void 0 && output !== void 0 && Number.isFinite(input + output) ? input + output : void 0;
1653
+ const total = suppliedTotal ?? derivedTotal;
1654
+ if (input === void 0 && output === void 0 && total === void 0 && cached === void 0) {
1655
+ return void 0;
1727
1656
  }
1728
1657
  return {
1729
- runId,
1730
- name: rs.name,
1731
- status: runStatus,
1732
- durationMs,
1733
- steps: roots
1658
+ ...input !== void 0 ? { input } : {},
1659
+ ...output !== void 0 ? { output } : {},
1660
+ ...total !== void 0 ? { total } : {},
1661
+ ...cached !== void 0 ? { cached } : {}
1734
1662
  };
1735
1663
  }
1736
1664
 
1737
- // packages/core/src/exporters/helpers.ts
1738
- var REDACT_SUBSTRINGS = [
1739
- "authorization",
1740
- "cookie",
1741
- "token",
1742
- "apikey",
1743
- "password",
1744
- "secret",
1745
- "email"
1746
- ];
1747
- function shouldRedactKey(key) {
1748
- const k = key.toLowerCase();
1749
- for (const s of REDACT_SUBSTRINGS) {
1750
- if (k.includes(s)) return true;
1665
+ // packages/core/src/persisted/from-trace-event.ts
1666
+ function sanitizeIdPart(value) {
1667
+ return value.replace(/[^a-zA-Z0-9_-]/g, "_");
1668
+ }
1669
+ function nodeIdForEvent(event) {
1670
+ switch (event.event) {
1671
+ case "run_started":
1672
+ case "run_completed":
1673
+ return event.runId;
1674
+ case "step_started":
1675
+ case "step_completed":
1676
+ return event.stepId;
1677
+ case "outcome_observed":
1678
+ return event.outcomeId;
1679
+ default:
1680
+ return "unknown";
1751
1681
  }
1752
- return false;
1753
1682
  }
1754
- function safeString(value, maxLength) {
1755
- if (value === null || value === void 0) return "";
1756
- let s;
1757
- if (typeof value === "string") s = value;
1758
- else if (typeof value === "number" || typeof value === "boolean") s = String(value);
1759
- else s = stableJson(value, false);
1760
- if (maxLength !== void 0 && maxLength >= 0 && s.length > maxLength) {
1761
- return `${s.slice(0, maxLength)}\u2026`;
1683
+ function createPersistedEventId(event, eventIndex) {
1684
+ const runId = sanitizeIdPart(event.runId);
1685
+ const ev = sanitizeIdPart(event.event);
1686
+ const node = sanitizeIdPart(nodeIdForEvent(event));
1687
+ return `manual:${runId}:${ev}:${node}:${eventIndex}`;
1688
+ }
1689
+ function toIsoTimestamp(ms) {
1690
+ if (typeof ms !== "number" || !Number.isFinite(ms)) {
1691
+ return { iso: (/* @__PURE__ */ new Date(0)).toISOString(), invalidTimestamp: true };
1762
1692
  }
1763
- return s;
1693
+ return { iso: new Date(ms).toISOString(), invalidTimestamp: false };
1764
1694
  }
1765
- function escapeMarkdown(value) {
1766
- return value.replace(/\|/g, "\\|").replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\n/g, " ");
1695
+ function buildSource(options) {
1696
+ return {
1697
+ type: "manual",
1698
+ name: options?.sourceName ?? "trace-event",
1699
+ version: options?.sourceVersion ?? "0.1"
1700
+ };
1767
1701
  }
1768
- function sortKeysDeep(input) {
1769
- if (input === null || typeof input !== "object") return input;
1770
- if (Array.isArray(input)) return input.map(sortKeysDeep);
1771
- const o = input;
1772
- const out = {};
1773
- for (const k of Object.keys(o).sort()) {
1774
- out[k] = sortKeysDeep(o[k]);
1702
+ function mapStepTypeToInspectKind(type) {
1703
+ switch (type) {
1704
+ case "run":
1705
+ return "RUN";
1706
+ case "llm":
1707
+ return "LLM";
1708
+ case "tool":
1709
+ return "TOOL";
1710
+ case "decision":
1711
+ return "DECISION";
1712
+ case "logic":
1713
+ case "state":
1714
+ case "custom":
1715
+ return "LOGIC";
1716
+ default:
1717
+ return "LOGIC";
1775
1718
  }
1776
- return out;
1777
1719
  }
1778
- function stableJson(value, pretty) {
1779
- const sorted = sortKeysDeep(value);
1780
- return pretty === true ? JSON.stringify(sorted, null, 2) : JSON.stringify(sorted);
1720
+ function mapRunOrStepStatus(status) {
1721
+ return status === "success" ? "ok" : "error";
1781
1722
  }
1782
- function compactAttributes(attrs, options) {
1783
- if (attrs === void 0) return {};
1784
- const maxLen = options?.maxLength ?? 500;
1785
- const out = {};
1786
- for (const key of Object.keys(attrs).sort()) {
1787
- if (shouldRedactKey(key)) {
1788
- out[key] = "[REDACTED]";
1789
- continue;
1723
+ function mapErrorInfo(error) {
1724
+ if (!error?.message) {
1725
+ return {};
1726
+ }
1727
+ const out = {
1728
+ persisted: {
1729
+ message: error.message,
1730
+ name: "Error"
1790
1731
  }
1791
- const v = attrs[key];
1792
- out[key] = compactValue(v, maxLen);
1732
+ };
1733
+ if (typeof error.stack === "string" && error.stack.length > 0) {
1734
+ out.errorStack = error.stack;
1793
1735
  }
1794
1736
  return out;
1795
1737
  }
1796
- function compactValue(value, maxLen, redacted) {
1797
- if (value === null || typeof value !== "object") {
1798
- return typeof value === "string" ? safeString(value, maxLen) : value;
1799
- }
1800
- if (Array.isArray(value)) {
1801
- const arr = value.slice(0, 20).map((x) => compactValue(x, maxLen));
1802
- if (value.length > 20) arr.push(`\u2026(+${value.length - 20} more)`);
1803
- return arr;
1804
- }
1805
- const o = value;
1806
- const inner = {};
1807
- for (const k of Object.keys(o)) {
1808
- if (shouldRedactKey(k)) inner[k] = "[REDACTED]";
1809
- else inner[k] = compactValue(o[k], maxLen);
1810
- }
1811
- return inner;
1738
+ function mapTokenUsageFromMetadata(metadata) {
1739
+ return normalizeTokenUsage(metadata?.tokens);
1812
1740
  }
1813
- function flattenTree(tree) {
1814
- const out = [];
1815
- function walk(nodes) {
1816
- for (const n of nodes) {
1817
- out.push(n);
1818
- if (n.children.length > 0) walk(n.children);
1741
+ function compactAttributes(entries) {
1742
+ const out = {};
1743
+ for (const [key, value] of Object.entries(entries)) {
1744
+ if (value !== void 0) {
1745
+ out[key] = value;
1819
1746
  }
1820
1747
  }
1821
- walk(tree.children);
1822
- return out;
1823
- }
1824
-
1825
- // packages/core/src/diff/engine.ts
1826
- var DEFAULT_THRESHOLD_MS = 0;
1827
- function pathSeg(step, index) {
1828
- return { index, name: step.name, stepId: step.id };
1748
+ return Object.keys(out).length > 0 ? out : void 0;
1829
1749
  }
1830
- function buildPath(segments) {
1831
- return { path: [...segments] };
1832
- }
1833
- function pairSteps(left, right) {
1834
- const usedRight = /* @__PURE__ */ new Set();
1835
- const pairs = [];
1836
- for (let i = 0; i < left.length; i++) {
1837
- const L = left[i];
1838
- let R = right.find((r) => !usedRight.has(r.id) && r.id === L.id);
1839
- if (R === void 0 && i < right.length && !usedRight.has(right[i].id)) {
1840
- const cand = right[i];
1841
- if (cand.name === L.name && (cand.type ?? "") === (L.type ?? "")) {
1842
- R = cand;
1750
+ function traceEventToPersistedInspectEvent(event, options) {
1751
+ const eventIndex = options?.eventIndex ?? 0;
1752
+ const eventId = createPersistedEventId(event, eventIndex);
1753
+ const source = buildSource(options);
1754
+ const tsMain = toIsoTimestamp(event.timestamp);
1755
+ switch (event.event) {
1756
+ case "run_started": {
1757
+ const tsStart = toIsoTimestamp(event.startTime);
1758
+ const correlation = extractCorrelationMetadata(event.metadata);
1759
+ const attributes = compactAttributes({
1760
+ legacyEvent: "run_started",
1761
+ metadata: event.metadata !== void 0 ? { ...event.metadata } : void 0,
1762
+ correlationId: correlation?.correlationId,
1763
+ requestId: correlation?.requestId,
1764
+ decisionId: correlation?.decisionId,
1765
+ groupId: correlation?.groupId,
1766
+ invalidTimestamp: tsMain.invalidTimestamp || tsStart.invalidTimestamp ? true : void 0
1767
+ });
1768
+ return {
1769
+ schemaVersion: "0.2",
1770
+ eventId,
1771
+ runId: event.runId,
1772
+ kind: "RUN",
1773
+ name: event.name,
1774
+ status: "running",
1775
+ timestamp: tsMain.iso,
1776
+ startedAt: tsStart.iso,
1777
+ confidence: "explicit",
1778
+ source,
1779
+ attributes
1780
+ };
1781
+ }
1782
+ case "run_completed": {
1783
+ const tsEnd = toIsoTimestamp(event.endTime);
1784
+ const { persisted: error, errorStack } = mapErrorInfo(event.error);
1785
+ const attributes = compactAttributes({
1786
+ legacyEvent: "run_completed",
1787
+ errorStack,
1788
+ invalidTimestamp: tsMain.invalidTimestamp || tsEnd.invalidTimestamp ? true : void 0
1789
+ });
1790
+ return {
1791
+ schemaVersion: "0.2",
1792
+ eventId,
1793
+ runId: event.runId,
1794
+ kind: "RUN",
1795
+ name: "run",
1796
+ status: mapRunOrStepStatus(event.status),
1797
+ timestamp: tsMain.iso,
1798
+ endedAt: tsEnd.iso,
1799
+ durationMs: event.durationMs,
1800
+ confidence: "explicit",
1801
+ source,
1802
+ attributes,
1803
+ error
1804
+ };
1805
+ }
1806
+ case "step_started": {
1807
+ const tsStart = toIsoTimestamp(event.startTime);
1808
+ const tokenUsage = mapTokenUsageFromMetadata(event.metadata);
1809
+ const attributes = compactAttributes({
1810
+ legacyEvent: "step_started",
1811
+ stepId: event.stepId,
1812
+ stepType: event.type,
1813
+ metadata: event.metadata !== void 0 ? { ...event.metadata } : void 0,
1814
+ invalidTimestamp: tsMain.invalidTimestamp || tsStart.invalidTimestamp ? true : void 0
1815
+ });
1816
+ const out = {
1817
+ schemaVersion: "0.2",
1818
+ eventId,
1819
+ runId: event.runId,
1820
+ kind: mapStepTypeToInspectKind(event.type),
1821
+ name: event.name,
1822
+ status: "running",
1823
+ timestamp: tsMain.iso,
1824
+ startedAt: tsStart.iso,
1825
+ confidence: "explicit",
1826
+ source,
1827
+ attributes
1828
+ };
1829
+ if (event.parentId !== void 0) {
1830
+ out.parentId = event.parentId;
1831
+ }
1832
+ if (tokenUsage !== void 0) {
1833
+ out.tokenUsage = tokenUsage;
1843
1834
  }
1835
+ return out;
1844
1836
  }
1845
- if (R === void 0) {
1846
- R = right.find(
1847
- (r) => !usedRight.has(r.id) && r.name === L.name && (r.type ?? "") === (L.type ?? "")
1848
- );
1837
+ case "step_completed": {
1838
+ const tsEnd = toIsoTimestamp(event.endTime);
1839
+ const { persisted: error, errorStack } = mapErrorInfo(event.error);
1840
+ const attributes = compactAttributes({
1841
+ legacyEvent: "step_completed",
1842
+ stepId: event.stepId,
1843
+ errorStack,
1844
+ invalidTimestamp: tsMain.invalidTimestamp || tsEnd.invalidTimestamp ? true : void 0
1845
+ });
1846
+ return {
1847
+ schemaVersion: "0.2",
1848
+ eventId,
1849
+ runId: event.runId,
1850
+ kind: "LOGIC",
1851
+ name: event.stepId,
1852
+ status: mapRunOrStepStatus(event.status),
1853
+ timestamp: tsMain.iso,
1854
+ endedAt: tsEnd.iso,
1855
+ durationMs: event.durationMs,
1856
+ confidence: "explicit",
1857
+ source,
1858
+ attributes,
1859
+ error
1860
+ };
1849
1861
  }
1850
- if (R !== void 0) {
1851
- usedRight.add(R.id);
1852
- pairs.push([L, R]);
1853
- } else {
1854
- pairs.push([L, void 0]);
1862
+ case "outcome_observed": {
1863
+ const tsObserved = toIsoTimestamp(event.observedAt);
1864
+ const attributes = compactAttributes({
1865
+ legacyEvent: "outcome_observed",
1866
+ outcomeId: event.outcomeId,
1867
+ outcomeStatus: event.status,
1868
+ expectation: event.expectation,
1869
+ method: event.method,
1870
+ actual: event.actual,
1871
+ evidence: event.evidence,
1872
+ observedAt: tsObserved.iso,
1873
+ invalidTimestamp: tsMain.invalidTimestamp || tsObserved.invalidTimestamp ? true : void 0
1874
+ });
1875
+ const out = {
1876
+ schemaVersion: "0.2",
1877
+ eventId,
1878
+ runId: event.runId,
1879
+ kind: "OUTCOME",
1880
+ name: event.name,
1881
+ status: event.status === "failed" ? "error" : "ok",
1882
+ timestamp: tsMain.iso,
1883
+ confidence: "explicit",
1884
+ source,
1885
+ attributes
1886
+ };
1887
+ if (event.parentId !== void 0) {
1888
+ out.parentId = event.parentId;
1889
+ }
1890
+ if (event.actual !== void 0) {
1891
+ out.outputSummary = event.actual;
1892
+ }
1893
+ return out;
1855
1894
  }
1856
- }
1857
- for (const R of right) {
1858
- if (!usedRight.has(R.id)) {
1859
- pairs.push([void 0, R]);
1895
+ default: {
1896
+ const _exhaustive = event;
1897
+ throw new Error(`Unsupported trace event: ${_exhaustive.event}`);
1860
1898
  }
1861
1899
  }
1862
- return pairs;
1863
1900
  }
1864
- function compareLeafSteps(L, R, segments, opts, out) {
1865
- const path8 = buildPath(segments);
1866
- if (L.name !== R.name) {
1867
- out.push({
1868
- kind: "structure",
1869
- severity: "warning",
1870
- message: "Step name differs",
1871
- path: path8,
1872
- left: L.name,
1873
- right: R.name
1874
- });
1875
- }
1876
- if ((L.type ?? "") !== (R.type ?? "")) {
1877
- out.push({
1878
- kind: "step-type",
1879
- severity: "warning",
1880
- message: "Step type differs",
1881
- path: path8,
1882
- left: L.type,
1883
- right: R.type
1884
- });
1885
- }
1886
- if ((L.status ?? "") !== (R.status ?? "")) {
1887
- out.push({
1888
- kind: "step-status",
1889
- severity: "warning",
1890
- message: "Step status differs",
1891
- path: path8,
1892
- left: L.status,
1893
- right: R.status
1894
- });
1901
+ function traceEventsToPersistedInspectEvents(events, options) {
1902
+ return events.map(
1903
+ (event, index) => traceEventToPersistedInspectEvent(event, { ...options, eventIndex: index })
1904
+ );
1905
+ }
1906
+
1907
+ // packages/core/src/logs/tree-builder.ts
1908
+ function inc(map, key) {
1909
+ map[key] = (map[key] ?? 0) + 1;
1910
+ }
1911
+ function computeRunStatus(events) {
1912
+ let hasRunning = false;
1913
+ for (const e of events) {
1914
+ if (e.status === "error") return "error";
1915
+ if (e.status === "running") hasRunning = true;
1895
1916
  }
1896
- const le = L.error ?? "";
1897
- const re = R.error ?? "";
1898
- if (le !== re) {
1899
- out.push({
1900
- kind: "error",
1901
- severity: "error",
1902
- message: "Step error message differs",
1903
- path: path8,
1904
- left: le || void 0,
1905
- right: re || void 0
1906
- });
1917
+ if (hasRunning) return "running";
1918
+ return "ok";
1919
+ }
1920
+ var TreeBuilder = class {
1921
+ constructor(options) {
1922
+ void options?.config;
1907
1923
  }
1908
- if (!opts.ignoreDuration) {
1909
- const ld = L.durationMs;
1910
- const rd = R.durationMs;
1911
- const th = opts.durationThresholdMs;
1912
- let differs = false;
1913
- if (ld === void 0 && rd === void 0) differs = false;
1914
- else if (ld === void 0 || rd === void 0) differs = true;
1915
- else differs = Math.abs(ld - rd) > th;
1916
- if (differs) {
1917
- out.push({
1918
- kind: "duration",
1919
- severity: "info",
1920
- message: "Step duration differs",
1921
- path: path8,
1922
- left: ld,
1923
- right: rd
1924
- });
1924
+ build(events) {
1925
+ const byRun = /* @__PURE__ */ new Map();
1926
+ for (const e of events) {
1927
+ if (!byRun.has(e.runId)) byRun.set(e.runId, []);
1928
+ byRun.get(e.runId).push(e);
1925
1929
  }
1926
- }
1927
- const lm = stableJson(L.metadata ?? {});
1928
- const rm = stableJson(R.metadata ?? {});
1929
- if (lm !== rm) {
1930
- out.push({
1931
- kind: "metadata",
1932
- severity: "info",
1933
- message: "Step metadata differs",
1934
- path: path8,
1935
- left: L.metadata,
1936
- right: R.metadata
1937
- });
1938
- }
1939
- const lo = stableJson(L.outputPreview ?? null);
1940
- const ro = stableJson(R.outputPreview ?? null);
1941
- if (lo !== ro) {
1942
- out.push({
1943
- kind: "output",
1944
- severity: "info",
1945
- message: "Output preview differs",
1946
- path: path8,
1947
- left: L.outputPreview,
1948
- right: R.outputPreview
1949
- });
1950
- }
1951
- }
1952
- function compareRecursive(L, R, segments, opts, out) {
1953
- compareLeafSteps(L, R, segments, opts, out);
1954
- const pairs = pairSteps(L.children, R.children);
1955
- let ci = 0;
1956
- for (const [lch, rch] of pairs) {
1957
- if (lch !== void 0 && rch !== void 0) {
1958
- compareRecursive(lch, rch, [...segments, pathSeg(lch, ci)], opts, out);
1959
- } else if (lch !== void 0) {
1960
- out.push({
1961
- kind: "step-removed",
1962
- severity: "warning",
1963
- message: `Step only in left run: ${lch.name}`,
1964
- path: buildPath([...segments, pathSeg(lch, ci)]),
1965
- left: lch.id,
1966
- right: void 0
1967
- });
1968
- } else if (rch !== void 0) {
1930
+ const out = [];
1931
+ for (const [runId, runEvents] of byRun.entries()) {
1932
+ const sorted = [...runEvents].sort((a, b) => a.timestamp - b.timestamp);
1933
+ const nodes = /* @__PURE__ */ new Map();
1934
+ for (const e of sorted) {
1935
+ nodes.set(e.eventId, { event: e, children: [], depth: 0 });
1936
+ }
1937
+ const roots = [];
1938
+ for (const node of nodes.values()) {
1939
+ const parentId = node.event.parentId;
1940
+ if (parentId && nodes.has(parentId)) {
1941
+ nodes.get(parentId).children.push(node);
1942
+ } else {
1943
+ roots.push(node);
1944
+ }
1945
+ }
1946
+ const assignDepth = (n, depth) => {
1947
+ n.depth = depth;
1948
+ for (const c of n.children) assignDepth(c, depth + 1);
1949
+ };
1950
+ for (const r of roots) assignDepth(r, 0);
1951
+ const confidenceBreakdown = {
1952
+ explicit: 0,
1953
+ correlated: 0,
1954
+ heuristic: 0,
1955
+ unknown: 0
1956
+ };
1957
+ const kinds = {};
1958
+ for (const e of sorted) {
1959
+ inc(confidenceBreakdown, e.confidence);
1960
+ kinds[e.kind] = (kinds[e.kind] ?? 0) + 1;
1961
+ }
1962
+ const startedAt = sorted.length > 0 ? sorted[0].timestamp : void 0;
1963
+ const endedAt = sorted.length > 0 ? sorted[sorted.length - 1].timestamp : void 0;
1964
+ const status = computeRunStatus(sorted);
1965
+ const durationMs = startedAt !== void 0 && endedAt !== void 0 && Number.isFinite(startedAt) && Number.isFinite(endedAt) && endedAt >= startedAt && status !== "running" ? endedAt - startedAt : void 0;
1966
+ const name = sorted.find((e) => e.kind === "RUN")?.name;
1969
1967
  out.push({
1970
- kind: "step-added",
1971
- severity: "warning",
1972
- message: `Step only in right run: ${rch.name}`,
1973
- path: buildPath([...segments, pathSeg(rch, ci)]),
1974
- left: void 0,
1975
- right: rch.id
1968
+ runId,
1969
+ name,
1970
+ status,
1971
+ startedAt,
1972
+ endedAt: status === "running" ? void 0 : endedAt,
1973
+ durationMs,
1974
+ children: roots,
1975
+ metadata: {
1976
+ totalEvents: sorted.length,
1977
+ confidenceBreakdown,
1978
+ kinds
1979
+ }
1976
1980
  });
1977
1981
  }
1978
- ci += 1;
1982
+ out.sort((a, b) => (b.startedAt ?? 0) - (a.startedAt ?? 0));
1983
+ return out;
1984
+ }
1985
+ };
1986
+
1987
+ // packages/core/src/persisted/to-inspect-event.ts
1988
+ function compactAttributes2(entries) {
1989
+ const out = {};
1990
+ for (const [key, value] of Object.entries(entries)) {
1991
+ if (value !== void 0) {
1992
+ out[key] = value;
1993
+ }
1979
1994
  }
1995
+ return Object.keys(out).length > 0 ? out : void 0;
1980
1996
  }
1981
- function mergeDiffDefaults(options) {
1997
+ function parseIsoToMs3(iso) {
1998
+ const parsed = Date.parse(iso);
1999
+ if (!Number.isFinite(parsed)) {
2000
+ return { ms: 0, invalidTimestamp: true };
2001
+ }
2002
+ return { ms: parsed, invalidTimestamp: false };
2003
+ }
2004
+ function mapPersistedSourceToInspect(event) {
2005
+ const attrs = event.attributes ?? {};
2006
+ const sourceName = event.source.name;
2007
+ if (sourceName === "pino") {
2008
+ return {
2009
+ type: "pino",
2010
+ file: typeof attrs.sourceFile === "string" ? attrs.sourceFile : void 0,
2011
+ line: typeof attrs.sourceLine === "number" ? attrs.sourceLine : void 0
2012
+ };
2013
+ }
2014
+ if (sourceName === "winston") {
2015
+ return {
2016
+ type: "winston",
2017
+ file: typeof attrs.sourceFile === "string" ? attrs.sourceFile : void 0,
2018
+ line: typeof attrs.sourceLine === "number" ? attrs.sourceLine : void 0
2019
+ };
2020
+ }
2021
+ const mapType = (t) => {
2022
+ switch (t) {
2023
+ case "manual":
2024
+ return "manual";
2025
+ case "json-log":
2026
+ return "json-log";
2027
+ case "log4js":
2028
+ return "log4js";
2029
+ case "adapter":
2030
+ case "ai-sdk":
2031
+ case "otel":
2032
+ return "adapter";
2033
+ default:
2034
+ return "json-log";
2035
+ }
2036
+ };
1982
2037
  return {
1983
- ignoreDuration: false,
1984
- durationThresholdMs: DEFAULT_THRESHOLD_MS,
1985
- focus: "all",
1986
- check: "all"
2038
+ type: mapType(event.source.type),
2039
+ file: typeof attrs.sourceFile === "string" ? attrs.sourceFile : void 0,
2040
+ line: typeof attrs.sourceLine === "number" ? attrs.sourceLine : void 0
1987
2041
  };
1988
2042
  }
1989
- function kindMatchesFilter(kind, merged) {
1990
- return true;
1991
- }
1992
- function diffRuns(left, right, options) {
1993
- const merged = mergeDiffDefaults();
1994
- const opts = {
1995
- ignoreDuration: merged.ignoreDuration,
1996
- durationThresholdMs: merged.durationThresholdMs
1997
- };
1998
- const raw = [];
1999
- if ((left.status ?? "") !== (right.status ?? "")) {
2000
- raw.push({
2001
- kind: "run-status",
2002
- severity: "warning",
2003
- message: "Run completion status differs",
2004
- left: left.status,
2005
- right: right.status
2006
- });
2043
+ function buildInspectAttributes(event) {
2044
+ const attrs = event.attributes !== void 0 ? { ...event.attributes } : {};
2045
+ if (event.inputSummary !== void 0) {
2046
+ attrs.inputSummary = event.inputSummary;
2007
2047
  }
2008
- {
2009
- const ld = left.durationMs;
2010
- const rd = right.durationMs;
2011
- const th = merged.durationThresholdMs;
2012
- let differs = false;
2013
- if (ld === void 0 && rd === void 0) differs = false;
2014
- else if (ld === void 0 || rd === void 0) differs = true;
2015
- else differs = Math.abs(ld - rd) > th;
2016
- if (differs) {
2017
- raw.push({
2018
- kind: "duration",
2019
- severity: "info",
2020
- message: "Run duration differs",
2021
- left: ld,
2022
- right: rd
2023
- });
2024
- }
2048
+ if (event.outputSummary !== void 0) {
2049
+ attrs.outputSummary = event.outputSummary;
2025
2050
  }
2026
- const pairs = pairSteps(left.steps, right.steps);
2027
- let idx = 0;
2028
- for (const [ls, rs] of pairs) {
2029
- if (ls !== void 0 && rs !== void 0) {
2030
- compareRecursive(ls, rs, [pathSeg(ls, idx)], opts, raw);
2031
- idx += 1;
2032
- } else if (ls !== void 0) {
2033
- raw.push({
2034
- kind: "step-removed",
2035
- severity: "warning",
2036
- message: `Step only in left run: ${ls.name}`,
2037
- path: buildPath([pathSeg(ls, idx)]),
2038
- left: ls.id,
2039
- right: void 0
2040
- });
2041
- idx += 1;
2042
- } else if (rs !== void 0) {
2043
- raw.push({
2044
- kind: "step-added",
2045
- severity: "warning",
2046
- message: `Step only in right run: ${rs.name}`,
2047
- path: buildPath([pathSeg(rs, idx)]),
2048
- left: void 0,
2049
- right: rs.id
2050
- });
2051
- idx += 1;
2051
+ if (event.error) {
2052
+ if (event.error.name !== void 0) {
2053
+ attrs.errorName = event.error.name;
2054
+ }
2055
+ attrs.errorMessage = event.error.message;
2056
+ if (event.error.code !== void 0) {
2057
+ attrs.errorCode = event.error.code;
2052
2058
  }
2053
2059
  }
2054
- const differences = raw.filter((d) => kindMatchesFilter(d.kind));
2055
- let errors = 0;
2056
- let warnings = 0;
2057
- let info = 0;
2058
- for (const d of differences) {
2059
- if (d.severity === "error") errors += 1;
2060
- else if (d.severity === "warning") warnings += 1;
2061
- else info += 1;
2060
+ if (event.tokenUsage) {
2061
+ attrs.tokens = { ...event.tokenUsage };
2062
2062
  }
2063
- const firstVisible = differences[0];
2064
- const firstDivergence = firstVisible !== void 0 ? {
2065
- kind: "first-divergence",
2066
- severity: firstVisible.severity,
2067
- message: `First divergence: ${firstVisible.message}`,
2068
- path: firstVisible.path,
2069
- left: firstVisible.left,
2070
- right: firstVisible.right
2071
- } : void 0;
2072
- const summary = {
2073
- leftRunId: left.runId,
2074
- rightRunId: right.runId,
2075
- totalDifferences: differences.length,
2076
- errors,
2077
- warnings,
2078
- info,
2079
- firstDivergence
2080
- };
2081
- return { summary, differences };
2082
- }
2083
-
2084
- // packages/core/src/exporters/markdown-exporter.ts
2085
- function renderTreeAscii(nodes, indent = "") {
2086
- const lines = [];
2087
- for (let i = 0; i < nodes.length; i++) {
2088
- const n = nodes[i];
2089
- const last = i === nodes.length - 1;
2090
- const branch = last ? "\u2514\u2500 " : "\u251C\u2500 ";
2091
- const ev = n.event;
2092
- const status = ev.status ?? "?";
2093
- const dur = ev.durationMs !== void 0 && Number.isFinite(ev.durationMs) ? `${ev.durationMs}ms` : "-";
2094
- lines.push(`${indent}${branch}${escapeMarkdown(ev.name)} [${ev.kind}] ${status} (${dur})`);
2095
- const nextIndent = indent + (last ? " " : "\u2502 ");
2096
- if (n.children.length > 0) {
2097
- const childStr = renderTreeAscii(n.children, nextIndent);
2098
- if (childStr.length > 0) lines.push(childStr);
2099
- }
2100
- }
2101
- return lines.join("\n");
2102
- }
2103
- function exportMarkdown(tree, options) {
2104
- const warnings = [];
2105
- const includeMetadata = options?.includeMetadata ?? true;
2106
- const includeAttributes = options?.includeAttributes ?? false;
2107
- const includeErrors = options?.includeErrors ?? true;
2108
- const maxLen = options?.maxAttributeLength ?? 500;
2109
- const titleName = tree.name ?? tree.runId;
2110
- const lines = [];
2111
- lines.push(`# AgentInspect Run: ${escapeMarkdown(titleName)}`);
2112
- lines.push("");
2113
- lines.push("Generated locally by AgentInspect. Review for sensitive data before sharing.");
2114
- lines.push("");
2115
- if (includeMetadata) {
2116
- lines.push("## Summary");
2117
- lines.push("");
2118
- lines.push(`- **runId**: ${escapeMarkdown(tree.runId)}`);
2119
- if (tree.name !== void 0) lines.push(`- **name**: ${escapeMarkdown(tree.name)}`);
2120
- lines.push(`- **status**: ${escapeMarkdown(String(tree.status ?? "unknown"))}`);
2121
- lines.push(
2122
- `- **durationMs**: ${tree.durationMs !== void 0 ? escapeMarkdown(String(tree.durationMs)) : "-"}`
2123
- );
2124
- lines.push(
2125
- `- **startedAt**: ${tree.startedAt !== void 0 ? escapeMarkdown(String(tree.startedAt)) : "-"}`
2126
- );
2127
- lines.push(
2128
- `- **endedAt**: ${tree.endedAt !== void 0 ? escapeMarkdown(String(tree.endedAt)) : "-"}`
2129
- );
2130
- lines.push(`- **totalEvents**: ${tree.metadata.totalEvents}`);
2131
- lines.push("");
2132
- lines.push("### Confidence breakdown");
2133
- lines.push("");
2134
- lines.push("| bucket | count |");
2135
- lines.push("| --- | --- |");
2136
- for (const k of Object.keys(tree.metadata.confidenceBreakdown).sort()) {
2137
- const key = k;
2138
- lines.push(
2139
- `| ${escapeMarkdown(key)} | ${tree.metadata.confidenceBreakdown[key]} |`
2140
- );
2141
- }
2142
- lines.push("");
2143
- lines.push("### Kind breakdown");
2144
- lines.push("");
2145
- lines.push("| kind | count |");
2146
- lines.push("| --- | --- |");
2147
- for (const k of Object.keys(tree.metadata.kinds).sort()) {
2148
- const key = k;
2149
- const c = tree.metadata.kinds[key];
2150
- if (c > 0) lines.push(`| ${escapeMarkdown(key)} | ${c} |`);
2151
- }
2152
- lines.push("");
2063
+ if (event.source.type === "ai-sdk" || event.source.type === "otel") {
2064
+ attrs.originalSourceType = event.source.type;
2153
2065
  }
2154
- lines.push("## Execution tree");
2155
- lines.push("");
2156
- lines.push("```text");
2157
- lines.push(
2158
- tree.children.length > 0 ? renderTreeAscii(tree.children) : "(no steps)"
2159
- );
2160
- lines.push("```");
2161
- lines.push("");
2162
- const flat = flattenTree(tree);
2163
- const errors = flat.filter((n) => n.event.status === "error");
2164
- if (includeErrors && errors.length > 0) {
2165
- lines.push("## Errors");
2166
- lines.push("");
2167
- for (const n of errors) {
2168
- const msg = n.event.attributes && typeof n.event.attributes.error === "object" ? safeString(
2169
- n.event.attributes.error.message,
2170
- maxLen
2171
- ) : "";
2172
- lines.push(
2173
- `- **${escapeMarkdown(n.event.name)}** (${escapeMarkdown(n.event.eventId)}): ${escapeMarkdown(msg || "error")}`
2174
- );
2175
- }
2176
- lines.push("");
2066
+ if (event.source.name !== void 0) {
2067
+ attrs.sourceName = event.source.name;
2177
2068
  }
2178
- if (includeAttributes) {
2179
- lines.push("## Attributes (bounded)");
2180
- lines.push("");
2181
- for (const n of flat) {
2182
- if (!n.event.attributes || Object.keys(n.event.attributes).length === 0) continue;
2183
- const compact = compactAttributes(n.event.attributes, {
2184
- maxLength: maxLen});
2185
- lines.push(`### ${escapeMarkdown(n.event.name)}`);
2186
- lines.push("");
2187
- lines.push("```json");
2188
- lines.push(stableJson(compact, true));
2189
- lines.push("```");
2190
- lines.push("");
2191
- }
2192
- warnings.push(
2193
- "Attributes may still contain sensitive data; review exports before sharing."
2194
- );
2069
+ if (event.source.version !== void 0) {
2070
+ attrs.sourceVersion = event.source.version;
2195
2071
  }
2196
- return {
2197
- format: "markdown",
2198
- content: lines.join("\n"),
2199
- contentType: "text/markdown",
2200
- fileExtension: ".md",
2201
- warnings
2202
- };
2203
- }
2204
-
2205
- // packages/core/src/persisted/token-usage.ts
2206
- function isRecord5(value) {
2207
- return typeof value === "object" && value !== null && !Array.isArray(value);
2208
- }
2209
- function nonNegativeFinite(value) {
2210
- return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
2072
+ return attrs;
2211
2073
  }
2212
- function normalizeTokenUsage(value) {
2213
- if (!isRecord5(value)) return void 0;
2214
- const input = nonNegativeFinite(value.input);
2215
- const output = nonNegativeFinite(value.output);
2216
- const suppliedTotal = nonNegativeFinite(value.total);
2217
- const cached = nonNegativeFinite(value.cached);
2218
- const derivedTotal = input !== void 0 && output !== void 0 && Number.isFinite(input + output) ? input + output : void 0;
2219
- const total = suppliedTotal ?? derivedTotal;
2220
- if (input === void 0 && output === void 0 && total === void 0 && cached === void 0) {
2221
- return void 0;
2074
+ function persistedInspectEventToInspectEvent(event) {
2075
+ if (!isPersistedInspectEvent(event)) {
2076
+ throw new Error("Invalid PersistedInspectEvent: failed isPersistedInspectEvent");
2222
2077
  }
2223
- return {
2224
- ...input !== void 0 ? { input } : {},
2225
- ...output !== void 0 ? { output } : {},
2226
- ...total !== void 0 ? { total } : {},
2227
- ...cached !== void 0 ? { cached } : {}
2228
- };
2229
- }
2230
-
2231
- // packages/core/src/persisted/from-trace-event.ts
2232
- function sanitizeIdPart(value) {
2233
- return value.replace(/[^a-zA-Z0-9_-]/g, "_");
2234
- }
2235
- function nodeIdForEvent(event) {
2236
- switch (event.event) {
2237
- case "run_started":
2238
- case "run_completed":
2239
- return event.runId;
2240
- case "step_started":
2241
- case "step_completed":
2242
- return event.stepId;
2243
- case "outcome_observed":
2244
- return event.outcomeId;
2245
- default:
2246
- return "unknown";
2078
+ const ts = parseIsoToMs3(event.timestamp);
2079
+ const attrs = buildInspectAttributes(event);
2080
+ if (ts.invalidTimestamp) {
2081
+ attrs.invalidTimestamp = true;
2247
2082
  }
2248
- }
2249
- function createPersistedEventId(event, eventIndex) {
2250
- const runId = sanitizeIdPart(event.runId);
2251
- const ev = sanitizeIdPart(event.event);
2252
- const node = sanitizeIdPart(nodeIdForEvent(event));
2253
- return `manual:${runId}:${ev}:${node}:${eventIndex}`;
2254
- }
2255
- function toIsoTimestamp(ms) {
2256
- if (typeof ms !== "number" || !Number.isFinite(ms)) {
2257
- return { iso: (/* @__PURE__ */ new Date(0)).toISOString(), invalidTimestamp: true };
2083
+ let status;
2084
+ if (event.status === "running" || event.status === "ok" || event.status === "error") {
2085
+ status = event.status;
2086
+ } else if (event.status === "unknown") {
2087
+ attrs.persistedStatus = "unknown";
2258
2088
  }
2259
- return { iso: new Date(ms).toISOString(), invalidTimestamp: false };
2260
- }
2261
- function buildSource(options) {
2262
- return {
2263
- type: "manual",
2264
- name: options?.sourceName ?? "trace-event",
2265
- version: options?.sourceVersion ?? "0.1"
2089
+ const out = {
2090
+ eventId: event.eventId,
2091
+ runId: event.runId,
2092
+ name: event.name,
2093
+ kind: event.kind,
2094
+ timestamp: ts.ms,
2095
+ confidence: event.confidence,
2096
+ source: mapPersistedSourceToInspect(event),
2097
+ attributes: compactAttributes2(attrs)
2266
2098
  };
2267
- }
2268
- function mapStepTypeToInspectKind(type) {
2269
- switch (type) {
2270
- case "run":
2271
- return "RUN";
2272
- case "llm":
2273
- return "LLM";
2274
- case "tool":
2275
- return "TOOL";
2276
- case "decision":
2277
- return "DECISION";
2278
- case "logic":
2279
- case "state":
2280
- case "custom":
2281
- return "LOGIC";
2282
- default:
2283
- return "LOGIC";
2099
+ if (event.parentId !== void 0) {
2100
+ out.parentId = event.parentId;
2284
2101
  }
2285
- }
2286
- function mapRunOrStepStatus(status) {
2287
- return status === "success" ? "ok" : "error";
2288
- }
2289
- function mapErrorInfo(error) {
2290
- if (!error?.message) {
2291
- return {};
2102
+ if (status !== void 0) {
2103
+ out.status = status;
2292
2104
  }
2293
- const out = {
2294
- persisted: {
2295
- message: error.message,
2296
- name: "Error"
2297
- }
2298
- };
2299
- if (typeof error.stack === "string" && error.stack.length > 0) {
2300
- out.errorStack = error.stack;
2105
+ if (event.durationMs !== void 0 && Number.isFinite(event.durationMs) && event.durationMs >= 0) {
2106
+ out.durationMs = event.durationMs;
2301
2107
  }
2302
2108
  return out;
2303
2109
  }
2304
- function mapTokenUsageFromMetadata(metadata) {
2305
- return normalizeTokenUsage(metadata?.tokens);
2306
- }
2307
- function compactAttributes2(entries) {
2308
- const out = {};
2309
- for (const [key, value] of Object.entries(entries)) {
2310
- if (value !== void 0) {
2311
- out[key] = value;
2110
+ function persistedInspectEventsToInspectEvents(events, options) {
2111
+ const skipInvalid = options?.skipInvalid === true;
2112
+ const out = [];
2113
+ for (const event of events) {
2114
+ if (!isPersistedInspectEvent(event)) {
2115
+ if (skipInvalid) {
2116
+ continue;
2117
+ }
2118
+ throw new Error("Invalid PersistedInspectEvent: failed isPersistedInspectEvent");
2312
2119
  }
2120
+ out.push(persistedInspectEventToInspectEvent(event));
2313
2121
  }
2314
- return Object.keys(out).length > 0 ? out : void 0;
2315
- }
2316
- function traceEventToPersistedInspectEvent(event, options) {
2317
- const eventIndex = options?.eventIndex ?? 0;
2318
- const eventId = createPersistedEventId(event, eventIndex);
2319
- const source = buildSource(options);
2320
- const tsMain = toIsoTimestamp(event.timestamp);
2321
- switch (event.event) {
2322
- case "run_started": {
2323
- const tsStart = toIsoTimestamp(event.startTime);
2324
- const correlation = extractCorrelationMetadata(event.metadata);
2325
- const attributes = compactAttributes2({
2326
- legacyEvent: "run_started",
2327
- metadata: event.metadata !== void 0 ? { ...event.metadata } : void 0,
2328
- correlationId: correlation?.correlationId,
2329
- requestId: correlation?.requestId,
2330
- decisionId: correlation?.decisionId,
2331
- groupId: correlation?.groupId,
2332
- invalidTimestamp: tsMain.invalidTimestamp || tsStart.invalidTimestamp ? true : void 0
2333
- });
2334
- return {
2335
- schemaVersion: "0.2",
2336
- eventId,
2337
- runId: event.runId,
2338
- kind: "RUN",
2339
- name: event.name,
2340
- status: "running",
2341
- timestamp: tsMain.iso,
2342
- startedAt: tsStart.iso,
2343
- confidence: "explicit",
2344
- source,
2345
- attributes
2346
- };
2347
- }
2348
- case "run_completed": {
2349
- const tsEnd = toIsoTimestamp(event.endTime);
2350
- const { persisted: error, errorStack } = mapErrorInfo(event.error);
2351
- const attributes = compactAttributes2({
2352
- legacyEvent: "run_completed",
2353
- errorStack,
2354
- invalidTimestamp: tsMain.invalidTimestamp || tsEnd.invalidTimestamp ? true : void 0
2355
- });
2356
- return {
2357
- schemaVersion: "0.2",
2358
- eventId,
2359
- runId: event.runId,
2360
- kind: "RUN",
2361
- name: "run",
2362
- status: mapRunOrStepStatus(event.status),
2363
- timestamp: tsMain.iso,
2364
- endedAt: tsEnd.iso,
2365
- durationMs: event.durationMs,
2366
- confidence: "explicit",
2367
- source,
2368
- attributes,
2369
- error
2370
- };
2371
- }
2372
- case "step_started": {
2373
- const tsStart = toIsoTimestamp(event.startTime);
2374
- const tokenUsage = mapTokenUsageFromMetadata(event.metadata);
2375
- const attributes = compactAttributes2({
2376
- legacyEvent: "step_started",
2377
- stepId: event.stepId,
2378
- stepType: event.type,
2379
- metadata: event.metadata !== void 0 ? { ...event.metadata } : void 0,
2380
- invalidTimestamp: tsMain.invalidTimestamp || tsStart.invalidTimestamp ? true : void 0
2381
- });
2382
- const out = {
2383
- schemaVersion: "0.2",
2384
- eventId,
2385
- runId: event.runId,
2386
- kind: mapStepTypeToInspectKind(event.type),
2387
- name: event.name,
2388
- status: "running",
2389
- timestamp: tsMain.iso,
2390
- startedAt: tsStart.iso,
2391
- confidence: "explicit",
2392
- source,
2393
- attributes
2394
- };
2395
- if (event.parentId !== void 0) {
2396
- out.parentId = event.parentId;
2397
- }
2398
- if (tokenUsage !== void 0) {
2399
- out.tokenUsage = tokenUsage;
2400
- }
2401
- return out;
2402
- }
2403
- case "step_completed": {
2404
- const tsEnd = toIsoTimestamp(event.endTime);
2405
- const { persisted: error, errorStack } = mapErrorInfo(event.error);
2406
- const attributes = compactAttributes2({
2407
- legacyEvent: "step_completed",
2408
- stepId: event.stepId,
2409
- errorStack,
2410
- invalidTimestamp: tsMain.invalidTimestamp || tsEnd.invalidTimestamp ? true : void 0
2411
- });
2412
- return {
2413
- schemaVersion: "0.2",
2414
- eventId,
2415
- runId: event.runId,
2416
- kind: "LOGIC",
2417
- name: event.stepId,
2418
- status: mapRunOrStepStatus(event.status),
2419
- timestamp: tsMain.iso,
2420
- endedAt: tsEnd.iso,
2421
- durationMs: event.durationMs,
2422
- confidence: "explicit",
2423
- source,
2424
- attributes,
2425
- error
2426
- };
2427
- }
2428
- case "outcome_observed": {
2429
- const tsObserved = toIsoTimestamp(event.observedAt);
2430
- const attributes = compactAttributes2({
2431
- legacyEvent: "outcome_observed",
2432
- outcomeId: event.outcomeId,
2433
- outcomeStatus: event.status,
2434
- expectation: event.expectation,
2435
- method: event.method,
2436
- actual: event.actual,
2437
- evidence: event.evidence,
2438
- observedAt: tsObserved.iso,
2439
- invalidTimestamp: tsMain.invalidTimestamp || tsObserved.invalidTimestamp ? true : void 0
2440
- });
2441
- const out = {
2442
- schemaVersion: "0.2",
2443
- eventId,
2444
- runId: event.runId,
2445
- kind: "OUTCOME",
2446
- name: event.name,
2447
- status: event.status === "failed" ? "error" : "ok",
2448
- timestamp: tsMain.iso,
2449
- confidence: "explicit",
2450
- source,
2451
- attributes
2452
- };
2453
- if (event.parentId !== void 0) {
2454
- out.parentId = event.parentId;
2455
- }
2456
- if (event.actual !== void 0) {
2457
- out.outputSummary = event.actual;
2458
- }
2459
- return out;
2460
- }
2461
- default: {
2462
- const _exhaustive = event;
2463
- throw new Error(`Unsupported trace event: ${_exhaustive.event}`);
2464
- }
2465
- }
2466
- }
2467
- function traceEventsToPersistedInspectEvents(events, options) {
2468
- return events.map(
2469
- (event, index) => traceEventToPersistedInspectEvent(event, { ...options, eventIndex: index })
2470
- );
2122
+ return out;
2471
2123
  }
2472
2124
 
2473
- // packages/core/src/persisted/to-inspect-event.ts
2474
- function compactAttributes3(entries) {
2475
- const out = {};
2476
- for (const [key, value] of Object.entries(entries)) {
2477
- if (value !== void 0) {
2478
- out[key] = value;
2479
- }
2480
- }
2481
- return Object.keys(out).length > 0 ? out : void 0;
2482
- }
2483
- function parseIsoToMs3(iso) {
2484
- const parsed = Date.parse(iso);
2485
- if (!Number.isFinite(parsed)) {
2486
- return { ms: 0, invalidTimestamp: true };
2487
- }
2488
- return { ms: parsed, invalidTimestamp: false };
2489
- }
2490
- function mapPersistedSourceToInspect(event) {
2491
- const attrs = event.attributes ?? {};
2492
- const sourceName = event.source.name;
2493
- if (sourceName === "pino") {
2494
- return {
2495
- type: "pino",
2496
- file: typeof attrs.sourceFile === "string" ? attrs.sourceFile : void 0,
2497
- line: typeof attrs.sourceLine === "number" ? attrs.sourceLine : void 0
2498
- };
2499
- }
2500
- if (sourceName === "winston") {
2501
- return {
2502
- type: "winston",
2503
- file: typeof attrs.sourceFile === "string" ? attrs.sourceFile : void 0,
2504
- line: typeof attrs.sourceLine === "number" ? attrs.sourceLine : void 0
2505
- };
2506
- }
2507
- const mapType = (t) => {
2508
- switch (t) {
2509
- case "manual":
2510
- return "manual";
2511
- case "json-log":
2512
- return "json-log";
2513
- case "log4js":
2514
- return "log4js";
2515
- case "adapter":
2516
- case "ai-sdk":
2517
- case "otel":
2518
- return "adapter";
2519
- default:
2520
- return "json-log";
2521
- }
2522
- };
2523
- return {
2524
- type: mapType(event.source.type),
2525
- file: typeof attrs.sourceFile === "string" ? attrs.sourceFile : void 0,
2526
- line: typeof attrs.sourceLine === "number" ? attrs.sourceLine : void 0
2527
- };
2528
- }
2529
- function buildInspectAttributes(event) {
2530
- const attrs = event.attributes !== void 0 ? { ...event.attributes } : {};
2531
- if (event.inputSummary !== void 0) {
2532
- attrs.inputSummary = event.inputSummary;
2533
- }
2534
- if (event.outputSummary !== void 0) {
2535
- attrs.outputSummary = event.outputSummary;
2536
- }
2537
- if (event.error) {
2538
- if (event.error.name !== void 0) {
2539
- attrs.errorName = event.error.name;
2540
- }
2541
- attrs.errorMessage = event.error.message;
2542
- if (event.error.code !== void 0) {
2543
- attrs.errorCode = event.error.code;
2544
- }
2545
- }
2546
- if (event.tokenUsage) {
2547
- attrs.tokens = { ...event.tokenUsage };
2548
- }
2549
- if (event.source.type === "ai-sdk" || event.source.type === "otel") {
2550
- attrs.originalSourceType = event.source.type;
2551
- }
2552
- if (event.source.name !== void 0) {
2553
- attrs.sourceName = event.source.name;
2554
- }
2555
- if (event.source.version !== void 0) {
2556
- attrs.sourceVersion = event.source.version;
2557
- }
2558
- return attrs;
2559
- }
2560
- function persistedInspectEventToInspectEvent(event) {
2561
- if (!isPersistedInspectEvent(event)) {
2562
- throw new Error("Invalid PersistedInspectEvent: failed isPersistedInspectEvent");
2563
- }
2564
- const ts = parseIsoToMs3(event.timestamp);
2565
- const attrs = buildInspectAttributes(event);
2566
- if (ts.invalidTimestamp) {
2567
- attrs.invalidTimestamp = true;
2568
- }
2569
- let status;
2570
- if (event.status === "running" || event.status === "ok" || event.status === "error") {
2571
- status = event.status;
2572
- } else if (event.status === "unknown") {
2573
- attrs.persistedStatus = "unknown";
2574
- }
2575
- const out = {
2576
- eventId: event.eventId,
2577
- runId: event.runId,
2578
- name: event.name,
2579
- kind: event.kind,
2580
- timestamp: ts.ms,
2581
- confidence: event.confidence,
2582
- source: mapPersistedSourceToInspect(event),
2583
- attributes: compactAttributes3(attrs)
2584
- };
2585
- if (event.parentId !== void 0) {
2586
- out.parentId = event.parentId;
2587
- }
2588
- if (status !== void 0) {
2589
- out.status = status;
2590
- }
2591
- if (event.durationMs !== void 0 && Number.isFinite(event.durationMs) && event.durationMs >= 0) {
2592
- out.durationMs = event.durationMs;
2593
- }
2594
- return out;
2595
- }
2596
- function persistedInspectEventsToInspectEvents(events, options) {
2597
- const skipInvalid = options?.skipInvalid === true;
2598
- const out = [];
2599
- for (const event of events) {
2600
- if (!isPersistedInspectEvent(event)) {
2601
- if (skipInvalid) {
2602
- continue;
2603
- }
2604
- throw new Error("Invalid PersistedInspectEvent: failed isPersistedInspectEvent");
2605
- }
2606
- out.push(persistedInspectEventToInspectEvent(event));
2607
- }
2608
- return out;
2609
- }
2610
-
2611
- // packages/core/src/logs/tree-builder.ts
2612
- function inc(map, key) {
2613
- map[key] = (map[key] ?? 0) + 1;
2614
- }
2615
- function computeRunStatus(events) {
2616
- let hasRunning = false;
2617
- for (const e of events) {
2618
- if (e.status === "error") return "error";
2619
- if (e.status === "running") hasRunning = true;
2620
- }
2621
- if (hasRunning) return "running";
2622
- return "ok";
2623
- }
2624
- var TreeBuilder = class {
2625
- constructor(options) {
2626
- void options?.config;
2627
- }
2628
- build(events) {
2629
- const byRun = /* @__PURE__ */ new Map();
2630
- for (const e of events) {
2631
- if (!byRun.has(e.runId)) byRun.set(e.runId, []);
2632
- byRun.get(e.runId).push(e);
2633
- }
2634
- const out = [];
2635
- for (const [runId, runEvents] of byRun.entries()) {
2636
- const sorted = [...runEvents].sort((a, b) => a.timestamp - b.timestamp);
2637
- const nodes = /* @__PURE__ */ new Map();
2638
- for (const e of sorted) {
2639
- nodes.set(e.eventId, { event: e, children: [], depth: 0 });
2640
- }
2641
- const roots = [];
2642
- for (const node of nodes.values()) {
2643
- const parentId = node.event.parentId;
2644
- if (parentId && nodes.has(parentId)) {
2645
- nodes.get(parentId).children.push(node);
2646
- } else {
2647
- roots.push(node);
2648
- }
2649
- }
2650
- const assignDepth = (n, depth) => {
2651
- n.depth = depth;
2652
- for (const c of n.children) assignDepth(c, depth + 1);
2653
- };
2654
- for (const r of roots) assignDepth(r, 0);
2655
- const confidenceBreakdown = {
2656
- explicit: 0,
2657
- correlated: 0,
2658
- heuristic: 0,
2659
- unknown: 0
2660
- };
2661
- const kinds = {};
2662
- for (const e of sorted) {
2663
- inc(confidenceBreakdown, e.confidence);
2664
- kinds[e.kind] = (kinds[e.kind] ?? 0) + 1;
2665
- }
2666
- const startedAt = sorted.length > 0 ? sorted[0].timestamp : void 0;
2667
- const endedAt = sorted.length > 0 ? sorted[sorted.length - 1].timestamp : void 0;
2668
- const status = computeRunStatus(sorted);
2669
- const durationMs = startedAt !== void 0 && endedAt !== void 0 && Number.isFinite(startedAt) && Number.isFinite(endedAt) && endedAt >= startedAt && status !== "running" ? endedAt - startedAt : void 0;
2670
- const name = sorted.find((e) => e.kind === "RUN")?.name;
2671
- out.push({
2672
- runId,
2673
- name,
2674
- status,
2675
- startedAt,
2676
- endedAt: status === "running" ? void 0 : endedAt,
2677
- durationMs,
2678
- children: roots,
2679
- metadata: {
2680
- totalEvents: sorted.length,
2681
- confidenceBreakdown,
2682
- kinds
2683
- }
2684
- });
2685
- }
2686
- out.sort((a, b) => (b.startedAt ?? 0) - (a.startedAt ?? 0));
2687
- return out;
2688
- }
2689
- };
2690
-
2691
2125
  // packages/core/src/persisted/tree-bridge.ts
2692
2126
  function persistedInspectEventsToRunTrees(events, options) {
2693
2127
  const inspectEvents = persistedInspectEventsToInspectEvents(events, {
@@ -2695,6 +2129,8 @@ function persistedInspectEventsToRunTrees(events, options) {
2695
2129
  });
2696
2130
  return new TreeBuilder().build(inspectEvents);
2697
2131
  }
2132
+
2133
+ // packages/core/src/readers/index.ts
2698
2134
  var DEFAULT_MAX_TRACE_INPUT_BYTES = 10 * 1024 * 1024;
2699
2135
  var MIN_DETECTION_CONFIDENCE = 0.5;
2700
2136
  var AMBIGUOUS_CONFIDENCE_DELTA = 0.05;
@@ -2767,591 +2203,1178 @@ var TraceReadError = class extends Error {
2767
2203
  this.code = code;
2768
2204
  this.warnings = warnings;
2769
2205
  }
2770
- };
2771
- function normalizeCandidate(reader, candidate) {
2772
- const confidence = Number.isFinite(candidate.confidence) ? Math.max(0, Math.min(1, candidate.confidence)) : 0;
2773
- return {
2774
- ...candidate,
2775
- format: candidate.format || reader.format,
2776
- confidence,
2777
- readerName: candidate.readerName ?? reader.name
2778
- };
2206
+ };
2207
+ function normalizeCandidate(reader, candidate) {
2208
+ const confidence = Number.isFinite(candidate.confidence) ? Math.max(0, Math.min(1, candidate.confidence)) : 0;
2209
+ return {
2210
+ ...candidate,
2211
+ format: candidate.format || reader.format,
2212
+ confidence,
2213
+ readerName: candidate.readerName ?? reader.name
2214
+ };
2215
+ }
2216
+ function sortCandidates(candidates) {
2217
+ return [...candidates].sort((a, b) => {
2218
+ if (b.confidence !== a.confidence) return b.confidence - a.confidence;
2219
+ return a.format.localeCompare(b.format);
2220
+ });
2221
+ }
2222
+ function collectWarnings(candidates) {
2223
+ return candidates.flatMap((candidate) => candidate.warnings ?? []);
2224
+ }
2225
+ function dedupeWarnings(warnings) {
2226
+ const seen = /* @__PURE__ */ new Set();
2227
+ const out = [];
2228
+ for (const warning of warnings) {
2229
+ const key = [
2230
+ warning.code,
2231
+ warning.message,
2232
+ warning.severity ?? "",
2233
+ warning.sourceFile ?? "",
2234
+ warning.line ?? "",
2235
+ warning.field ?? ""
2236
+ ].join("\0");
2237
+ if (seen.has(key)) continue;
2238
+ seen.add(key);
2239
+ out.push(warning);
2240
+ }
2241
+ return out;
2242
+ }
2243
+ function attachSingleSourceFile(warnings, resolved) {
2244
+ if (resolved.sourceFiles.length !== 1) return [...warnings];
2245
+ const [sourceFile] = resolved.sourceFiles;
2246
+ return warnings.map((warning) => ({
2247
+ ...warning,
2248
+ sourceFile: warning.sourceFile ?? sourceFile
2249
+ }));
2250
+ }
2251
+ function findReaderByFormat(format, readers) {
2252
+ return readers.find((reader) => reader.format === format);
2253
+ }
2254
+ async function jsonlFilesInDirectory(dirPath) {
2255
+ const entries = await readdir(dirPath, { withFileTypes: true });
2256
+ return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map((entry) => path.join(dirPath, entry.name)).sort((a, b) => a.localeCompare(b));
2257
+ }
2258
+ async function resolveInput(input) {
2259
+ const cached = resolvedInputCache.get(input);
2260
+ if (cached) return cached;
2261
+ const promise = resolveInputUncached(input);
2262
+ resolvedInputCache.set(input, promise);
2263
+ return promise;
2264
+ }
2265
+ function assertInputWithinBounds(content, sourceFile) {
2266
+ const bytes = Buffer.byteLength(content, "utf8");
2267
+ if (bytes <= DEFAULT_MAX_TRACE_INPUT_BYTES) return;
2268
+ throw new TraceReadError("unsupported_format", "Trace input exceeds the local reader size limit.", [
2269
+ {
2270
+ code: "input_too_large",
2271
+ message: `Trace input is ${bytes} bytes; max is ${DEFAULT_MAX_TRACE_INPUT_BYTES} bytes.`,
2272
+ severity: "error",
2273
+ ...sourceFile !== void 0 ? { sourceFile } : {}
2274
+ }
2275
+ ]);
2276
+ }
2277
+ async function resolveInputUncached(input) {
2278
+ if (input.type === "string") {
2279
+ assertInputWithinBounds(input.content);
2280
+ return { content: input.content, sourceFiles: [] };
2281
+ }
2282
+ if (input.type === "buffer") {
2283
+ const content = input.content.toString("utf-8");
2284
+ assertInputWithinBounds(content);
2285
+ return { content, sourceFiles: [] };
2286
+ }
2287
+ if (input.type === "file") {
2288
+ const content = await readFile(input.path, "utf-8");
2289
+ assertInputWithinBounds(content, input.path);
2290
+ return { content, sourceFiles: [input.path] };
2291
+ }
2292
+ if (input.type === "directory") {
2293
+ const files = await jsonlFilesInDirectory(input.path);
2294
+ const parts = await Promise.all(
2295
+ files.map(async (file) => (await readFile(file, "utf-8")).trimEnd())
2296
+ );
2297
+ const content = parts.filter((part) => part.trim() !== "").join("\n");
2298
+ assertInputWithinBounds(content, input.path);
2299
+ return {
2300
+ content,
2301
+ sourceFiles: files
2302
+ };
2303
+ }
2304
+ return void 0;
2305
+ }
2306
+ function detectJsonlFormat(content) {
2307
+ let saw01 = false;
2308
+ let saw02 = false;
2309
+ let saw10 = false;
2310
+ let validRows = 0;
2311
+ let invalidJsonRows = 0;
2312
+ let unknownSchemaRows = 0;
2313
+ let firstInvalidJsonLine;
2314
+ let firstUnknownSchemaLine;
2315
+ let lineNumber = 0;
2316
+ for (const line of content.split(/\r?\n/)) {
2317
+ lineNumber += 1;
2318
+ const trimmed = line.trim();
2319
+ if (trimmed === "") continue;
2320
+ let parsed;
2321
+ try {
2322
+ parsed = JSON.parse(trimmed);
2323
+ } catch {
2324
+ invalidJsonRows += 1;
2325
+ firstInvalidJsonLine ??= lineNumber;
2326
+ continue;
2327
+ }
2328
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) && "schemaVersion" in parsed) {
2329
+ const version = parsed.schemaVersion;
2330
+ if (version === "0.1") {
2331
+ saw01 = true;
2332
+ validRows += 1;
2333
+ continue;
2334
+ }
2335
+ if (version === "0.2") {
2336
+ saw02 = true;
2337
+ validRows += 1;
2338
+ continue;
2339
+ }
2340
+ if (version === "1.0") {
2341
+ saw10 = true;
2342
+ validRows += 1;
2343
+ continue;
2344
+ }
2345
+ }
2346
+ unknownSchemaRows += 1;
2347
+ firstUnknownSchemaLine ??= lineNumber;
2348
+ }
2349
+ const warnings = [];
2350
+ if (invalidJsonRows > 0) {
2351
+ warnings.push({
2352
+ code: "invalid_jsonl_rows",
2353
+ message: `Skipped ${invalidJsonRows} invalid JSONL row(s) during format detection.`,
2354
+ severity: "warning",
2355
+ ...firstInvalidJsonLine !== void 0 ? { line: firstInvalidJsonLine } : {}
2356
+ });
2357
+ }
2358
+ if (unknownSchemaRows > 0) {
2359
+ warnings.push({
2360
+ code: "unknown_schema_rows",
2361
+ message: `Skipped ${unknownSchemaRows} row(s) with unknown schemaVersion during format detection.`,
2362
+ severity: "warning",
2363
+ ...firstUnknownSchemaLine !== void 0 ? { line: firstUnknownSchemaLine } : {}
2364
+ });
2365
+ }
2366
+ let format = "empty";
2367
+ const seenFormats = [saw01, saw02, saw10].filter(Boolean).length;
2368
+ if (seenFormats > 1) format = "mixed";
2369
+ else if (saw01) format = "0.1";
2370
+ else if (saw02) format = "0.2";
2371
+ else if (saw10) format = "1.0";
2372
+ return { format, validRows, warnings };
2779
2373
  }
2780
- function sortCandidates(candidates) {
2781
- return [...candidates].sort((a, b) => {
2782
- if (b.confidence !== a.confidence) return b.confidence - a.confidence;
2783
- return a.format.localeCompare(b.format);
2374
+ function agentInspectFormatLabel(format) {
2375
+ switch (format) {
2376
+ case "0.1":
2377
+ return "agent-inspect-v0.1-jsonl";
2378
+ case "0.2":
2379
+ return "agent-inspect-v0.2-jsonl";
2380
+ case "1.0":
2381
+ return "agent-inspect-v1.0-jsonl";
2382
+ case "mixed":
2383
+ return "agent-inspect-mixed-jsonl";
2384
+ default:
2385
+ return "agent-inspect-jsonl";
2386
+ }
2387
+ }
2388
+ function persistedEventsForParsedTrace(parsed) {
2389
+ if ((parsed.format === "0.2" || parsed.format === "1.0") && parsed.persisted.length > 0) {
2390
+ return [...parsed.persisted];
2391
+ }
2392
+ if (parsed.format === "mixed" && parsed.rows.length > 0) {
2393
+ return parsed.rows.map((row, index) => {
2394
+ if (row.format === "0.2" || row.format === "1.0") return row.event;
2395
+ return traceEventToPersistedInspectEvent(row.event, {
2396
+ eventIndex: index,
2397
+ sourceName: "agent-inspect-jsonl-reader"
2398
+ });
2399
+ });
2400
+ }
2401
+ return traceEventsToPersistedInspectEvents(parsed.events, {
2402
+ sourceName: "agent-inspect-jsonl-reader"
2784
2403
  });
2785
2404
  }
2786
- function collectWarnings(candidates) {
2787
- return candidates.flatMap((candidate) => candidate.warnings ?? []);
2405
+ function isRecord6(value) {
2406
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2788
2407
  }
2789
- function dedupeWarnings(warnings) {
2790
- const seen = /* @__PURE__ */ new Set();
2791
- const out = [];
2792
- for (const warning of warnings) {
2793
- const key = [
2794
- warning.code,
2795
- warning.message,
2796
- warning.severity ?? "",
2797
- warning.sourceFile ?? "",
2798
- warning.line ?? "",
2799
- warning.field ?? ""
2800
- ].join("\0");
2801
- if (seen.has(key)) continue;
2802
- seen.add(key);
2803
- out.push(warning);
2408
+ function isNonEmptyString3(value) {
2409
+ return typeof value === "string" && value.trim() !== "";
2410
+ }
2411
+ function readStringField(record, keys) {
2412
+ for (const key of keys) {
2413
+ const value = record[key];
2414
+ if (isNonEmptyString3(value)) return value;
2804
2415
  }
2805
- return out;
2416
+ return void 0;
2806
2417
  }
2807
- function attachSingleSourceFile(warnings, resolved) {
2808
- if (resolved.sourceFiles.length !== 1) return [...warnings];
2809
- const [sourceFile] = resolved.sourceFiles;
2810
- return warnings.map((warning) => ({
2811
- ...warning,
2812
- sourceFile: warning.sourceFile ?? sourceFile
2813
- }));
2418
+ function readRecordField(record, key) {
2419
+ const value = record[key];
2420
+ return isRecord6(value) ? value : void 0;
2814
2421
  }
2815
- function findReaderByFormat(format, readers) {
2816
- return readers.find((reader) => reader.format === format);
2422
+ function parseJsonDocument(content) {
2423
+ return JSON.parse(content);
2817
2424
  }
2818
- async function jsonlFilesInDirectory(dirPath) {
2819
- const entries = await readdir(dirPath, { withFileTypes: true });
2820
- return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map((entry) => path.join(dirPath, entry.name)).sort((a, b) => a.localeCompare(b));
2425
+ function looksLikeOpenInferenceSpan(value) {
2426
+ if (!isRecord6(value)) return false;
2427
+ const attributes = readRecordField(value, "attributes");
2428
+ 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);
2821
2429
  }
2822
- async function resolveInput(input) {
2823
- const cached = resolvedInputCache.get(input);
2824
- if (cached) return cached;
2825
- const promise = resolveInputUncached(input);
2826
- resolvedInputCache.set(input, promise);
2827
- return promise;
2430
+ function extractOpenInferenceDocument(root) {
2431
+ const warnings = [];
2432
+ const unsupportedFields = [];
2433
+ if (Array.isArray(root)) {
2434
+ const spans = root.filter(looksLikeOpenInferenceSpan);
2435
+ if (spans.length === 0) return void 0;
2436
+ if (spans.length !== root.length) {
2437
+ warnings.push({
2438
+ code: "openinference_skipped_items",
2439
+ message: "Skipped non-span item(s) in OpenInference span array.",
2440
+ severity: "warning"
2441
+ });
2442
+ }
2443
+ return {
2444
+ spans,
2445
+ confidence: 0.82,
2446
+ description: "OpenInference span array",
2447
+ warnings,
2448
+ unsupportedFields
2449
+ };
2450
+ }
2451
+ if (!isRecord6(root)) return void 0;
2452
+ const rootFormat = root.format;
2453
+ const rootCompatibility = root.compatibility;
2454
+ const version = typeof root.version === "string" && root.version.trim() !== "" ? root.version : void 0;
2455
+ if (Array.isArray(root.spans)) {
2456
+ const spans = root.spans.filter(looksLikeOpenInferenceSpan);
2457
+ if (spans.length === 0 && (rootFormat === "openinference" || rootCompatibility === "openinference-compatible")) {
2458
+ warnings.push({
2459
+ code: "openinference_no_valid_spans",
2460
+ message: "OpenInference document did not contain any valid spans.",
2461
+ severity: "error"
2462
+ });
2463
+ return {
2464
+ spans,
2465
+ confidence: 0.7,
2466
+ description: "Malformed OpenInference document",
2467
+ version,
2468
+ warnings,
2469
+ unsupportedFields
2470
+ };
2471
+ }
2472
+ if (spans.length === 0) return void 0;
2473
+ if (spans.length !== root.spans.length) {
2474
+ warnings.push({
2475
+ code: "openinference_skipped_spans",
2476
+ message: "Skipped invalid OpenInference span item(s).",
2477
+ severity: "warning"
2478
+ });
2479
+ }
2480
+ return {
2481
+ spans,
2482
+ confidence: rootFormat === "openinference" || rootCompatibility === "openinference-compatible" ? 0.9 : 0.84,
2483
+ description: rootFormat === "openinference" || rootCompatibility === "openinference-compatible" ? "OpenInference document" : "OpenInference spans document",
2484
+ version,
2485
+ warnings,
2486
+ unsupportedFields
2487
+ };
2488
+ }
2489
+ if (Array.isArray(root.data)) {
2490
+ const spans = root.data.filter(looksLikeOpenInferenceSpan);
2491
+ if (spans.length === 0) return void 0;
2492
+ if (spans.length !== root.data.length) {
2493
+ warnings.push({
2494
+ code: "openinference_skipped_data_items",
2495
+ message: "Skipped non-span item(s) in OpenInference data array.",
2496
+ severity: "warning"
2497
+ });
2498
+ }
2499
+ return {
2500
+ spans,
2501
+ confidence: 0.8,
2502
+ description: "OpenInference data document",
2503
+ version,
2504
+ warnings,
2505
+ unsupportedFields
2506
+ };
2507
+ }
2508
+ if (looksLikeOpenInferenceSpan(root)) {
2509
+ return {
2510
+ spans: [root],
2511
+ confidence: 0.76,
2512
+ description: "OpenInference single span",
2513
+ version,
2514
+ warnings,
2515
+ unsupportedFields
2516
+ };
2517
+ }
2518
+ if (rootFormat === "openinference" || rootCompatibility === "openinference-compatible") {
2519
+ warnings.push({
2520
+ code: "openinference_missing_spans",
2521
+ message: "OpenInference document is missing a spans array.",
2522
+ severity: "error"
2523
+ });
2524
+ return {
2525
+ spans: [],
2526
+ confidence: 0.7,
2527
+ description: "Malformed OpenInference document",
2528
+ version,
2529
+ warnings,
2530
+ unsupportedFields
2531
+ };
2532
+ }
2533
+ return void 0;
2534
+ }
2535
+ function parseUnixNanoToIso(value) {
2536
+ if (typeof value === "bigint" && value >= 0n) {
2537
+ return new Date(Number(value / 1000000n)).toISOString();
2538
+ }
2539
+ if (typeof value === "number" && Number.isFinite(value) && value >= 0) {
2540
+ return new Date(Math.floor(value / 1e6)).toISOString();
2541
+ }
2542
+ if (typeof value === "string" && /^\d+$/.test(value)) {
2543
+ return new Date(Number(BigInt(value) / 1000000n)).toISOString();
2544
+ }
2545
+ return void 0;
2546
+ }
2547
+ function parseIsoTime(value) {
2548
+ if (!isNonEmptyString3(value)) return void 0;
2549
+ const ms = Date.parse(value);
2550
+ if (!Number.isFinite(ms)) return void 0;
2551
+ return new Date(ms).toISOString();
2552
+ }
2553
+ function readOpenInferenceTimestamp(span, nanoKeys, isoKeys) {
2554
+ for (const key of nanoKeys) {
2555
+ const iso = parseUnixNanoToIso(span[key]);
2556
+ if (iso !== void 0) return iso;
2557
+ }
2558
+ for (const key of isoKeys) {
2559
+ const iso = parseIsoTime(span[key]);
2560
+ if (iso !== void 0) return iso;
2561
+ }
2562
+ return void 0;
2828
2563
  }
2829
- function assertInputWithinBounds(content, sourceFile) {
2830
- const bytes = Buffer.byteLength(content, "utf8");
2831
- if (bytes <= DEFAULT_MAX_TRACE_INPUT_BYTES) return;
2832
- throw new TraceReadError("unsupported_format", "Trace input exceeds the local reader size limit.", [
2833
- {
2834
- code: "input_too_large",
2835
- message: `Trace input is ${bytes} bytes; max is ${DEFAULT_MAX_TRACE_INPUT_BYTES} bytes.`,
2836
- severity: "error",
2837
- ...sourceFile !== void 0 ? { sourceFile } : {}
2838
- }
2839
- ]);
2564
+ function durationBetweenIso(startedAt, endedAt) {
2565
+ if (startedAt === void 0 || endedAt === void 0) return void 0;
2566
+ const startMs = Date.parse(startedAt);
2567
+ const endMs = Date.parse(endedAt);
2568
+ if (!Number.isFinite(startMs) || !Number.isFinite(endMs) || endMs < startMs) {
2569
+ return void 0;
2570
+ }
2571
+ return endMs - startMs;
2840
2572
  }
2841
- async function resolveInputUncached(input) {
2842
- if (input.type === "string") {
2843
- assertInputWithinBounds(input.content);
2844
- return { content: input.content, sourceFiles: [] };
2573
+ function isSensitiveOpenInferenceAttribute(key) {
2574
+ return OPENINFERENCE_SENSITIVE_ATTRIBUTE_KEYS.some(
2575
+ (sensitiveKey) => key === sensitiveKey || key.startsWith(`${sensitiveKey}.`) || key.endsWith(".message.content") || key.endsWith(".document.content")
2576
+ );
2577
+ }
2578
+ function summarizeAttributeValue(value) {
2579
+ if (typeof value === "string") {
2580
+ return { type: "string", length: value.length };
2845
2581
  }
2846
- if (input.type === "buffer") {
2847
- const content = input.content.toString("utf-8");
2848
- assertInputWithinBounds(content);
2849
- return { content, sourceFiles: [] };
2582
+ if (typeof value === "number") {
2583
+ return { type: "number", finite: Number.isFinite(value) };
2850
2584
  }
2851
- if (input.type === "file") {
2852
- const content = await readFile(input.path, "utf-8");
2853
- assertInputWithinBounds(content, input.path);
2854
- return { content, sourceFiles: [input.path] };
2585
+ if (typeof value === "boolean") {
2586
+ return { type: "boolean" };
2855
2587
  }
2856
- if (input.type === "directory") {
2857
- const files = await jsonlFilesInDirectory(input.path);
2858
- const parts = await Promise.all(
2859
- files.map(async (file) => (await readFile(file, "utf-8")).trimEnd())
2860
- );
2861
- const content = parts.filter((part) => part.trim() !== "").join("\n");
2862
- assertInputWithinBounds(content, input.path);
2863
- return {
2864
- content,
2865
- sourceFiles: files
2866
- };
2588
+ if (Array.isArray(value)) {
2589
+ return { type: "array", length: value.length };
2867
2590
  }
2868
- return void 0;
2591
+ if (isRecord6(value)) {
2592
+ return { type: "object", keyCount: Object.keys(value).length };
2593
+ }
2594
+ if (value === null) {
2595
+ return { type: "null" };
2596
+ }
2597
+ return { type: typeof value };
2869
2598
  }
2870
- function detectJsonlFormat(content) {
2871
- let saw01 = false;
2872
- let saw02 = false;
2873
- let saw10 = false;
2874
- let validRows = 0;
2875
- let invalidJsonRows = 0;
2876
- let unknownSchemaRows = 0;
2877
- let firstInvalidJsonLine;
2878
- let firstUnknownSchemaLine;
2879
- let lineNumber = 0;
2880
- for (const line of content.split(/\r?\n/)) {
2881
- lineNumber += 1;
2882
- const trimmed = line.trim();
2883
- if (trimmed === "") continue;
2884
- let parsed;
2885
- try {
2886
- parsed = JSON.parse(trimmed);
2887
- } catch {
2888
- invalidJsonRows += 1;
2889
- firstInvalidJsonLine ??= lineNumber;
2599
+ function sanitizeOpenInferenceAttributes(attributes, pathPrefix) {
2600
+ const out = {};
2601
+ const warnings = [];
2602
+ const unsupportedFields = [];
2603
+ const summarizedKeys = [];
2604
+ for (const [key, value] of Object.entries(attributes)) {
2605
+ if (isSensitiveOpenInferenceAttribute(key)) {
2606
+ summarizedKeys.push(key);
2607
+ out[`${key}.summary`] = summarizeAttributeValue(value);
2608
+ unsupportedFields.push(`${pathPrefix}.attributes.${key}`);
2890
2609
  continue;
2891
2610
  }
2892
- if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) && "schemaVersion" in parsed) {
2893
- const version = parsed.schemaVersion;
2894
- if (version === "0.1") {
2895
- saw01 = true;
2896
- validRows += 1;
2897
- continue;
2898
- }
2899
- if (version === "0.2") {
2900
- saw02 = true;
2901
- validRows += 1;
2902
- continue;
2903
- }
2904
- if (version === "1.0") {
2905
- saw10 = true;
2906
- validRows += 1;
2907
- continue;
2908
- }
2909
- }
2910
- unknownSchemaRows += 1;
2911
- firstUnknownSchemaLine ??= lineNumber;
2611
+ out[key] = value;
2912
2612
  }
2913
- const warnings = [];
2914
- if (invalidJsonRows > 0) {
2613
+ if (summarizedKeys.length > 0) {
2614
+ out["openinference.summarized_attributes"] = summarizedKeys;
2915
2615
  warnings.push({
2916
- code: "invalid_jsonl_rows",
2917
- message: `Skipped ${invalidJsonRows} invalid JSONL row(s) during format detection.`,
2918
- severity: "warning",
2919
- ...firstInvalidJsonLine !== void 0 ? { line: firstInvalidJsonLine } : {}
2616
+ code: "openinference_sensitive_attribute_summarized",
2617
+ message: "OpenInference prompt/output/document attribute(s) were summarized instead of copied verbatim.",
2618
+ severity: "warning"
2920
2619
  });
2921
2620
  }
2922
- if (unknownSchemaRows > 0) {
2923
- warnings.push({
2924
- code: "unknown_schema_rows",
2925
- message: `Skipped ${unknownSchemaRows} row(s) with unknown schemaVersion during format detection.`,
2926
- severity: "warning",
2927
- ...firstUnknownSchemaLine !== void 0 ? { line: firstUnknownSchemaLine } : {}
2928
- });
2621
+ return { attributes: out, warnings, unsupportedFields };
2622
+ }
2623
+ function mapOpenInferenceKind(span, attributes, pathPrefix) {
2624
+ const warnings = [];
2625
+ const agentInspectKind = attributes["agent_inspect.kind"];
2626
+ 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") {
2627
+ return { kind: agentInspectKind, warnings };
2628
+ }
2629
+ const rawKind = readStringField(span, ["kind", "span_kind", "spanKind"]) ?? (typeof attributes["openinference.span.kind"] === "string" ? attributes["openinference.span.kind"] : void 0);
2630
+ const normalized = rawKind?.toUpperCase();
2631
+ switch (normalized) {
2632
+ case "LLM":
2633
+ return { kind: "LLM", warnings };
2634
+ case "TOOL":
2635
+ return { kind: "TOOL", warnings };
2636
+ case "CHAIN":
2637
+ return { kind: "CHAIN", warnings };
2638
+ case "RETRIEVER":
2639
+ return { kind: "RETRIEVER", warnings };
2640
+ case "AGENT":
2641
+ return { kind: "AGENT", warnings };
2642
+ case "EMBEDDING":
2643
+ warnings.push({
2644
+ code: "openinference_kind_semantic_loss",
2645
+ message: "OpenInference EMBEDDING span kind mapped to AgentInspect LLM.",
2646
+ severity: "warning",
2647
+ field: `${pathPrefix}.attributes.openinference.span.kind`
2648
+ });
2649
+ return { kind: "LLM", warnings };
2650
+ case "RERANKER":
2651
+ warnings.push({
2652
+ code: "openinference_kind_semantic_loss",
2653
+ message: "OpenInference RERANKER span kind mapped to AgentInspect RETRIEVER.",
2654
+ severity: "warning",
2655
+ field: `${pathPrefix}.attributes.openinference.span.kind`
2656
+ });
2657
+ return { kind: "RETRIEVER", warnings };
2658
+ case "UNKNOWN":
2659
+ case void 0:
2660
+ warnings.push({
2661
+ code: "openinference_kind_unknown",
2662
+ message: "OpenInference span kind was missing or unknown; mapped to AgentInspect LOGIC.",
2663
+ severity: "warning",
2664
+ field: `${pathPrefix}.attributes.openinference.span.kind`
2665
+ });
2666
+ return { kind: "LOGIC", warnings };
2667
+ default:
2668
+ warnings.push({
2669
+ code: "openinference_kind_unsupported",
2670
+ message: `Unsupported OpenInference span kind "${rawKind}" mapped to AgentInspect LOGIC.`,
2671
+ severity: "warning",
2672
+ field: `${pathPrefix}.attributes.openinference.span.kind`
2673
+ });
2674
+ return { kind: "LOGIC", warnings };
2929
2675
  }
2930
- let format = "empty";
2931
- const seenFormats = [saw01, saw02, saw10].filter(Boolean).length;
2932
- if (seenFormats > 1) format = "mixed";
2933
- else if (saw01) format = "0.1";
2934
- else if (saw02) format = "0.2";
2935
- else if (saw10) format = "1.0";
2936
- return { format, validRows, warnings };
2937
2676
  }
2938
- function agentInspectFormatLabel(format) {
2939
- switch (format) {
2940
- case "0.1":
2941
- return "agent-inspect-v0.1-jsonl";
2942
- case "0.2":
2943
- return "agent-inspect-v0.2-jsonl";
2944
- case "1.0":
2945
- return "agent-inspect-v1.0-jsonl";
2946
- case "mixed":
2947
- return "agent-inspect-mixed-jsonl";
2677
+ function mapOpenInferenceStatus(status) {
2678
+ if (!isRecord6(status)) return void 0;
2679
+ const rawCode = status.code;
2680
+ if (typeof rawCode !== "string") return void 0;
2681
+ switch (rawCode.toUpperCase()) {
2682
+ case "OK":
2683
+ return "ok";
2684
+ case "ERROR":
2685
+ return "error";
2686
+ case "UNSET":
2687
+ return "unknown";
2948
2688
  default:
2949
- return "agent-inspect-jsonl";
2689
+ return "unknown";
2950
2690
  }
2951
2691
  }
2952
- function persistedEventsForParsedTrace(parsed) {
2953
- if ((parsed.format === "0.2" || parsed.format === "1.0") && parsed.persisted.length > 0) {
2954
- return [...parsed.persisted];
2692
+ function readOpenInferenceTokenUsage(attributes) {
2693
+ const prompt = attributes["llm.token_count.prompt"];
2694
+ const completion = attributes["llm.token_count.completion"];
2695
+ const total = attributes["llm.token_count.total"];
2696
+ const cached = attributes["llm.token_count.prompt_details.cache_read"];
2697
+ const usage = {};
2698
+ if (typeof prompt === "number" && Number.isFinite(prompt) && prompt >= 0) {
2699
+ usage.input = prompt;
2955
2700
  }
2956
- if (parsed.format === "mixed" && parsed.rows.length > 0) {
2957
- return parsed.rows.map((row, index) => {
2958
- if (row.format === "0.2" || row.format === "1.0") return row.event;
2959
- return traceEventToPersistedInspectEvent(row.event, {
2960
- eventIndex: index,
2961
- sourceName: "agent-inspect-jsonl-reader"
2962
- });
2963
- });
2701
+ if (typeof completion === "number" && Number.isFinite(completion) && completion >= 0) {
2702
+ usage.output = completion;
2964
2703
  }
2965
- return traceEventsToPersistedInspectEvents(parsed.events, {
2966
- sourceName: "agent-inspect-jsonl-reader"
2967
- });
2968
- }
2969
- function isRecord6(value) {
2970
- return typeof value === "object" && value !== null && !Array.isArray(value);
2971
- }
2972
- function isNonEmptyString3(value) {
2973
- return typeof value === "string" && value.trim() !== "";
2974
- }
2975
- function readStringField(record, keys) {
2976
- for (const key of keys) {
2977
- const value = record[key];
2978
- if (isNonEmptyString3(value)) return value;
2704
+ if (typeof total === "number" && Number.isFinite(total) && total >= 0) {
2705
+ usage.total = total;
2979
2706
  }
2980
- return void 0;
2981
- }
2982
- function readRecordField(record, key) {
2983
- const value = record[key];
2984
- return isRecord6(value) ? value : void 0;
2985
- }
2986
- function parseJsonDocument(content) {
2987
- return JSON.parse(content);
2707
+ if (typeof cached === "number" && Number.isFinite(cached) && cached >= 0) {
2708
+ usage.cached = cached;
2709
+ }
2710
+ if (usage.total === void 0 && usage.input !== void 0 && usage.output !== void 0) {
2711
+ usage.total = usage.input + usage.output;
2712
+ }
2713
+ return Object.keys(usage).length > 0 ? usage : void 0;
2988
2714
  }
2989
- function looksLikeOpenInferenceSpan(value) {
2990
- if (!isRecord6(value)) return false;
2991
- const attributes = readRecordField(value, "attributes");
2992
- 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);
2715
+ function readOpenInferenceConfidence(attributes) {
2716
+ const confidence = attributes["agent_inspect.confidence"];
2717
+ if (confidence === "explicit" || confidence === "correlated" || confidence === "heuristic" || confidence === "unknown") {
2718
+ return confidence;
2719
+ }
2720
+ return "correlated";
2993
2721
  }
2994
- function extractOpenInferenceDocument(root) {
2722
+ function mapOpenInferenceSpan(span, index, version) {
2723
+ const pathPrefix = `spans[${index}]`;
2995
2724
  const warnings = [];
2996
2725
  const unsupportedFields = [];
2997
- if (Array.isArray(root)) {
2998
- const spans = root.filter(looksLikeOpenInferenceSpan);
2999
- if (spans.length === 0) return void 0;
3000
- if (spans.length !== root.length) {
2726
+ const rawAttributes = readRecordField(span, "attributes") ?? {};
2727
+ const sanitized = sanitizeOpenInferenceAttributes(rawAttributes, pathPrefix);
2728
+ warnings.push(...sanitized.warnings);
2729
+ unsupportedFields.push(...sanitized.unsupportedFields);
2730
+ const attributes = { ...sanitized.attributes };
2731
+ for (const [key, value] of Object.entries(span)) {
2732
+ if (OPENINFERENCE_SPAN_KEYS.has(key)) continue;
2733
+ unsupportedFields.push(`${pathPrefix}.${key}`);
2734
+ if (value === null || typeof value !== "object") {
2735
+ attributes[`openinference.${key}`] = value;
2736
+ } else {
2737
+ attributes[`openinference.${key}.summary`] = summarizeAttributeValue(value);
3001
2738
  warnings.push({
3002
- code: "openinference_skipped_items",
3003
- message: "Skipped non-span item(s) in OpenInference span array.",
3004
- severity: "warning"
2739
+ code: "openinference_unsupported_field_summarized",
2740
+ message: `Unsupported OpenInference span field "${key}" was summarized.`,
2741
+ severity: "warning",
2742
+ field: `${pathPrefix}.${key}`
3005
2743
  });
3006
2744
  }
3007
- return {
3008
- spans,
3009
- confidence: 0.82,
3010
- description: "OpenInference span array",
3011
- warnings,
3012
- unsupportedFields
3013
- };
3014
2745
  }
3015
- if (!isRecord6(root)) return void 0;
3016
- const rootFormat = root.format;
3017
- const rootCompatibility = root.compatibility;
3018
- const version = typeof root.version === "string" && root.version.trim() !== "" ? root.version : void 0;
3019
- if (Array.isArray(root.spans)) {
3020
- const spans = root.spans.filter(looksLikeOpenInferenceSpan);
3021
- if (spans.length === 0 && (rootFormat === "openinference" || rootCompatibility === "openinference-compatible")) {
3022
- warnings.push({
3023
- code: "openinference_no_valid_spans",
3024
- message: "OpenInference document did not contain any valid spans.",
3025
- severity: "error"
3026
- });
3027
- return {
3028
- spans,
3029
- confidence: 0.7,
3030
- description: "Malformed OpenInference document",
3031
- version,
3032
- warnings,
3033
- unsupportedFields
3034
- };
3035
- }
3036
- if (spans.length === 0) return void 0;
3037
- if (spans.length !== root.spans.length) {
3038
- warnings.push({
3039
- code: "openinference_skipped_spans",
3040
- message: "Skipped invalid OpenInference span item(s).",
3041
- severity: "warning"
3042
- });
2746
+ const traceId = readStringField(span, ["trace_id", "traceId"]) ?? `trace-${index}`;
2747
+ const spanId = readStringField(span, ["span_id", "spanId"]) ?? `span-${index}`;
2748
+ const parentSpanId = readStringField(span, ["parent_span_id", "parentSpanId"]);
2749
+ const name = readStringField(span, ["name"]) ?? spanId;
2750
+ const startedAt = readOpenInferenceTimestamp(
2751
+ span,
2752
+ ["start_time_unix_nano", "startTimeUnixNano"],
2753
+ ["start_time", "startTime"]
2754
+ );
2755
+ const endedAt = readOpenInferenceTimestamp(
2756
+ span,
2757
+ ["end_time_unix_nano", "endTimeUnixNano"],
2758
+ ["end_time", "endTime"]
2759
+ );
2760
+ const timestamp = startedAt ?? "1970-01-01T00:00:00.000Z";
2761
+ if (startedAt === void 0) {
2762
+ warnings.push({
2763
+ code: "openinference_missing_start_time",
2764
+ message: "OpenInference span is missing a valid start time; using Unix epoch.",
2765
+ severity: "warning",
2766
+ field: `${pathPrefix}.start_time_unix_nano`
2767
+ });
2768
+ unsupportedFields.push(`${pathPrefix}.start_time_unix_nano`);
2769
+ }
2770
+ const { kind, warnings: kindWarnings } = mapOpenInferenceKind(
2771
+ span,
2772
+ rawAttributes,
2773
+ pathPrefix
2774
+ );
2775
+ warnings.push(...kindWarnings);
2776
+ const status = mapOpenInferenceStatus(span.status);
2777
+ const tokenUsage = readOpenInferenceTokenUsage(rawAttributes);
2778
+ const errorMessage = isRecord6(span.status) && typeof span.status.message === "string" ? span.status.message : void 0;
2779
+ const event = {
2780
+ schemaVersion: "0.2",
2781
+ eventId: typeof rawAttributes["agent_inspect.event_id"] === "string" ? rawAttributes["agent_inspect.event_id"] : spanId,
2782
+ runId: typeof rawAttributes["agent_inspect.run_id"] === "string" ? rawAttributes["agent_inspect.run_id"] : traceId,
2783
+ kind,
2784
+ name,
2785
+ timestamp,
2786
+ confidence: readOpenInferenceConfidence(rawAttributes),
2787
+ source: {
2788
+ type: "otel",
2789
+ name: "openinference",
2790
+ ...version !== void 0 ? { version } : {}
2791
+ },
2792
+ attributes,
2793
+ trace: {
2794
+ traceId,
2795
+ spanId,
2796
+ ...parentSpanId !== void 0 ? { parentSpanId } : {}
3043
2797
  }
3044
- return {
3045
- spans,
3046
- confidence: rootFormat === "openinference" || rootCompatibility === "openinference-compatible" ? 0.9 : 0.84,
3047
- description: rootFormat === "openinference" || rootCompatibility === "openinference-compatible" ? "OpenInference document" : "OpenInference spans document",
3048
- version,
3049
- warnings,
3050
- unsupportedFields
2798
+ };
2799
+ if (status !== void 0) {
2800
+ event.status = status;
2801
+ }
2802
+ if (startedAt !== void 0) {
2803
+ event.startedAt = startedAt;
2804
+ }
2805
+ if (endedAt !== void 0) {
2806
+ event.endedAt = endedAt;
2807
+ }
2808
+ const durationMs = durationBetweenIso(startedAt, endedAt);
2809
+ if (durationMs !== void 0) {
2810
+ event.durationMs = durationMs;
2811
+ }
2812
+ if (tokenUsage !== void 0) {
2813
+ event.tokenUsage = tokenUsage;
2814
+ }
2815
+ if (status === "error") {
2816
+ event.error = {
2817
+ message: errorMessage !== void 0 && errorMessage.trim() !== "" ? errorMessage : "OpenInference span error"
3051
2818
  };
3052
2819
  }
3053
- if (Array.isArray(root.data)) {
3054
- const spans = root.data.filter(looksLikeOpenInferenceSpan);
3055
- if (spans.length === 0) return void 0;
3056
- if (spans.length !== root.data.length) {
3057
- warnings.push({
3058
- code: "openinference_skipped_data_items",
3059
- message: "Skipped non-span item(s) in OpenInference data array.",
3060
- severity: "warning"
3061
- });
2820
+ return {
2821
+ event,
2822
+ warnings,
2823
+ unsupportedFields,
2824
+ spanId,
2825
+ ...parentSpanId !== void 0 ? { parentSpanId } : {}
2826
+ };
2827
+ }
2828
+ function mapOpenInferenceEvents(document) {
2829
+ const mapped = document.spans.map(
2830
+ (span, index) => mapOpenInferenceSpan(span, index, document.version)
2831
+ );
2832
+ const spanIdToEventId = new Map(
2833
+ mapped.map((span) => [span.spanId, span.event.eventId])
2834
+ );
2835
+ for (const span of mapped) {
2836
+ if (span.parentSpanId === void 0) continue;
2837
+ span.event.parentId = spanIdToEventId.get(span.parentSpanId) ?? span.parentSpanId;
2838
+ }
2839
+ return {
2840
+ events: mapped.map((span) => span.event),
2841
+ warnings: mapped.flatMap((span) => span.warnings),
2842
+ unsupportedFields: mapped.flatMap((span) => span.unsupportedFields)
2843
+ };
2844
+ }
2845
+ var openInferenceJsonReader = {
2846
+ format: OPENINFERENCE_READER_FORMAT,
2847
+ name: "OpenInference JSON",
2848
+ async detect(input) {
2849
+ const resolved = await resolveInput(input);
2850
+ if (!resolved) return void 0;
2851
+ let parsed;
2852
+ try {
2853
+ parsed = parseJsonDocument(resolved.content);
2854
+ } catch {
2855
+ return void 0;
3062
2856
  }
2857
+ const document = extractOpenInferenceDocument(parsed);
2858
+ if (!document) return void 0;
3063
2859
  return {
3064
- spans,
3065
- confidence: 0.8,
3066
- description: "OpenInference data document",
3067
- version,
3068
- warnings,
3069
- unsupportedFields
2860
+ format: OPENINFERENCE_READER_FORMAT,
2861
+ confidence: document.confidence,
2862
+ readerName: "OpenInference JSON",
2863
+ description: document.description,
2864
+ warnings: attachSingleSourceFile(document.warnings, resolved)
3070
2865
  };
3071
- }
3072
- if (looksLikeOpenInferenceSpan(root)) {
2866
+ },
2867
+ async read(input) {
2868
+ const resolved = await resolveInput(input);
2869
+ if (!resolved) {
2870
+ throw new TraceReadError(
2871
+ "unsupported_format",
2872
+ "OpenInference JSON reader requires file, string, or buffer input."
2873
+ );
2874
+ }
2875
+ let parsed;
2876
+ try {
2877
+ parsed = parseJsonDocument(resolved.content);
2878
+ } catch {
2879
+ throw new TraceReadError("unsupported_format", "OpenInference JSON input is not valid JSON.", [
2880
+ {
2881
+ code: "openinference_invalid_json",
2882
+ message: "OpenInference JSON reader could not parse the input as JSON.",
2883
+ severity: "error"
2884
+ }
2885
+ ]);
2886
+ }
2887
+ const document = extractOpenInferenceDocument(parsed);
2888
+ if (!document || document.spans.length === 0) {
2889
+ throw new TraceReadError(
2890
+ "unsupported_format",
2891
+ "No valid OpenInference spans found.",
2892
+ attachSingleSourceFile(
2893
+ document?.warnings ?? [
2894
+ {
2895
+ code: "openinference_no_valid_spans",
2896
+ message: "OpenInference JSON input did not contain valid spans.",
2897
+ severity: "error"
2898
+ }
2899
+ ],
2900
+ resolved
2901
+ )
2902
+ );
2903
+ }
2904
+ const mapped = mapOpenInferenceEvents(document);
2905
+ const warnings = attachSingleSourceFile(
2906
+ [...document.warnings, ...mapped.warnings],
2907
+ resolved
2908
+ );
2909
+ const unsupportedFields = [
2910
+ ...document.unsupportedFields,
2911
+ ...mapped.unsupportedFields
2912
+ ].sort((a, b) => a.localeCompare(b));
3073
2913
  return {
3074
- spans: [root],
3075
- confidence: 0.76,
3076
- description: "OpenInference single span",
3077
- version,
2914
+ format: OPENINFERENCE_READER_FORMAT,
2915
+ events: mapped.events,
2916
+ runs: persistedInspectEventsToRunTrees(mapped.events, { skipInvalid: true }),
3078
2917
  warnings,
3079
- unsupportedFields
2918
+ unsupportedFields,
2919
+ sourceFiles: resolved.sourceFiles
3080
2920
  };
3081
2921
  }
3082
- if (rootFormat === "openinference" || rootCompatibility === "openinference-compatible") {
2922
+ };
2923
+ function parseOtlpAnyValue(value, field, warnings, unsupportedFields) {
2924
+ if (!isRecord6(value)) {
2925
+ unsupportedFields.push(field);
3083
2926
  warnings.push({
3084
- code: "openinference_missing_spans",
3085
- message: "OpenInference document is missing a spans array.",
3086
- severity: "error"
2927
+ code: "otlp_attribute_value_invalid",
2928
+ message: "OTLP attribute value was not an AnyValue object.",
2929
+ severity: "warning",
2930
+ field
3087
2931
  });
3088
- return {
3089
- spans: [],
3090
- confidence: 0.7,
3091
- description: "Malformed OpenInference document",
3092
- version,
3093
- warnings,
3094
- unsupportedFields
3095
- };
2932
+ return void 0;
3096
2933
  }
3097
- return void 0;
3098
- }
3099
- function parseUnixNanoToIso(value) {
3100
- if (typeof value === "bigint" && value >= 0n) {
3101
- return new Date(Number(value / 1000000n)).toISOString();
2934
+ if (typeof value.stringValue === "string") return value.stringValue;
2935
+ if (typeof value.boolValue === "boolean") return value.boolValue;
2936
+ if (typeof value.intValue === "number" && Number.isFinite(value.intValue)) {
2937
+ return value.intValue;
3102
2938
  }
3103
- if (typeof value === "number" && Number.isFinite(value) && value >= 0) {
3104
- return new Date(Math.floor(value / 1e6)).toISOString();
2939
+ if (typeof value.intValue === "string" && value.intValue.trim() !== "") {
2940
+ const n = Number(value.intValue);
2941
+ if (Number.isFinite(n)) return n;
3105
2942
  }
3106
- if (typeof value === "string" && /^\d+$/.test(value)) {
3107
- return new Date(Number(BigInt(value) / 1000000n)).toISOString();
2943
+ if (typeof value.doubleValue === "number" && Number.isFinite(value.doubleValue)) {
2944
+ return value.doubleValue;
3108
2945
  }
3109
- return void 0;
3110
- }
3111
- function parseIsoTime(value) {
3112
- if (!isNonEmptyString3(value)) return void 0;
3113
- const ms = Date.parse(value);
3114
- if (!Number.isFinite(ms)) return void 0;
3115
- return new Date(ms).toISOString();
3116
- }
3117
- function readOpenInferenceTimestamp(span, nanoKeys, isoKeys) {
3118
- for (const key of nanoKeys) {
3119
- const iso = parseUnixNanoToIso(span[key]);
3120
- if (iso !== void 0) return iso;
2946
+ if (isRecord6(value.arrayValue) && Array.isArray(value.arrayValue.values)) {
2947
+ return value.arrayValue.values.map(
2948
+ (item, index) => parseOtlpAnyValue(item, `${field}.arrayValue.values[${index}]`, warnings, unsupportedFields)
2949
+ );
3121
2950
  }
3122
- for (const key of isoKeys) {
3123
- const iso = parseIsoTime(span[key]);
3124
- if (iso !== void 0) return iso;
2951
+ if (isRecord6(value.kvlistValue) && Array.isArray(value.kvlistValue.values)) {
2952
+ const out = {};
2953
+ for (const [index, item] of value.kvlistValue.values.entries()) {
2954
+ if (!isRecord6(item) || typeof item.key !== "string") {
2955
+ unsupportedFields.push(`${field}.kvlistValue.values[${index}]`);
2956
+ continue;
2957
+ }
2958
+ out[item.key] = parseOtlpAnyValue(
2959
+ item.value,
2960
+ `${field}.kvlistValue.values[${index}].value`,
2961
+ warnings,
2962
+ unsupportedFields
2963
+ );
2964
+ }
2965
+ return out;
3125
2966
  }
3126
- return void 0;
3127
- }
3128
- function durationBetweenIso(startedAt, endedAt) {
3129
- if (startedAt === void 0 || endedAt === void 0) return void 0;
3130
- const startMs = Date.parse(startedAt);
3131
- const endMs = Date.parse(endedAt);
3132
- if (!Number.isFinite(startMs) || !Number.isFinite(endMs) || endMs < startMs) {
3133
- return void 0;
2967
+ if (typeof value.bytesValue === "string") {
2968
+ unsupportedFields.push(field);
2969
+ warnings.push({
2970
+ code: "otlp_bytes_value_summarized",
2971
+ message: "OTLP bytesValue attribute was summarized instead of decoded.",
2972
+ severity: "warning",
2973
+ field
2974
+ });
2975
+ return { type: "bytes", length: value.bytesValue.length };
3134
2976
  }
3135
- return endMs - startMs;
3136
- }
3137
- function isSensitiveOpenInferenceAttribute(key) {
3138
- return OPENINFERENCE_SENSITIVE_ATTRIBUTE_KEYS.some(
3139
- (sensitiveKey) => key === sensitiveKey || key.startsWith(`${sensitiveKey}.`) || key.endsWith(".message.content") || key.endsWith(".document.content")
3140
- );
2977
+ unsupportedFields.push(field);
2978
+ warnings.push({
2979
+ code: "otlp_attribute_value_unsupported",
2980
+ message: "OTLP attribute value used an unsupported AnyValue shape.",
2981
+ severity: "warning",
2982
+ field
2983
+ });
2984
+ return void 0;
3141
2985
  }
3142
- function summarizeAttributeValue(value) {
3143
- if (typeof value === "string") {
3144
- return { type: "string", length: value.length };
3145
- }
3146
- if (typeof value === "number") {
3147
- return { type: "number", finite: Number.isFinite(value) };
3148
- }
3149
- if (typeof value === "boolean") {
3150
- return { type: "boolean" };
3151
- }
3152
- if (Array.isArray(value)) {
3153
- return { type: "array", length: value.length };
2986
+ function parseOtlpAttributes(value, pathPrefix) {
2987
+ const attributes = {};
2988
+ const warnings = [];
2989
+ const unsupportedFields = [];
2990
+ if (value === void 0) {
2991
+ return { attributes, warnings, unsupportedFields };
3154
2992
  }
3155
- if (isRecord6(value)) {
3156
- return { type: "object", keyCount: Object.keys(value).length };
2993
+ if (!Array.isArray(value)) {
2994
+ unsupportedFields.push(pathPrefix);
2995
+ warnings.push({
2996
+ code: "otlp_attributes_invalid",
2997
+ message: "OTLP attributes field was not an array.",
2998
+ severity: "warning",
2999
+ field: pathPrefix
3000
+ });
3001
+ return { attributes, warnings, unsupportedFields };
3157
3002
  }
3158
- if (value === null) {
3159
- return { type: "null" };
3003
+ for (const [index, item] of value.entries()) {
3004
+ const field = `${pathPrefix}[${index}]`;
3005
+ if (!isRecord6(item) || typeof item.key !== "string") {
3006
+ unsupportedFields.push(field);
3007
+ warnings.push({
3008
+ code: "otlp_attribute_invalid",
3009
+ message: "Skipped OTLP attribute without a string key.",
3010
+ severity: "warning",
3011
+ field
3012
+ });
3013
+ continue;
3014
+ }
3015
+ const parsed = parseOtlpAnyValue(
3016
+ item.value,
3017
+ `${field}.value`,
3018
+ warnings,
3019
+ unsupportedFields
3020
+ );
3021
+ if (parsed !== void 0) {
3022
+ attributes[item.key] = parsed;
3023
+ }
3160
3024
  }
3161
- return { type: typeof value };
3025
+ return { attributes, warnings, unsupportedFields };
3162
3026
  }
3163
- function sanitizeOpenInferenceAttributes(attributes, pathPrefix) {
3164
- const out = {};
3027
+ function looksLikeOtlpSpan(value) {
3028
+ return isRecord6(value) && readStringField(value, ["traceId"]) !== void 0 && readStringField(value, ["spanId"]) !== void 0 && readStringField(value, ["name"]) !== void 0;
3029
+ }
3030
+ function extractOtlpDocument(root) {
3031
+ if (!isRecord6(root) || !Array.isArray(root.resourceSpans)) return void 0;
3032
+ const spans = [];
3165
3033
  const warnings = [];
3166
3034
  const unsupportedFields = [];
3167
- const summarizedKeys = [];
3168
- for (const [key, value] of Object.entries(attributes)) {
3169
- if (isSensitiveOpenInferenceAttribute(key)) {
3170
- summarizedKeys.push(key);
3171
- out[`${key}.summary`] = summarizeAttributeValue(value);
3172
- unsupportedFields.push(`${pathPrefix}.attributes.${key}`);
3035
+ for (const [resourceIndex, resourceSpan] of root.resourceSpans.entries()) {
3036
+ const resourcePath = `resourceSpans[${resourceIndex}]`;
3037
+ if (!isRecord6(resourceSpan)) {
3038
+ unsupportedFields.push(resourcePath);
3039
+ continue;
3040
+ }
3041
+ const resource = readRecordField(resourceSpan, "resource");
3042
+ const resourceParsed = parseOtlpAttributes(
3043
+ resource?.attributes,
3044
+ `${resourcePath}.resource.attributes`
3045
+ );
3046
+ warnings.push(...resourceParsed.warnings);
3047
+ unsupportedFields.push(...resourceParsed.unsupportedFields);
3048
+ if (!Array.isArray(resourceSpan.scopeSpans)) {
3049
+ unsupportedFields.push(`${resourcePath}.scopeSpans`);
3050
+ warnings.push({
3051
+ code: "otlp_scope_spans_missing",
3052
+ message: "OTLP resourceSpans entry did not contain a scopeSpans array.",
3053
+ severity: "warning",
3054
+ field: `${resourcePath}.scopeSpans`
3055
+ });
3173
3056
  continue;
3174
3057
  }
3175
- out[key] = value;
3058
+ for (const [scopeIndex, scopeSpan] of resourceSpan.scopeSpans.entries()) {
3059
+ const scopePath = `${resourcePath}.scopeSpans[${scopeIndex}]`;
3060
+ if (!isRecord6(scopeSpan)) {
3061
+ unsupportedFields.push(scopePath);
3062
+ continue;
3063
+ }
3064
+ const scope = readRecordField(scopeSpan, "scope");
3065
+ const scopeParsed = parseOtlpAttributes(
3066
+ scope?.attributes,
3067
+ `${scopePath}.scope.attributes`
3068
+ );
3069
+ warnings.push(...scopeParsed.warnings);
3070
+ unsupportedFields.push(...scopeParsed.unsupportedFields);
3071
+ if (!Array.isArray(scopeSpan.spans)) {
3072
+ unsupportedFields.push(`${scopePath}.spans`);
3073
+ warnings.push({
3074
+ code: "otlp_spans_missing",
3075
+ message: "OTLP scopeSpans entry did not contain a spans array.",
3076
+ severity: "warning",
3077
+ field: `${scopePath}.spans`
3078
+ });
3079
+ continue;
3080
+ }
3081
+ for (const [spanIndex, span] of scopeSpan.spans.entries()) {
3082
+ const spanPath = `${scopePath}.spans[${spanIndex}]`;
3083
+ if (!looksLikeOtlpSpan(span)) {
3084
+ unsupportedFields.push(spanPath);
3085
+ warnings.push({
3086
+ code: "otlp_invalid_span",
3087
+ message: "Skipped OTLP span without required traceId, spanId, or name.",
3088
+ severity: "warning",
3089
+ field: spanPath
3090
+ });
3091
+ continue;
3092
+ }
3093
+ spans.push({
3094
+ span,
3095
+ resourceAttributes: resourceParsed.attributes,
3096
+ scopeAttributes: scopeParsed.attributes,
3097
+ scopeName: readStringField(scope ?? {}, ["name"]),
3098
+ scopeVersion: readStringField(scope ?? {}, ["version"]),
3099
+ pathPrefix: spanPath
3100
+ });
3101
+ }
3102
+ }
3176
3103
  }
3177
- if (summarizedKeys.length > 0) {
3178
- out["openinference.summarized_attributes"] = summarizedKeys;
3104
+ if (spans.length === 0) {
3179
3105
  warnings.push({
3180
- code: "openinference_sensitive_attribute_summarized",
3181
- message: "OpenInference prompt/output/document attribute(s) were summarized instead of copied verbatim.",
3182
- severity: "warning"
3106
+ code: "otlp_no_valid_spans",
3107
+ message: "OTLP JSON payload did not contain any valid spans.",
3108
+ severity: "error"
3183
3109
  });
3110
+ return {
3111
+ spans,
3112
+ confidence: 0.7,
3113
+ description: "Malformed OTLP JSON trace payload",
3114
+ warnings,
3115
+ unsupportedFields
3116
+ };
3184
3117
  }
3185
- return { attributes: out, warnings, unsupportedFields };
3186
- }
3187
- function mapOpenInferenceKind(span, attributes, pathPrefix) {
3188
- const warnings = [];
3189
- const agentInspectKind = attributes["agent_inspect.kind"];
3190
- 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") {
3191
- return { kind: agentInspectKind, warnings };
3192
- }
3193
- const rawKind = readStringField(span, ["kind", "span_kind", "spanKind"]) ?? (typeof attributes["openinference.span.kind"] === "string" ? attributes["openinference.span.kind"] : void 0);
3194
- const normalized = rawKind?.toUpperCase();
3195
- switch (normalized) {
3196
- case "LLM":
3197
- return { kind: "LLM", warnings };
3198
- case "TOOL":
3199
- return { kind: "TOOL", warnings };
3200
- case "CHAIN":
3201
- return { kind: "CHAIN", warnings };
3202
- case "RETRIEVER":
3203
- return { kind: "RETRIEVER", warnings };
3204
- case "AGENT":
3205
- return { kind: "AGENT", warnings };
3206
- case "EMBEDDING":
3207
- warnings.push({
3208
- code: "openinference_kind_semantic_loss",
3209
- message: "OpenInference EMBEDDING span kind mapped to AgentInspect LLM.",
3210
- severity: "warning",
3211
- field: `${pathPrefix}.attributes.openinference.span.kind`
3212
- });
3213
- return { kind: "LLM", warnings };
3214
- case "RERANKER":
3215
- warnings.push({
3216
- code: "openinference_kind_semantic_loss",
3217
- message: "OpenInference RERANKER span kind mapped to AgentInspect RETRIEVER.",
3218
- severity: "warning",
3219
- field: `${pathPrefix}.attributes.openinference.span.kind`
3220
- });
3221
- return { kind: "RETRIEVER", warnings };
3222
- case "UNKNOWN":
3223
- case void 0:
3224
- warnings.push({
3225
- code: "openinference_kind_unknown",
3226
- message: "OpenInference span kind was missing or unknown; mapped to AgentInspect LOGIC.",
3227
- severity: "warning",
3228
- field: `${pathPrefix}.attributes.openinference.span.kind`
3229
- });
3230
- return { kind: "LOGIC", warnings };
3231
- default:
3232
- warnings.push({
3233
- code: "openinference_kind_unsupported",
3234
- message: `Unsupported OpenInference span kind "${rawKind}" mapped to AgentInspect LOGIC.`,
3235
- severity: "warning",
3236
- field: `${pathPrefix}.attributes.openinference.span.kind`
3237
- });
3238
- return { kind: "LOGIC", warnings };
3239
- }
3118
+ return {
3119
+ spans,
3120
+ confidence: 0.93,
3121
+ description: "OTLP JSON trace payload",
3122
+ warnings,
3123
+ unsupportedFields
3124
+ };
3240
3125
  }
3241
- function mapOpenInferenceStatus(status) {
3126
+ function mapOtlpStatus(status) {
3242
3127
  if (!isRecord6(status)) return void 0;
3243
3128
  const rawCode = status.code;
3244
3129
  if (typeof rawCode !== "string") return void 0;
3245
3130
  switch (rawCode.toUpperCase()) {
3131
+ case "STATUS_CODE_OK":
3246
3132
  case "OK":
3247
3133
  return "ok";
3134
+ case "STATUS_CODE_ERROR":
3248
3135
  case "ERROR":
3249
3136
  return "error";
3137
+ case "STATUS_CODE_UNSET":
3250
3138
  case "UNSET":
3251
3139
  return "unknown";
3252
3140
  default:
3253
3141
  return "unknown";
3254
3142
  }
3255
3143
  }
3256
- function readOpenInferenceTokenUsage(attributes) {
3257
- const prompt = attributes["llm.token_count.prompt"];
3258
- const completion = attributes["llm.token_count.completion"];
3259
- const total = attributes["llm.token_count.total"];
3260
- const cached = attributes["llm.token_count.prompt_details.cache_read"];
3261
- const usage = {};
3262
- if (typeof prompt === "number" && Number.isFinite(prompt) && prompt >= 0) {
3263
- usage.input = prompt;
3144
+ function readOtlpKind(attributes, pathPrefix) {
3145
+ const warnings = [];
3146
+ const agentInspectKind = attributes["agent_inspect.kind"];
3147
+ 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") {
3148
+ return { kind: agentInspectKind, warnings };
3264
3149
  }
3265
- if (typeof completion === "number" && Number.isFinite(completion) && completion >= 0) {
3266
- usage.output = completion;
3150
+ const operation = attributes["gen_ai.operation.name"];
3151
+ if (typeof operation === "string") {
3152
+ switch (operation) {
3153
+ case "generate_content":
3154
+ case "chat":
3155
+ return { kind: "LLM", warnings };
3156
+ case "execute_tool":
3157
+ return { kind: "TOOL", warnings };
3158
+ case "invoke_agent":
3159
+ return { kind: "AGENT", warnings };
3160
+ default:
3161
+ warnings.push({
3162
+ code: "otlp_gen_ai_operation_semantic_loss",
3163
+ message: `OTLP GenAI operation "${operation}" mapped to AgentInspect LOGIC.`,
3164
+ severity: "warning",
3165
+ field: `${pathPrefix}.attributes.gen_ai.operation.name`
3166
+ });
3167
+ return { kind: "LOGIC", warnings };
3168
+ }
3267
3169
  }
3268
- if (typeof total === "number" && Number.isFinite(total) && total >= 0) {
3269
- usage.total = total;
3170
+ warnings.push({
3171
+ code: "otlp_kind_unknown",
3172
+ message: "OTLP span had no AgentInspect kind or GenAI operation; mapped to LOGIC.",
3173
+ severity: "warning",
3174
+ field: `${pathPrefix}.attributes`
3175
+ });
3176
+ return { kind: "LOGIC", warnings };
3177
+ }
3178
+ function readOtlpTokenUsage(attributes) {
3179
+ const input = attributes["gen_ai.usage.input_tokens"];
3180
+ const output = attributes["gen_ai.usage.output_tokens"];
3181
+ const usage = {};
3182
+ if (typeof input === "number" && Number.isFinite(input) && input >= 0) {
3183
+ usage.input = input;
3270
3184
  }
3271
- if (typeof cached === "number" && Number.isFinite(cached) && cached >= 0) {
3272
- usage.cached = cached;
3185
+ if (typeof output === "number" && Number.isFinite(output) && output >= 0) {
3186
+ usage.output = output;
3273
3187
  }
3274
- if (usage.total === void 0 && usage.input !== void 0 && usage.output !== void 0) {
3188
+ if (usage.input !== void 0 && usage.output !== void 0) {
3275
3189
  usage.total = usage.input + usage.output;
3276
3190
  }
3277
3191
  return Object.keys(usage).length > 0 ? usage : void 0;
3278
3192
  }
3279
- function readOpenInferenceConfidence(attributes) {
3280
- const confidence = attributes["agent_inspect.confidence"];
3281
- if (confidence === "explicit" || confidence === "correlated" || confidence === "heuristic" || confidence === "unknown") {
3282
- return confidence;
3193
+ function readOtlpConfidence(attributes) {
3194
+ return readOpenInferenceConfidence(attributes);
3195
+ }
3196
+ function sanitizeOtlpAttributes(attributes, pathPrefix) {
3197
+ const ownerPath = pathPrefix.endsWith(".attributes") ? pathPrefix.slice(0, -".attributes".length) : pathPrefix;
3198
+ const sanitized = sanitizeOpenInferenceAttributes(attributes, ownerPath);
3199
+ return {
3200
+ ...sanitized,
3201
+ warnings: sanitized.warnings.map(
3202
+ (warning) => warning.code === "openinference_sensitive_attribute_summarized" ? {
3203
+ ...warning,
3204
+ code: "otlp_sensitive_attribute_summarized",
3205
+ message: "OTLP prompt/output/document attribute(s) were summarized instead of copied verbatim."
3206
+ } : warning
3207
+ )
3208
+ };
3209
+ }
3210
+ function mapOtlpEvents(value, pathPrefix) {
3211
+ const warnings = [];
3212
+ const unsupportedFields = [];
3213
+ if (value === void 0) return { warnings, unsupportedFields };
3214
+ if (!Array.isArray(value)) {
3215
+ unsupportedFields.push(pathPrefix);
3216
+ warnings.push({
3217
+ code: "otlp_events_invalid",
3218
+ message: "OTLP events field was not an array.",
3219
+ severity: "warning",
3220
+ field: pathPrefix
3221
+ });
3222
+ return { warnings, unsupportedFields };
3223
+ }
3224
+ const events = [];
3225
+ for (const [index, event] of value.entries()) {
3226
+ const eventPath = `${pathPrefix}[${index}]`;
3227
+ if (!isRecord6(event)) {
3228
+ unsupportedFields.push(eventPath);
3229
+ continue;
3230
+ }
3231
+ const parsedAttributes = parseOtlpAttributes(
3232
+ event.attributes,
3233
+ `${eventPath}.attributes`
3234
+ );
3235
+ warnings.push(...parsedAttributes.warnings);
3236
+ unsupportedFields.push(...parsedAttributes.unsupportedFields);
3237
+ const sanitized = sanitizeOtlpAttributes(
3238
+ parsedAttributes.attributes,
3239
+ `${eventPath}.attributes`
3240
+ );
3241
+ warnings.push(...sanitized.warnings);
3242
+ unsupportedFields.push(...sanitized.unsupportedFields);
3243
+ const out = {};
3244
+ const name = readStringField(event, ["name"]);
3245
+ if (name !== void 0) {
3246
+ out.name = name;
3247
+ }
3248
+ const timestamp = parseUnixNanoToIso(event.timeUnixNano);
3249
+ if (timestamp !== void 0) {
3250
+ out.timestamp = timestamp;
3251
+ } else if (event.timeUnixNano !== void 0) {
3252
+ unsupportedFields.push(`${eventPath}.timeUnixNano`);
3253
+ warnings.push({
3254
+ code: "otlp_event_timestamp_invalid",
3255
+ message: "OTLP event timeUnixNano could not be parsed.",
3256
+ severity: "warning",
3257
+ field: `${eventPath}.timeUnixNano`
3258
+ });
3259
+ }
3260
+ if (Object.keys(sanitized.attributes).length > 0) {
3261
+ out.attributes = sanitized.attributes;
3262
+ }
3263
+ events.push(out);
3264
+ }
3265
+ return {
3266
+ events: events.length > 0 ? events : void 0,
3267
+ warnings,
3268
+ unsupportedFields
3269
+ };
3270
+ }
3271
+ function mapOtlpSpan(context) {
3272
+ const { span, pathPrefix } = context;
3273
+ const warnings = [];
3274
+ const unsupportedFields = [];
3275
+ const parsedSpanAttributes = parseOtlpAttributes(
3276
+ span.attributes,
3277
+ `${pathPrefix}.attributes`
3278
+ );
3279
+ warnings.push(...parsedSpanAttributes.warnings);
3280
+ unsupportedFields.push(...parsedSpanAttributes.unsupportedFields);
3281
+ const sanitizedSpanAttributes = sanitizeOtlpAttributes(
3282
+ parsedSpanAttributes.attributes,
3283
+ `${pathPrefix}.attributes`
3284
+ );
3285
+ warnings.push(...sanitizedSpanAttributes.warnings);
3286
+ unsupportedFields.push(...sanitizedSpanAttributes.unsupportedFields);
3287
+ const attributes = {
3288
+ ...sanitizedSpanAttributes.attributes
3289
+ };
3290
+ for (const [key, value] of Object.entries(context.resourceAttributes)) {
3291
+ attributes[`resource.${key}`] = value;
3292
+ }
3293
+ for (const [key, value] of Object.entries(context.scopeAttributes)) {
3294
+ attributes[`scope.${key}`] = value;
3295
+ }
3296
+ if (context.scopeName !== void 0) {
3297
+ attributes["scope.name"] = context.scopeName;
3298
+ }
3299
+ if (context.scopeVersion !== void 0) {
3300
+ attributes["scope.version"] = context.scopeVersion;
3283
3301
  }
3284
- return "correlated";
3285
- }
3286
- function mapOpenInferenceSpan(span, index, version) {
3287
- const pathPrefix = `spans[${index}]`;
3288
- const warnings = [];
3289
- const unsupportedFields = [];
3290
- const rawAttributes = readRecordField(span, "attributes") ?? {};
3291
- const sanitized = sanitizeOpenInferenceAttributes(rawAttributes, pathPrefix);
3292
- warnings.push(...sanitized.warnings);
3293
- unsupportedFields.push(...sanitized.unsupportedFields);
3294
- const attributes = { ...sanitized.attributes };
3295
3302
  for (const [key, value] of Object.entries(span)) {
3296
- if (OPENINFERENCE_SPAN_KEYS.has(key)) continue;
3303
+ if (OTLP_SPAN_KEYS.has(key)) continue;
3297
3304
  unsupportedFields.push(`${pathPrefix}.${key}`);
3298
3305
  if (value === null || typeof value !== "object") {
3299
- attributes[`openinference.${key}`] = value;
3306
+ attributes[`otlp.${key}`] = value;
3300
3307
  } else {
3301
- attributes[`openinference.${key}.summary`] = summarizeAttributeValue(value);
3308
+ attributes[`otlp.${key}.summary`] = summarizeAttributeValue(value);
3302
3309
  warnings.push({
3303
- code: "openinference_unsupported_field_summarized",
3304
- message: `Unsupported OpenInference span field "${key}" was summarized.`,
3310
+ code: "otlp_unsupported_field_summarized",
3311
+ message: `Unsupported OTLP span field "${key}" was summarized.`,
3305
3312
  severity: "warning",
3306
3313
  field: `${pathPrefix}.${key}`
3307
3314
  });
3308
3315
  }
3309
3316
  }
3310
- const traceId = readStringField(span, ["trace_id", "traceId"]) ?? `trace-${index}`;
3311
- const spanId = readStringField(span, ["span_id", "spanId"]) ?? `span-${index}`;
3312
- const parentSpanId = readStringField(span, ["parent_span_id", "parentSpanId"]);
3313
- const name = readStringField(span, ["name"]) ?? spanId;
3317
+ for (const key of [
3318
+ "droppedAttributesCount",
3319
+ "droppedEventsCount",
3320
+ "droppedLinksCount",
3321
+ "links"
3322
+ ]) {
3323
+ if (span[key] !== void 0) {
3324
+ unsupportedFields.push(`${pathPrefix}.${key}`);
3325
+ warnings.push({
3326
+ code: "otlp_span_field_not_mapped",
3327
+ message: `OTLP span field "${key}" is not represented in AgentInspect events.`,
3328
+ severity: "warning",
3329
+ field: `${pathPrefix}.${key}`
3330
+ });
3331
+ }
3332
+ }
3333
+ const events = mapOtlpEvents(span.events, `${pathPrefix}.events`);
3334
+ warnings.push(...events.warnings);
3335
+ unsupportedFields.push(...events.unsupportedFields);
3336
+ if (events.events !== void 0) {
3337
+ attributes["otlp.events"] = events.events;
3338
+ }
3339
+ const traceId = readStringField(span, ["traceId"]) ?? "trace-unknown";
3340
+ const spanId = readStringField(span, ["spanId"]) ?? "span-unknown";
3341
+ const parentSpanId = readStringField(span, ["parentSpanId"]);
3314
3342
  const startedAt = readOpenInferenceTimestamp(
3315
3343
  span,
3316
- ["start_time_unix_nano", "startTimeUnixNano"],
3317
- ["start_time", "startTime"]
3318
- );
3319
- const endedAt = readOpenInferenceTimestamp(
3320
- span,
3321
- ["end_time_unix_nano", "endTimeUnixNano"],
3322
- ["end_time", "endTime"]
3344
+ ["startTimeUnixNano"],
3345
+ []
3323
3346
  );
3347
+ const endedAt = readOpenInferenceTimestamp(span, ["endTimeUnixNano"], []);
3324
3348
  const timestamp = startedAt ?? "1970-01-01T00:00:00.000Z";
3325
3349
  if (startedAt === void 0) {
3350
+ unsupportedFields.push(`${pathPrefix}.startTimeUnixNano`);
3326
3351
  warnings.push({
3327
- code: "openinference_missing_start_time",
3328
- message: "OpenInference span is missing a valid start time; using Unix epoch.",
3352
+ code: "otlp_missing_start_time",
3353
+ message: "OTLP span is missing a valid startTimeUnixNano; using Unix epoch.",
3329
3354
  severity: "warning",
3330
- field: `${pathPrefix}.start_time_unix_nano`
3355
+ field: `${pathPrefix}.startTimeUnixNano`
3331
3356
  });
3332
- unsupportedFields.push(`${pathPrefix}.start_time_unix_nano`);
3333
3357
  }
3334
- const { kind, warnings: kindWarnings } = mapOpenInferenceKind(
3335
- span,
3336
- rawAttributes,
3358
+ const { kind, warnings: kindWarnings } = readOtlpKind(
3359
+ parsedSpanAttributes.attributes,
3337
3360
  pathPrefix
3338
3361
  );
3339
3362
  warnings.push(...kindWarnings);
3340
- const status = mapOpenInferenceStatus(span.status);
3341
- const tokenUsage = readOpenInferenceTokenUsage(rawAttributes);
3363
+ const status = mapOtlpStatus(span.status);
3364
+ const tokenUsage = readOtlpTokenUsage(parsedSpanAttributes.attributes);
3342
3365
  const errorMessage = isRecord6(span.status) && typeof span.status.message === "string" ? span.status.message : void 0;
3343
3366
  const event = {
3344
3367
  schemaVersion: "0.2",
3345
- eventId: typeof rawAttributes["agent_inspect.event_id"] === "string" ? rawAttributes["agent_inspect.event_id"] : spanId,
3346
- runId: typeof rawAttributes["agent_inspect.run_id"] === "string" ? rawAttributes["agent_inspect.run_id"] : traceId,
3368
+ eventId: typeof parsedSpanAttributes.attributes["agent_inspect.event_id"] === "string" ? parsedSpanAttributes.attributes["agent_inspect.event_id"] : spanId,
3369
+ runId: typeof parsedSpanAttributes.attributes["agent_inspect.run_id"] === "string" ? parsedSpanAttributes.attributes["agent_inspect.run_id"] : traceId,
3347
3370
  kind,
3348
- name,
3371
+ name: readStringField(span, ["name"]) ?? spanId,
3349
3372
  timestamp,
3350
- confidence: readOpenInferenceConfidence(rawAttributes),
3373
+ confidence: readOtlpConfidence(parsedSpanAttributes.attributes),
3351
3374
  source: {
3352
3375
  type: "otel",
3353
- name: "openinference",
3354
- ...version !== void 0 ? { version } : {}
3376
+ name: context.scopeName ?? (typeof context.resourceAttributes["service.name"] === "string" ? context.resourceAttributes["service.name"] : "otlp-json"),
3377
+ ...context.scopeVersion !== void 0 ? { version: context.scopeVersion } : {}
3355
3378
  },
3356
3379
  attributes,
3357
3380
  trace: {
@@ -3378,7 +3401,7 @@ function mapOpenInferenceSpan(span, index, version) {
3378
3401
  }
3379
3402
  if (status === "error") {
3380
3403
  event.error = {
3381
- message: errorMessage !== void 0 && errorMessage.trim() !== "" ? errorMessage : "OpenInference span error"
3404
+ message: errorMessage !== void 0 && errorMessage.trim() !== "" ? errorMessage : "OTLP span error"
3382
3405
  };
3383
3406
  }
3384
3407
  return {
@@ -3389,10 +3412,8 @@ function mapOpenInferenceSpan(span, index, version) {
3389
3412
  ...parentSpanId !== void 0 ? { parentSpanId } : {}
3390
3413
  };
3391
3414
  }
3392
- function mapOpenInferenceEvents(document) {
3393
- const mapped = document.spans.map(
3394
- (span, index) => mapOpenInferenceSpan(span, index, document.version)
3395
- );
3415
+ function mapOtlpEventsToPersisted(document) {
3416
+ const mapped = document.spans.map((span) => mapOtlpSpan(span));
3396
3417
  const spanIdToEventId = new Map(
3397
3418
  mapped.map((span) => [span.spanId, span.event.eventId])
3398
3419
  );
@@ -3406,9 +3427,9 @@ function mapOpenInferenceEvents(document) {
3406
3427
  unsupportedFields: mapped.flatMap((span) => span.unsupportedFields)
3407
3428
  };
3408
3429
  }
3409
- var openInferenceJsonReader = {
3410
- format: OPENINFERENCE_READER_FORMAT,
3411
- name: "OpenInference JSON",
3430
+ var otlpJsonReader = {
3431
+ format: OTLP_READER_FORMAT,
3432
+ name: "OTLP JSON",
3412
3433
  async detect(input) {
3413
3434
  const resolved = await resolveInput(input);
3414
3435
  if (!resolved) return void 0;
@@ -3418,12 +3439,12 @@ var openInferenceJsonReader = {
3418
3439
  } catch {
3419
3440
  return void 0;
3420
3441
  }
3421
- const document = extractOpenInferenceDocument(parsed);
3442
+ const document = extractOtlpDocument(parsed);
3422
3443
  if (!document) return void 0;
3423
3444
  return {
3424
- format: OPENINFERENCE_READER_FORMAT,
3445
+ format: OTLP_READER_FORMAT,
3425
3446
  confidence: document.confidence,
3426
- readerName: "OpenInference JSON",
3447
+ readerName: "OTLP JSON",
3427
3448
  description: document.description,
3428
3449
  warnings: attachSingleSourceFile(document.warnings, resolved)
3429
3450
  };
@@ -3433,31 +3454,31 @@ var openInferenceJsonReader = {
3433
3454
  if (!resolved) {
3434
3455
  throw new TraceReadError(
3435
3456
  "unsupported_format",
3436
- "OpenInference JSON reader requires file, string, or buffer input."
3457
+ "OTLP JSON reader requires file, string, or buffer input."
3437
3458
  );
3438
3459
  }
3439
3460
  let parsed;
3440
3461
  try {
3441
3462
  parsed = parseJsonDocument(resolved.content);
3442
3463
  } catch {
3443
- throw new TraceReadError("unsupported_format", "OpenInference JSON input is not valid JSON.", [
3464
+ throw new TraceReadError("unsupported_format", "OTLP JSON input is not valid JSON.", [
3444
3465
  {
3445
- code: "openinference_invalid_json",
3446
- message: "OpenInference JSON reader could not parse the input as JSON.",
3466
+ code: "otlp_invalid_json",
3467
+ message: "OTLP JSON reader could not parse the input as JSON.",
3447
3468
  severity: "error"
3448
3469
  }
3449
3470
  ]);
3450
3471
  }
3451
- const document = extractOpenInferenceDocument(parsed);
3472
+ const document = extractOtlpDocument(parsed);
3452
3473
  if (!document || document.spans.length === 0) {
3453
3474
  throw new TraceReadError(
3454
3475
  "unsupported_format",
3455
- "No valid OpenInference spans found.",
3476
+ "No valid OTLP spans found.",
3456
3477
  attachSingleSourceFile(
3457
3478
  document?.warnings ?? [
3458
3479
  {
3459
- code: "openinference_no_valid_spans",
3460
- message: "OpenInference JSON input did not contain valid spans.",
3480
+ code: "otlp_no_valid_spans",
3481
+ message: "OTLP JSON input did not contain valid spans.",
3461
3482
  severity: "error"
3462
3483
  }
3463
3484
  ],
@@ -3465,7 +3486,7 @@ var openInferenceJsonReader = {
3465
3486
  )
3466
3487
  );
3467
3488
  }
3468
- const mapped = mapOpenInferenceEvents(document);
3489
+ const mapped = mapOtlpEventsToPersisted(document);
3469
3490
  const warnings = attachSingleSourceFile(
3470
3491
  [...document.warnings, ...mapped.warnings],
3471
3492
  resolved
@@ -3475,7 +3496,7 @@ var openInferenceJsonReader = {
3475
3496
  ...mapped.unsupportedFields
3476
3497
  ].sort((a, b) => a.localeCompare(b));
3477
3498
  return {
3478
- format: OPENINFERENCE_READER_FORMAT,
3499
+ format: OTLP_READER_FORMAT,
3479
3500
  events: mapped.events,
3480
3501
  runs: persistedInspectEventsToRunTrees(mapped.events, { skipInvalid: true }),
3481
3502
  warnings,
@@ -3484,788 +3505,770 @@ var openInferenceJsonReader = {
3484
3505
  };
3485
3506
  }
3486
3507
  };
3487
- function parseOtlpAnyValue(value, field, warnings, unsupportedFields) {
3488
- if (!isRecord6(value)) {
3489
- unsupportedFields.push(field);
3490
- warnings.push({
3491
- code: "otlp_attribute_value_invalid",
3492
- message: "OTLP attribute value was not an AnyValue object.",
3493
- severity: "warning",
3494
- field
3495
- });
3496
- return void 0;
3497
- }
3498
- if (typeof value.stringValue === "string") return value.stringValue;
3499
- if (typeof value.boolValue === "boolean") return value.boolValue;
3500
- if (typeof value.intValue === "number" && Number.isFinite(value.intValue)) {
3501
- return value.intValue;
3502
- }
3503
- if (typeof value.intValue === "string" && value.intValue.trim() !== "") {
3504
- const n = Number(value.intValue);
3505
- if (Number.isFinite(n)) return n;
3506
- }
3507
- if (typeof value.doubleValue === "number" && Number.isFinite(value.doubleValue)) {
3508
- return value.doubleValue;
3509
- }
3510
- if (isRecord6(value.arrayValue) && Array.isArray(value.arrayValue.values)) {
3511
- return value.arrayValue.values.map(
3512
- (item, index) => parseOtlpAnyValue(item, `${field}.arrayValue.values[${index}]`, warnings, unsupportedFields)
3513
- );
3514
- }
3515
- if (isRecord6(value.kvlistValue) && Array.isArray(value.kvlistValue.values)) {
3516
- const out = {};
3517
- for (const [index, item] of value.kvlistValue.values.entries()) {
3518
- if (!isRecord6(item) || typeof item.key !== "string") {
3519
- unsupportedFields.push(`${field}.kvlistValue.values[${index}]`);
3520
- continue;
3521
- }
3522
- out[item.key] = parseOtlpAnyValue(
3523
- item.value,
3524
- `${field}.kvlistValue.values[${index}].value`,
3525
- warnings,
3526
- unsupportedFields
3527
- );
3508
+ var agentInspectJsonlReader = {
3509
+ format: "agent-inspect-jsonl",
3510
+ name: "AgentInspect JSONL",
3511
+ async detect(input) {
3512
+ const resolved = await resolveInput(input);
3513
+ if (!resolved) return void 0;
3514
+ const detected = detectJsonlFormat(resolved.content);
3515
+ if (detected.validRows === 0 || detected.format === "empty") {
3516
+ return void 0;
3528
3517
  }
3529
- return out;
3530
- }
3531
- if (typeof value.bytesValue === "string") {
3532
- unsupportedFields.push(field);
3533
- warnings.push({
3534
- code: "otlp_bytes_value_summarized",
3535
- message: "OTLP bytesValue attribute was summarized instead of decoded.",
3536
- severity: "warning",
3537
- field
3538
- });
3539
- return { type: "bytes", length: value.bytesValue.length };
3540
- }
3541
- unsupportedFields.push(field);
3542
- warnings.push({
3543
- code: "otlp_attribute_value_unsupported",
3544
- message: "OTLP attribute value used an unsupported AnyValue shape.",
3545
- severity: "warning",
3546
- field
3547
- });
3548
- return void 0;
3549
- }
3550
- function parseOtlpAttributes(value, pathPrefix) {
3551
- const attributes = {};
3552
- const warnings = [];
3553
- const unsupportedFields = [];
3554
- if (value === void 0) {
3555
- return { attributes, warnings, unsupportedFields };
3556
- }
3557
- if (!Array.isArray(value)) {
3558
- unsupportedFields.push(pathPrefix);
3559
- warnings.push({
3560
- code: "otlp_attributes_invalid",
3561
- message: "OTLP attributes field was not an array.",
3562
- severity: "warning",
3563
- field: pathPrefix
3564
- });
3565
- return { attributes, warnings, unsupportedFields };
3518
+ return {
3519
+ format: "agent-inspect-jsonl",
3520
+ confidence: 0.95,
3521
+ readerName: "AgentInspect JSONL",
3522
+ description: agentInspectFormatLabel(detected.format),
3523
+ warnings: attachSingleSourceFile(detected.warnings, resolved)
3524
+ };
3525
+ },
3526
+ async read(input) {
3527
+ const resolved = await resolveInput(input);
3528
+ if (!resolved) {
3529
+ throw new Error("AgentInspect JSONL reader requires file, directory, string, or buffer input.");
3530
+ }
3531
+ const parsed = parseTraceJsonl(resolved.content, { warnings: false });
3532
+ if (parsed.sourceEventCount === 0) {
3533
+ throw new Error("No valid AgentInspect JSONL events found.");
3534
+ }
3535
+ const events = persistedEventsForParsedTrace(parsed);
3536
+ return {
3537
+ format: agentInspectFormatLabel(parsed.format),
3538
+ events,
3539
+ runs: persistedInspectEventsToRunTrees(events, { skipInvalid: true }),
3540
+ warnings: parsed.format === "mixed" ? attachSingleSourceFile(
3541
+ [
3542
+ {
3543
+ code: "mixed_agent_inspect_jsonl",
3544
+ message: "Trace input mixes schemaVersion 0.1 and 0.2 rows; events were normalized for reading.",
3545
+ severity: "warning"
3546
+ }
3547
+ ],
3548
+ resolved
3549
+ ) : [],
3550
+ unsupportedFields: [],
3551
+ sourceFiles: resolved.sourceFiles
3552
+ };
3566
3553
  }
3567
- for (const [index, item] of value.entries()) {
3568
- const field = `${pathPrefix}[${index}]`;
3569
- if (!isRecord6(item) || typeof item.key !== "string") {
3570
- unsupportedFields.push(field);
3571
- warnings.push({
3572
- code: "otlp_attribute_invalid",
3573
- message: "Skipped OTLP attribute without a string key.",
3574
- severity: "warning",
3575
- field
3576
- });
3577
- continue;
3578
- }
3579
- const parsed = parseOtlpAnyValue(
3580
- item.value,
3581
- `${field}.value`,
3582
- warnings,
3583
- unsupportedFields
3584
- );
3585
- if (parsed !== void 0) {
3586
- attributes[item.key] = parsed;
3554
+ };
3555
+ var DEFAULT_TRACE_READERS = [
3556
+ agentInspectJsonlReader,
3557
+ openInferenceJsonReader,
3558
+ otlpJsonReader
3559
+ ];
3560
+ async function detectTraceFormat(input, options = {}) {
3561
+ const readers = options.readers ?? DEFAULT_TRACE_READERS;
3562
+ if (options.format !== void 0) {
3563
+ const reader = findReaderByFormat(options.format, readers);
3564
+ if (!reader) {
3565
+ return {
3566
+ status: "unsupported",
3567
+ candidates: [],
3568
+ warnings: [
3569
+ {
3570
+ code: "unsupported_format",
3571
+ message: `No trace reader is registered for format "${options.format}".`,
3572
+ severity: "error"
3573
+ }
3574
+ ]
3575
+ };
3587
3576
  }
3577
+ return {
3578
+ status: "detected",
3579
+ format: reader.format,
3580
+ candidates: [
3581
+ {
3582
+ format: reader.format,
3583
+ confidence: 1,
3584
+ readerName: reader.name,
3585
+ description: "Explicit format override"
3586
+ }
3587
+ ],
3588
+ warnings: []
3589
+ };
3588
3590
  }
3589
- return { attributes, warnings, unsupportedFields };
3590
- }
3591
- function looksLikeOtlpSpan(value) {
3592
- return isRecord6(value) && readStringField(value, ["traceId"]) !== void 0 && readStringField(value, ["spanId"]) !== void 0 && readStringField(value, ["name"]) !== void 0;
3593
- }
3594
- function extractOtlpDocument(root) {
3595
- if (!isRecord6(root) || !Array.isArray(root.resourceSpans)) return void 0;
3596
- const spans = [];
3591
+ const candidates = [];
3597
3592
  const warnings = [];
3598
- const unsupportedFields = [];
3599
- for (const [resourceIndex, resourceSpan] of root.resourceSpans.entries()) {
3600
- const resourcePath = `resourceSpans[${resourceIndex}]`;
3601
- if (!isRecord6(resourceSpan)) {
3602
- unsupportedFields.push(resourcePath);
3603
- continue;
3604
- }
3605
- const resource = readRecordField(resourceSpan, "resource");
3606
- const resourceParsed = parseOtlpAttributes(
3607
- resource?.attributes,
3608
- `${resourcePath}.resource.attributes`
3609
- );
3610
- warnings.push(...resourceParsed.warnings);
3611
- unsupportedFields.push(...resourceParsed.unsupportedFields);
3612
- if (!Array.isArray(resourceSpan.scopeSpans)) {
3613
- unsupportedFields.push(`${resourcePath}.scopeSpans`);
3614
- warnings.push({
3615
- code: "otlp_scope_spans_missing",
3616
- message: "OTLP resourceSpans entry did not contain a scopeSpans array.",
3617
- severity: "warning",
3618
- field: `${resourcePath}.scopeSpans`
3619
- });
3620
- continue;
3621
- }
3622
- for (const [scopeIndex, scopeSpan] of resourceSpan.scopeSpans.entries()) {
3623
- const scopePath = `${resourcePath}.scopeSpans[${scopeIndex}]`;
3624
- if (!isRecord6(scopeSpan)) {
3625
- unsupportedFields.push(scopePath);
3626
- continue;
3593
+ for (const reader of readers) {
3594
+ try {
3595
+ const candidate = await reader.detect(input);
3596
+ if (candidate !== void 0) {
3597
+ candidates.push(normalizeCandidate(reader, candidate));
3627
3598
  }
3628
- const scope = readRecordField(scopeSpan, "scope");
3629
- const scopeParsed = parseOtlpAttributes(
3630
- scope?.attributes,
3631
- `${scopePath}.scope.attributes`
3632
- );
3633
- warnings.push(...scopeParsed.warnings);
3634
- unsupportedFields.push(...scopeParsed.unsupportedFields);
3635
- if (!Array.isArray(scopeSpan.spans)) {
3636
- unsupportedFields.push(`${scopePath}.spans`);
3637
- warnings.push({
3638
- code: "otlp_spans_missing",
3639
- message: "OTLP scopeSpans entry did not contain a spans array.",
3640
- severity: "warning",
3641
- field: `${scopePath}.spans`
3642
- });
3599
+ } catch (error) {
3600
+ if (error instanceof TraceReadError) {
3601
+ warnings.push(...error.warnings);
3643
3602
  continue;
3644
3603
  }
3645
- for (const [spanIndex, span] of scopeSpan.spans.entries()) {
3646
- const spanPath = `${scopePath}.spans[${spanIndex}]`;
3647
- if (!looksLikeOtlpSpan(span)) {
3648
- unsupportedFields.push(spanPath);
3649
- warnings.push({
3650
- code: "otlp_invalid_span",
3651
- message: "Skipped OTLP span without required traceId, spanId, or name.",
3652
- severity: "warning",
3653
- field: spanPath
3654
- });
3655
- continue;
3656
- }
3657
- spans.push({
3658
- span,
3659
- resourceAttributes: resourceParsed.attributes,
3660
- scopeAttributes: scopeParsed.attributes,
3661
- scopeName: readStringField(scope ?? {}, ["name"]),
3662
- scopeVersion: readStringField(scope ?? {}, ["version"]),
3663
- pathPrefix: spanPath
3664
- });
3665
- }
3604
+ warnings.push({
3605
+ code: "reader_detect_failed",
3606
+ message: error instanceof Error && error.message.trim() !== "" ? error.message : `Trace reader "${reader.format}" failed during detection.`,
3607
+ severity: "warning"
3608
+ });
3666
3609
  }
3667
3610
  }
3668
- if (spans.length === 0) {
3669
- warnings.push({
3670
- code: "otlp_no_valid_spans",
3671
- message: "OTLP JSON payload did not contain any valid spans.",
3672
- severity: "error"
3673
- });
3611
+ const sorted = sortCandidates(
3612
+ candidates.filter((candidate) => candidate.confidence >= MIN_DETECTION_CONFIDENCE)
3613
+ );
3614
+ const candidateWarnings = collectWarnings(sorted);
3615
+ const lowConfidenceWarnings = candidates.length > sorted.length ? [
3616
+ {
3617
+ code: "low_confidence_candidates",
3618
+ message: `Ignored ${candidates.length - sorted.length} low-confidence format candidate(s).`,
3619
+ severity: "info"
3620
+ }
3621
+ ] : [];
3622
+ const allWarnings = dedupeWarnings([
3623
+ ...warnings,
3624
+ ...candidateWarnings,
3625
+ ...lowConfidenceWarnings
3626
+ ]);
3627
+ if (sorted.length === 0) {
3674
3628
  return {
3675
- spans,
3676
- confidence: 0.7,
3677
- description: "Malformed OTLP JSON trace payload",
3678
- warnings,
3679
- unsupportedFields
3629
+ status: "unsupported",
3630
+ candidates: [],
3631
+ warnings: allWarnings
3680
3632
  };
3681
3633
  }
3682
- return {
3683
- spans,
3684
- confidence: 0.93,
3685
- description: "OTLP JSON trace payload",
3686
- warnings,
3687
- unsupportedFields
3688
- };
3689
- }
3690
- function mapOtlpStatus(status) {
3691
- if (!isRecord6(status)) return void 0;
3692
- const rawCode = status.code;
3693
- if (typeof rawCode !== "string") return void 0;
3694
- switch (rawCode.toUpperCase()) {
3695
- case "STATUS_CODE_OK":
3696
- case "OK":
3697
- return "ok";
3698
- case "STATUS_CODE_ERROR":
3699
- case "ERROR":
3700
- return "error";
3701
- case "STATUS_CODE_UNSET":
3702
- case "UNSET":
3703
- return "unknown";
3704
- default:
3705
- return "unknown";
3706
- }
3707
- }
3708
- function readOtlpKind(attributes, pathPrefix) {
3709
- const warnings = [];
3710
- const agentInspectKind = attributes["agent_inspect.kind"];
3711
- 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") {
3712
- return { kind: agentInspectKind, warnings };
3713
- }
3714
- const operation = attributes["gen_ai.operation.name"];
3715
- if (typeof operation === "string") {
3716
- switch (operation) {
3717
- case "generate_content":
3718
- case "chat":
3719
- return { kind: "LLM", warnings };
3720
- case "execute_tool":
3721
- return { kind: "TOOL", warnings };
3722
- case "invoke_agent":
3723
- return { kind: "AGENT", warnings };
3724
- default:
3725
- warnings.push({
3726
- code: "otlp_gen_ai_operation_semantic_loss",
3727
- message: `OTLP GenAI operation "${operation}" mapped to AgentInspect LOGIC.`,
3728
- severity: "warning",
3729
- field: `${pathPrefix}.attributes.gen_ai.operation.name`
3730
- });
3731
- return { kind: "LOGIC", warnings };
3732
- }
3733
- }
3734
- warnings.push({
3735
- code: "otlp_kind_unknown",
3736
- message: "OTLP span had no AgentInspect kind or GenAI operation; mapped to LOGIC.",
3737
- severity: "warning",
3738
- field: `${pathPrefix}.attributes`
3739
- });
3740
- return { kind: "LOGIC", warnings };
3741
- }
3742
- function readOtlpTokenUsage(attributes) {
3743
- const input = attributes["gen_ai.usage.input_tokens"];
3744
- const output = attributes["gen_ai.usage.output_tokens"];
3745
- const usage = {};
3746
- if (typeof input === "number" && Number.isFinite(input) && input >= 0) {
3747
- usage.input = input;
3748
- }
3749
- if (typeof output === "number" && Number.isFinite(output) && output >= 0) {
3750
- usage.output = output;
3751
- }
3752
- if (usage.input !== void 0 && usage.output !== void 0) {
3753
- usage.total = usage.input + usage.output;
3634
+ const [best, second] = sorted;
3635
+ if (second !== void 0 && best.confidence - second.confidence <= AMBIGUOUS_CONFIDENCE_DELTA) {
3636
+ return {
3637
+ status: "ambiguous",
3638
+ candidates: sorted,
3639
+ warnings: [
3640
+ ...allWarnings,
3641
+ {
3642
+ code: "ambiguous_format_candidates",
3643
+ message: `Top trace format candidates are within ${AMBIGUOUS_CONFIDENCE_DELTA} confidence.`,
3644
+ severity: "warning"
3645
+ }
3646
+ ]
3647
+ };
3754
3648
  }
3755
- return Object.keys(usage).length > 0 ? usage : void 0;
3756
- }
3757
- function readOtlpConfidence(attributes) {
3758
- return readOpenInferenceConfidence(attributes);
3759
- }
3760
- function sanitizeOtlpAttributes(attributes, pathPrefix) {
3761
- const ownerPath = pathPrefix.endsWith(".attributes") ? pathPrefix.slice(0, -".attributes".length) : pathPrefix;
3762
- const sanitized = sanitizeOpenInferenceAttributes(attributes, ownerPath);
3763
3649
  return {
3764
- ...sanitized,
3765
- warnings: sanitized.warnings.map(
3766
- (warning) => warning.code === "openinference_sensitive_attribute_summarized" ? {
3767
- ...warning,
3768
- code: "otlp_sensitive_attribute_summarized",
3769
- message: "OTLP prompt/output/document attribute(s) were summarized instead of copied verbatim."
3770
- } : warning
3771
- )
3650
+ status: "detected",
3651
+ format: best.format,
3652
+ candidates: sorted,
3653
+ warnings: allWarnings
3772
3654
  };
3773
3655
  }
3774
- function mapOtlpEvents(value, pathPrefix) {
3775
- const warnings = [];
3776
- const unsupportedFields = [];
3777
- if (value === void 0) return { warnings, unsupportedFields };
3778
- if (!Array.isArray(value)) {
3779
- unsupportedFields.push(pathPrefix);
3780
- warnings.push({
3781
- code: "otlp_events_invalid",
3782
- message: "OTLP events field was not an array.",
3783
- severity: "warning",
3784
- field: pathPrefix
3785
- });
3786
- return { warnings, unsupportedFields };
3656
+ async function readTrace(input, options = {}) {
3657
+ const readers = options.readers ?? DEFAULT_TRACE_READERS;
3658
+ const detection = await detectTraceFormat(input, options);
3659
+ if (detection.status === "unsupported" || detection.format === void 0) {
3660
+ throw new TraceReadError(
3661
+ "unsupported_format",
3662
+ "No trace reader could detect the input format.",
3663
+ detection.warnings
3664
+ );
3787
3665
  }
3788
- const events = [];
3789
- for (const [index, event] of value.entries()) {
3790
- const eventPath = `${pathPrefix}[${index}]`;
3791
- if (!isRecord6(event)) {
3792
- unsupportedFields.push(eventPath);
3793
- continue;
3794
- }
3795
- const parsedAttributes = parseOtlpAttributes(
3796
- event.attributes,
3797
- `${eventPath}.attributes`
3666
+ if (detection.status === "ambiguous") {
3667
+ throw new TraceReadError(
3668
+ "ambiguous_format",
3669
+ "Multiple trace readers matched the input with equal confidence.",
3670
+ detection.warnings
3798
3671
  );
3799
- warnings.push(...parsedAttributes.warnings);
3800
- unsupportedFields.push(...parsedAttributes.unsupportedFields);
3801
- const sanitized = sanitizeOtlpAttributes(
3802
- parsedAttributes.attributes,
3803
- `${eventPath}.attributes`
3672
+ }
3673
+ const reader = findReaderByFormat(detection.format, readers);
3674
+ if (!reader) {
3675
+ throw new TraceReadError(
3676
+ "unsupported_format",
3677
+ `No trace reader is registered for format "${detection.format}".`,
3678
+ detection.warnings
3804
3679
  );
3805
- warnings.push(...sanitized.warnings);
3806
- unsupportedFields.push(...sanitized.unsupportedFields);
3807
- const out = {};
3808
- const name = readStringField(event, ["name"]);
3809
- if (name !== void 0) {
3810
- out.name = name;
3811
- }
3812
- const timestamp = parseUnixNanoToIso(event.timeUnixNano);
3813
- if (timestamp !== void 0) {
3814
- out.timestamp = timestamp;
3815
- } else if (event.timeUnixNano !== void 0) {
3816
- unsupportedFields.push(`${eventPath}.timeUnixNano`);
3817
- warnings.push({
3818
- code: "otlp_event_timestamp_invalid",
3819
- message: "OTLP event timeUnixNano could not be parsed.",
3820
- severity: "warning",
3821
- field: `${eventPath}.timeUnixNano`
3822
- });
3823
- }
3824
- if (Object.keys(sanitized.attributes).length > 0) {
3825
- out.attributes = sanitized.attributes;
3680
+ }
3681
+ try {
3682
+ const result = await reader.read(input, { format: detection.format });
3683
+ return {
3684
+ ...result,
3685
+ format: result.format || detection.format,
3686
+ warnings: [...detection.warnings, ...result.warnings]
3687
+ };
3688
+ } catch (error) {
3689
+ if (error instanceof TraceReadError) {
3690
+ throw new TraceReadError(
3691
+ error.code,
3692
+ error.message,
3693
+ dedupeWarnings([...detection.warnings, ...error.warnings])
3694
+ );
3826
3695
  }
3827
- events.push(out);
3696
+ throw new TraceReadError(
3697
+ "reader_failed",
3698
+ error instanceof Error && error.message.trim() !== "" ? error.message : `Trace reader "${reader.format}" failed.`,
3699
+ detection.warnings
3700
+ );
3828
3701
  }
3829
- return {
3830
- events: events.length > 0 ? events : void 0,
3831
- warnings,
3832
- unsupportedFields
3833
- };
3834
3702
  }
3835
- function mapOtlpSpan(context) {
3836
- const { span, pathPrefix } = context;
3837
- const warnings = [];
3838
- const unsupportedFields = [];
3839
- const parsedSpanAttributes = parseOtlpAttributes(
3840
- span.attributes,
3841
- `${pathPrefix}.attributes`
3842
- );
3843
- warnings.push(...parsedSpanAttributes.warnings);
3844
- unsupportedFields.push(...parsedSpanAttributes.unsupportedFields);
3845
- const sanitizedSpanAttributes = sanitizeOtlpAttributes(
3846
- parsedSpanAttributes.attributes,
3847
- `${pathPrefix}.attributes`
3848
- );
3849
- warnings.push(...sanitizedSpanAttributes.warnings);
3850
- unsupportedFields.push(...sanitizedSpanAttributes.unsupportedFields);
3851
- const attributes = {
3852
- ...sanitizedSpanAttributes.attributes
3853
- };
3854
- for (const [key, value] of Object.entries(context.resourceAttributes)) {
3855
- attributes[`resource.${key}`] = value;
3856
- }
3857
- for (const [key, value] of Object.entries(context.scopeAttributes)) {
3858
- attributes[`scope.${key}`] = value;
3703
+ function openTrace(input, options = {}) {
3704
+ return readTrace(input, options);
3705
+ }
3706
+
3707
+ // packages/core/src/diff/comparable.ts
3708
+ function extractOutputPreview(meta) {
3709
+ if (meta === void 0) return void 0;
3710
+ if ("outputPreview" in meta) return meta.outputPreview;
3711
+ if ("resultPreview" in meta) return meta.resultPreview;
3712
+ return void 0;
3713
+ }
3714
+ function mapStepStatus(s) {
3715
+ if (s === void 0) return "running";
3716
+ return s;
3717
+ }
3718
+ function manualTraceEventsToComparableRun(events) {
3719
+ const started = events.find((e) => e.event === "run_started");
3720
+ if (!started || started.event !== "run_started") {
3721
+ throw new Error("Invalid trace: missing run_started");
3859
3722
  }
3860
- if (context.scopeName !== void 0) {
3861
- attributes["scope.name"] = context.scopeName;
3723
+ const rs = started;
3724
+ const runId = rs.runId;
3725
+ const completedAll = events.filter((e) => e.event === "run_completed");
3726
+ const lastCompleted = completedAll[completedAll.length - 1];
3727
+ let runStatus;
3728
+ if (lastCompleted === void 0) runStatus = "running";
3729
+ else runStatus = lastCompleted.status;
3730
+ const durationMs = lastCompleted !== void 0 && Number.isFinite(lastCompleted.durationMs) ? lastCompleted.durationMs : void 0;
3731
+ const steps = /* @__PURE__ */ new Map();
3732
+ let order = 0;
3733
+ for (const e of events) {
3734
+ if (e.event !== "step_started") continue;
3735
+ const s = e;
3736
+ const meta = s.metadata ? { ...s.metadata } : void 0;
3737
+ steps.set(s.stepId, {
3738
+ id: s.stepId,
3739
+ parentId: s.parentId,
3740
+ name: s.name,
3741
+ type: s.type,
3742
+ order: order++,
3743
+ timestamp: s.timestamp,
3744
+ metadata: meta
3745
+ });
3862
3746
  }
3863
- if (context.scopeVersion !== void 0) {
3864
- attributes["scope.version"] = context.scopeVersion;
3747
+ for (const e of events) {
3748
+ if (e.event !== "step_completed") continue;
3749
+ const acc = steps.get(e.stepId);
3750
+ if (!acc) continue;
3751
+ acc.status = e.status;
3752
+ acc.durationMs = e.durationMs;
3753
+ if (e.error?.message) acc.errorMsg = e.error.message;
3754
+ const extra = e;
3755
+ if (extra.metadata !== void 0 && typeof extra.metadata === "object") {
3756
+ acc.metadata = { ...acc.metadata ?? {}, ...extra.metadata };
3757
+ }
3865
3758
  }
3866
- for (const [key, value] of Object.entries(span)) {
3867
- if (OTLP_SPAN_KEYS.has(key)) continue;
3868
- unsupportedFields.push(`${pathPrefix}.${key}`);
3869
- if (value === null || typeof value !== "object") {
3870
- attributes[`otlp.${key}`] = value;
3871
- } else {
3872
- attributes[`otlp.${key}.summary`] = summarizeAttributeValue(value);
3873
- warnings.push({
3874
- code: "otlp_unsupported_field_summarized",
3875
- message: `Unsupported OTLP span field "${key}" was summarized.`,
3876
- severity: "warning",
3877
- field: `${pathPrefix}.${key}`
3878
- });
3759
+ const nodes = /* @__PURE__ */ new Map();
3760
+ for (const acc of steps.values()) {
3761
+ let meta = acc.metadata ? { ...acc.metadata } : void 0;
3762
+ if (acc.parentId !== void 0 && !steps.has(acc.parentId)) {
3763
+ meta = { ...meta ?? {}, agent_inspect_diff_parent_missing: true };
3879
3764
  }
3765
+ const outputPreview = extractOutputPreview(meta);
3766
+ const sc = {
3767
+ id: acc.id,
3768
+ name: acc.name,
3769
+ type: acc.type,
3770
+ status: mapStepStatus(acc.status),
3771
+ durationMs: acc.durationMs,
3772
+ error: acc.errorMsg,
3773
+ metadata: meta && Object.keys(meta).length > 0 ? meta : void 0,
3774
+ outputPreview,
3775
+ children: []
3776
+ };
3777
+ nodes.set(acc.id, sc);
3880
3778
  }
3881
- for (const key of [
3882
- "droppedAttributesCount",
3883
- "droppedEventsCount",
3884
- "droppedLinksCount",
3885
- "links"
3886
- ]) {
3887
- if (span[key] !== void 0) {
3888
- unsupportedFields.push(`${pathPrefix}.${key}`);
3889
- warnings.push({
3890
- code: "otlp_span_field_not_mapped",
3891
- message: `OTLP span field "${key}" is not represented in AgentInspect events.`,
3892
- severity: "warning",
3893
- field: `${pathPrefix}.${key}`
3894
- });
3779
+ const roots = [];
3780
+ const sortByOrder = (a, b) => {
3781
+ const oa = steps.get(a.id)?.order ?? 0;
3782
+ const ob = steps.get(b.id)?.order ?? 0;
3783
+ return oa - ob;
3784
+ };
3785
+ for (const acc of steps.values()) {
3786
+ const node = nodes.get(acc.id);
3787
+ if (acc.parentId !== void 0 && nodes.has(acc.parentId)) {
3788
+ nodes.get(acc.parentId).children.push(node);
3789
+ } else {
3790
+ roots.push(node);
3895
3791
  }
3896
3792
  }
3897
- const events = mapOtlpEvents(span.events, `${pathPrefix}.events`);
3898
- warnings.push(...events.warnings);
3899
- unsupportedFields.push(...events.unsupportedFields);
3900
- if (events.events !== void 0) {
3901
- attributes["otlp.events"] = events.events;
3902
- }
3903
- const traceId = readStringField(span, ["traceId"]) ?? "trace-unknown";
3904
- const spanId = readStringField(span, ["spanId"]) ?? "span-unknown";
3905
- const parentSpanId = readStringField(span, ["parentSpanId"]);
3906
- const startedAt = readOpenInferenceTimestamp(
3907
- span,
3908
- ["startTimeUnixNano"],
3909
- []
3910
- );
3911
- const endedAt = readOpenInferenceTimestamp(span, ["endTimeUnixNano"], []);
3912
- const timestamp = startedAt ?? "1970-01-01T00:00:00.000Z";
3913
- if (startedAt === void 0) {
3914
- unsupportedFields.push(`${pathPrefix}.startTimeUnixNano`);
3915
- warnings.push({
3916
- code: "otlp_missing_start_time",
3917
- message: "OTLP span is missing a valid startTimeUnixNano; using Unix epoch.",
3918
- severity: "warning",
3919
- field: `${pathPrefix}.startTimeUnixNano`
3920
- });
3793
+ roots.sort(sortByOrder);
3794
+ for (const n of nodes.values()) {
3795
+ n.children.sort(sortByOrder);
3921
3796
  }
3922
- const { kind, warnings: kindWarnings } = readOtlpKind(
3923
- parsedSpanAttributes.attributes,
3924
- pathPrefix
3925
- );
3926
- warnings.push(...kindWarnings);
3927
- const status = mapOtlpStatus(span.status);
3928
- const tokenUsage = readOtlpTokenUsage(parsedSpanAttributes.attributes);
3929
- const errorMessage = isRecord6(span.status) && typeof span.status.message === "string" ? span.status.message : void 0;
3930
- const event = {
3931
- schemaVersion: "0.2",
3932
- eventId: typeof parsedSpanAttributes.attributes["agent_inspect.event_id"] === "string" ? parsedSpanAttributes.attributes["agent_inspect.event_id"] : spanId,
3933
- runId: typeof parsedSpanAttributes.attributes["agent_inspect.run_id"] === "string" ? parsedSpanAttributes.attributes["agent_inspect.run_id"] : traceId,
3934
- kind,
3935
- name: readStringField(span, ["name"]) ?? spanId,
3936
- timestamp,
3937
- confidence: readOtlpConfidence(parsedSpanAttributes.attributes),
3938
- source: {
3939
- type: "otel",
3940
- name: context.scopeName ?? (typeof context.resourceAttributes["service.name"] === "string" ? context.resourceAttributes["service.name"] : "otlp-json"),
3941
- ...context.scopeVersion !== void 0 ? { version: context.scopeVersion } : {}
3942
- },
3943
- attributes,
3944
- trace: {
3945
- traceId,
3946
- spanId,
3947
- ...parentSpanId !== void 0 ? { parentSpanId } : {}
3948
- }
3797
+ return {
3798
+ runId,
3799
+ name: rs.name,
3800
+ status: runStatus,
3801
+ durationMs,
3802
+ steps: roots
3949
3803
  };
3950
- if (status !== void 0) {
3951
- event.status = status;
3804
+ }
3805
+
3806
+ // packages/core/src/exporters/helpers.ts
3807
+ var REDACT_SUBSTRINGS = [
3808
+ "authorization",
3809
+ "cookie",
3810
+ "token",
3811
+ "apikey",
3812
+ "password",
3813
+ "secret",
3814
+ "email"
3815
+ ];
3816
+ function shouldRedactKey(key) {
3817
+ const k = key.toLowerCase();
3818
+ for (const s of REDACT_SUBSTRINGS) {
3819
+ if (k.includes(s)) return true;
3952
3820
  }
3953
- if (startedAt !== void 0) {
3954
- event.startedAt = startedAt;
3821
+ return false;
3822
+ }
3823
+ function safeString(value, maxLength) {
3824
+ if (value === null || value === void 0) return "";
3825
+ let s;
3826
+ if (typeof value === "string") s = value;
3827
+ else if (typeof value === "number" || typeof value === "boolean") s = String(value);
3828
+ else s = stableJson(value, false);
3829
+ if (maxLength !== void 0 && maxLength >= 0 && s.length > maxLength) {
3830
+ return `${s.slice(0, maxLength)}\u2026`;
3955
3831
  }
3956
- if (endedAt !== void 0) {
3957
- event.endedAt = endedAt;
3832
+ return s;
3833
+ }
3834
+ function escapeMarkdown(value) {
3835
+ return value.replace(/\|/g, "\\|").replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\n/g, " ");
3836
+ }
3837
+ function sortKeysDeep(input) {
3838
+ if (input === null || typeof input !== "object") return input;
3839
+ if (Array.isArray(input)) return input.map(sortKeysDeep);
3840
+ const o = input;
3841
+ const out = {};
3842
+ for (const k of Object.keys(o).sort()) {
3843
+ out[k] = sortKeysDeep(o[k]);
3958
3844
  }
3959
- const durationMs = durationBetweenIso(startedAt, endedAt);
3960
- if (durationMs !== void 0) {
3961
- event.durationMs = durationMs;
3845
+ return out;
3846
+ }
3847
+ function stableJson(value, pretty) {
3848
+ const sorted = sortKeysDeep(value);
3849
+ return pretty === true ? JSON.stringify(sorted, null, 2) : JSON.stringify(sorted);
3850
+ }
3851
+ function compactAttributes3(attrs, options) {
3852
+ if (attrs === void 0) return {};
3853
+ const maxLen = options?.maxLength ?? 500;
3854
+ const out = {};
3855
+ for (const key of Object.keys(attrs).sort()) {
3856
+ if (shouldRedactKey(key)) {
3857
+ out[key] = "[REDACTED]";
3858
+ continue;
3859
+ }
3860
+ const v = attrs[key];
3861
+ out[key] = compactValue(v, maxLen);
3962
3862
  }
3963
- if (tokenUsage !== void 0) {
3964
- event.tokenUsage = tokenUsage;
3863
+ return out;
3864
+ }
3865
+ function compactValue(value, maxLen, redacted) {
3866
+ if (value === null || typeof value !== "object") {
3867
+ return typeof value === "string" ? safeString(value, maxLen) : value;
3965
3868
  }
3966
- if (status === "error") {
3967
- event.error = {
3968
- message: errorMessage !== void 0 && errorMessage.trim() !== "" ? errorMessage : "OTLP span error"
3969
- };
3869
+ if (Array.isArray(value)) {
3870
+ const arr = value.slice(0, 20).map((x) => compactValue(x, maxLen));
3871
+ if (value.length > 20) arr.push(`\u2026(+${value.length - 20} more)`);
3872
+ return arr;
3970
3873
  }
3971
- return {
3972
- event,
3973
- warnings,
3974
- unsupportedFields,
3975
- spanId,
3976
- ...parentSpanId !== void 0 ? { parentSpanId } : {}
3977
- };
3874
+ const o = value;
3875
+ const inner = {};
3876
+ for (const k of Object.keys(o)) {
3877
+ if (shouldRedactKey(k)) inner[k] = "[REDACTED]";
3878
+ else inner[k] = compactValue(o[k], maxLen);
3879
+ }
3880
+ return inner;
3978
3881
  }
3979
- function mapOtlpEventsToPersisted(document) {
3980
- const mapped = document.spans.map((span) => mapOtlpSpan(span));
3981
- const spanIdToEventId = new Map(
3982
- mapped.map((span) => [span.spanId, span.event.eventId])
3983
- );
3984
- for (const span of mapped) {
3985
- if (span.parentSpanId === void 0) continue;
3986
- span.event.parentId = spanIdToEventId.get(span.parentSpanId) ?? span.parentSpanId;
3882
+ function flattenTree(tree) {
3883
+ const out = [];
3884
+ function walk(nodes) {
3885
+ for (const n of nodes) {
3886
+ out.push(n);
3887
+ if (n.children.length > 0) walk(n.children);
3888
+ }
3987
3889
  }
3988
- return {
3989
- events: mapped.map((span) => span.event),
3990
- warnings: mapped.flatMap((span) => span.warnings),
3991
- unsupportedFields: mapped.flatMap((span) => span.unsupportedFields)
3992
- };
3890
+ walk(tree.children);
3891
+ return out;
3993
3892
  }
3994
- var otlpJsonReader = {
3995
- format: OTLP_READER_FORMAT,
3996
- name: "OTLP JSON",
3997
- async detect(input) {
3998
- const resolved = await resolveInput(input);
3999
- if (!resolved) return void 0;
4000
- let parsed;
4001
- try {
4002
- parsed = parseJsonDocument(resolved.content);
4003
- } catch {
4004
- return void 0;
3893
+
3894
+ // packages/core/src/diff/engine.ts
3895
+ var DEFAULT_THRESHOLD_MS = 0;
3896
+ function pathSeg(step, index) {
3897
+ return { index, name: step.name, stepId: step.id };
3898
+ }
3899
+ function buildPath(segments) {
3900
+ return { path: [...segments] };
3901
+ }
3902
+ function pairSteps(left, right) {
3903
+ const usedRight = /* @__PURE__ */ new Set();
3904
+ const pairs = [];
3905
+ for (let i = 0; i < left.length; i++) {
3906
+ const L = left[i];
3907
+ let R = right.find((r) => !usedRight.has(r.id) && r.id === L.id);
3908
+ if (R === void 0 && i < right.length && !usedRight.has(right[i].id)) {
3909
+ const cand = right[i];
3910
+ if (cand.name === L.name && (cand.type ?? "") === (L.type ?? "")) {
3911
+ R = cand;
3912
+ }
4005
3913
  }
4006
- const document = extractOtlpDocument(parsed);
4007
- if (!document) return void 0;
4008
- return {
4009
- format: OTLP_READER_FORMAT,
4010
- confidence: document.confidence,
4011
- readerName: "OTLP JSON",
4012
- description: document.description,
4013
- warnings: attachSingleSourceFile(document.warnings, resolved)
4014
- };
4015
- },
4016
- async read(input) {
4017
- const resolved = await resolveInput(input);
4018
- if (!resolved) {
4019
- throw new TraceReadError(
4020
- "unsupported_format",
4021
- "OTLP JSON reader requires file, string, or buffer input."
3914
+ if (R === void 0) {
3915
+ R = right.find(
3916
+ (r) => !usedRight.has(r.id) && r.name === L.name && (r.type ?? "") === (L.type ?? "")
4022
3917
  );
4023
3918
  }
4024
- let parsed;
4025
- try {
4026
- parsed = parseJsonDocument(resolved.content);
4027
- } catch {
4028
- throw new TraceReadError("unsupported_format", "OTLP JSON input is not valid JSON.", [
4029
- {
4030
- code: "otlp_invalid_json",
4031
- message: "OTLP JSON reader could not parse the input as JSON.",
4032
- severity: "error"
4033
- }
4034
- ]);
4035
- }
4036
- const document = extractOtlpDocument(parsed);
4037
- if (!document || document.spans.length === 0) {
4038
- throw new TraceReadError(
4039
- "unsupported_format",
4040
- "No valid OTLP spans found.",
4041
- attachSingleSourceFile(
4042
- document?.warnings ?? [
4043
- {
4044
- code: "otlp_no_valid_spans",
4045
- message: "OTLP JSON input did not contain valid spans.",
4046
- severity: "error"
4047
- }
4048
- ],
4049
- resolved
4050
- )
4051
- );
3919
+ if (R !== void 0) {
3920
+ usedRight.add(R.id);
3921
+ pairs.push([L, R]);
3922
+ } else {
3923
+ pairs.push([L, void 0]);
4052
3924
  }
4053
- const mapped = mapOtlpEventsToPersisted(document);
4054
- const warnings = attachSingleSourceFile(
4055
- [...document.warnings, ...mapped.warnings],
4056
- resolved
4057
- );
4058
- const unsupportedFields = [
4059
- ...document.unsupportedFields,
4060
- ...mapped.unsupportedFields
4061
- ].sort((a, b) => a.localeCompare(b));
4062
- return {
4063
- format: OTLP_READER_FORMAT,
4064
- events: mapped.events,
4065
- runs: persistedInspectEventsToRunTrees(mapped.events, { skipInvalid: true }),
4066
- warnings,
4067
- unsupportedFields,
4068
- sourceFiles: resolved.sourceFiles
4069
- };
4070
3925
  }
4071
- };
4072
- var agentInspectJsonlReader = {
4073
- format: "agent-inspect-jsonl",
4074
- name: "AgentInspect JSONL",
4075
- async detect(input) {
4076
- const resolved = await resolveInput(input);
4077
- if (!resolved) return void 0;
4078
- const detected = detectJsonlFormat(resolved.content);
4079
- if (detected.validRows === 0 || detected.format === "empty") {
4080
- return void 0;
4081
- }
4082
- return {
4083
- format: "agent-inspect-jsonl",
4084
- confidence: 0.95,
4085
- readerName: "AgentInspect JSONL",
4086
- description: agentInspectFormatLabel(detected.format),
4087
- warnings: attachSingleSourceFile(detected.warnings, resolved)
4088
- };
4089
- },
4090
- async read(input) {
4091
- const resolved = await resolveInput(input);
4092
- if (!resolved) {
4093
- throw new Error("AgentInspect JSONL reader requires file, directory, string, or buffer input.");
3926
+ for (const R of right) {
3927
+ if (!usedRight.has(R.id)) {
3928
+ pairs.push([void 0, R]);
4094
3929
  }
4095
- const parsed = parseTraceJsonl(resolved.content, { warnings: false });
4096
- if (parsed.sourceEventCount === 0) {
4097
- throw new Error("No valid AgentInspect JSONL events found.");
3930
+ }
3931
+ return pairs;
3932
+ }
3933
+ function compareLeafSteps(L, R, segments, opts, out) {
3934
+ const path12 = buildPath(segments);
3935
+ if (L.name !== R.name) {
3936
+ out.push({
3937
+ kind: "structure",
3938
+ severity: "warning",
3939
+ message: "Step name differs",
3940
+ path: path12,
3941
+ left: L.name,
3942
+ right: R.name
3943
+ });
3944
+ }
3945
+ if ((L.type ?? "") !== (R.type ?? "")) {
3946
+ out.push({
3947
+ kind: "step-type",
3948
+ severity: "warning",
3949
+ message: "Step type differs",
3950
+ path: path12,
3951
+ left: L.type,
3952
+ right: R.type
3953
+ });
3954
+ }
3955
+ if ((L.status ?? "") !== (R.status ?? "")) {
3956
+ out.push({
3957
+ kind: "step-status",
3958
+ severity: "warning",
3959
+ message: "Step status differs",
3960
+ path: path12,
3961
+ left: L.status,
3962
+ right: R.status
3963
+ });
3964
+ }
3965
+ const le = L.error ?? "";
3966
+ const re = R.error ?? "";
3967
+ if (le !== re) {
3968
+ out.push({
3969
+ kind: "error",
3970
+ severity: "error",
3971
+ message: "Step error message differs",
3972
+ path: path12,
3973
+ left: le || void 0,
3974
+ right: re || void 0
3975
+ });
3976
+ }
3977
+ if (!opts.ignoreDuration) {
3978
+ const ld = L.durationMs;
3979
+ const rd = R.durationMs;
3980
+ const th = opts.durationThresholdMs;
3981
+ let differs = false;
3982
+ if (ld === void 0 && rd === void 0) differs = false;
3983
+ else if (ld === void 0 || rd === void 0) differs = true;
3984
+ else differs = Math.abs(ld - rd) > th;
3985
+ if (differs) {
3986
+ out.push({
3987
+ kind: "duration",
3988
+ severity: "info",
3989
+ message: "Step duration differs",
3990
+ path: path12,
3991
+ left: ld,
3992
+ right: rd
3993
+ });
4098
3994
  }
4099
- const events = persistedEventsForParsedTrace(parsed);
4100
- return {
4101
- format: agentInspectFormatLabel(parsed.format),
4102
- events,
4103
- runs: persistedInspectEventsToRunTrees(events, { skipInvalid: true }),
4104
- warnings: parsed.format === "mixed" ? attachSingleSourceFile(
4105
- [
4106
- {
4107
- code: "mixed_agent_inspect_jsonl",
4108
- message: "Trace input mixes schemaVersion 0.1 and 0.2 rows; events were normalized for reading.",
4109
- severity: "warning"
4110
- }
4111
- ],
4112
- resolved
4113
- ) : [],
4114
- unsupportedFields: [],
4115
- sourceFiles: resolved.sourceFiles
4116
- };
4117
3995
  }
4118
- };
4119
- var DEFAULT_TRACE_READERS = [
4120
- agentInspectJsonlReader,
4121
- openInferenceJsonReader,
4122
- otlpJsonReader
4123
- ];
4124
- async function detectTraceFormat(input, options = {}) {
4125
- const readers = options.readers ?? DEFAULT_TRACE_READERS;
4126
- if (options.format !== void 0) {
4127
- const reader = findReaderByFormat(options.format, readers);
4128
- if (!reader) {
4129
- return {
4130
- status: "unsupported",
4131
- candidates: [],
4132
- warnings: [
4133
- {
4134
- code: "unsupported_format",
4135
- message: `No trace reader is registered for format "${options.format}".`,
4136
- severity: "error"
4137
- }
4138
- ]
4139
- };
3996
+ const lm = stableJson(L.metadata ?? {});
3997
+ const rm = stableJson(R.metadata ?? {});
3998
+ if (lm !== rm) {
3999
+ out.push({
4000
+ kind: "metadata",
4001
+ severity: "info",
4002
+ message: "Step metadata differs",
4003
+ path: path12,
4004
+ left: L.metadata,
4005
+ right: R.metadata
4006
+ });
4007
+ }
4008
+ const lo = stableJson(L.outputPreview ?? null);
4009
+ const ro = stableJson(R.outputPreview ?? null);
4010
+ if (lo !== ro) {
4011
+ out.push({
4012
+ kind: "output",
4013
+ severity: "info",
4014
+ message: "Output preview differs",
4015
+ path: path12,
4016
+ left: L.outputPreview,
4017
+ right: R.outputPreview
4018
+ });
4019
+ }
4020
+ }
4021
+ function compareRecursive(L, R, segments, opts, out) {
4022
+ compareLeafSteps(L, R, segments, opts, out);
4023
+ const pairs = pairSteps(L.children, R.children);
4024
+ let ci = 0;
4025
+ for (const [lch, rch] of pairs) {
4026
+ if (lch !== void 0 && rch !== void 0) {
4027
+ compareRecursive(lch, rch, [...segments, pathSeg(lch, ci)], opts, out);
4028
+ } else if (lch !== void 0) {
4029
+ out.push({
4030
+ kind: "step-removed",
4031
+ severity: "warning",
4032
+ message: `Step only in left run: ${lch.name}`,
4033
+ path: buildPath([...segments, pathSeg(lch, ci)]),
4034
+ left: lch.id,
4035
+ right: void 0
4036
+ });
4037
+ } else if (rch !== void 0) {
4038
+ out.push({
4039
+ kind: "step-added",
4040
+ severity: "warning",
4041
+ message: `Step only in right run: ${rch.name}`,
4042
+ path: buildPath([...segments, pathSeg(rch, ci)]),
4043
+ left: void 0,
4044
+ right: rch.id
4045
+ });
4140
4046
  }
4141
- return {
4142
- status: "detected",
4143
- format: reader.format,
4144
- candidates: [
4145
- {
4146
- format: reader.format,
4147
- confidence: 1,
4148
- readerName: reader.name,
4149
- description: "Explicit format override"
4150
- }
4151
- ],
4152
- warnings: []
4153
- };
4047
+ ci += 1;
4154
4048
  }
4155
- const candidates = [];
4156
- const warnings = [];
4157
- for (const reader of readers) {
4158
- try {
4159
- const candidate = await reader.detect(input);
4160
- if (candidate !== void 0) {
4161
- candidates.push(normalizeCandidate(reader, candidate));
4162
- }
4163
- } catch (error) {
4164
- if (error instanceof TraceReadError) {
4165
- warnings.push(...error.warnings);
4166
- continue;
4167
- }
4168
- warnings.push({
4169
- code: "reader_detect_failed",
4170
- message: error instanceof Error && error.message.trim() !== "" ? error.message : `Trace reader "${reader.format}" failed during detection.`,
4171
- severity: "warning"
4049
+ }
4050
+ function mergeDiffDefaults(options) {
4051
+ return {
4052
+ ignoreDuration: false,
4053
+ durationThresholdMs: DEFAULT_THRESHOLD_MS,
4054
+ focus: "all",
4055
+ check: "all"
4056
+ };
4057
+ }
4058
+ function kindMatchesFilter(kind, merged) {
4059
+ return true;
4060
+ }
4061
+ function diffRuns(left, right, options) {
4062
+ const merged = mergeDiffDefaults();
4063
+ const opts = {
4064
+ ignoreDuration: merged.ignoreDuration,
4065
+ durationThresholdMs: merged.durationThresholdMs
4066
+ };
4067
+ const raw = [];
4068
+ if ((left.status ?? "") !== (right.status ?? "")) {
4069
+ raw.push({
4070
+ kind: "run-status",
4071
+ severity: "warning",
4072
+ message: "Run completion status differs",
4073
+ left: left.status,
4074
+ right: right.status
4075
+ });
4076
+ }
4077
+ {
4078
+ const ld = left.durationMs;
4079
+ const rd = right.durationMs;
4080
+ const th = merged.durationThresholdMs;
4081
+ let differs = false;
4082
+ if (ld === void 0 && rd === void 0) differs = false;
4083
+ else if (ld === void 0 || rd === void 0) differs = true;
4084
+ else differs = Math.abs(ld - rd) > th;
4085
+ if (differs) {
4086
+ raw.push({
4087
+ kind: "duration",
4088
+ severity: "info",
4089
+ message: "Run duration differs",
4090
+ left: ld,
4091
+ right: rd
4172
4092
  });
4173
4093
  }
4174
4094
  }
4175
- const sorted = sortCandidates(
4176
- candidates.filter((candidate) => candidate.confidence >= MIN_DETECTION_CONFIDENCE)
4177
- );
4178
- const candidateWarnings = collectWarnings(sorted);
4179
- const lowConfidenceWarnings = candidates.length > sorted.length ? [
4180
- {
4181
- code: "low_confidence_candidates",
4182
- message: `Ignored ${candidates.length - sorted.length} low-confidence format candidate(s).`,
4183
- severity: "info"
4095
+ const pairs = pairSteps(left.steps, right.steps);
4096
+ let idx = 0;
4097
+ for (const [ls, rs] of pairs) {
4098
+ if (ls !== void 0 && rs !== void 0) {
4099
+ compareRecursive(ls, rs, [pathSeg(ls, idx)], opts, raw);
4100
+ idx += 1;
4101
+ } else if (ls !== void 0) {
4102
+ raw.push({
4103
+ kind: "step-removed",
4104
+ severity: "warning",
4105
+ message: `Step only in left run: ${ls.name}`,
4106
+ path: buildPath([pathSeg(ls, idx)]),
4107
+ left: ls.id,
4108
+ right: void 0
4109
+ });
4110
+ idx += 1;
4111
+ } else if (rs !== void 0) {
4112
+ raw.push({
4113
+ kind: "step-added",
4114
+ severity: "warning",
4115
+ message: `Step only in right run: ${rs.name}`,
4116
+ path: buildPath([pathSeg(rs, idx)]),
4117
+ left: void 0,
4118
+ right: rs.id
4119
+ });
4120
+ idx += 1;
4184
4121
  }
4185
- ] : [];
4186
- const allWarnings = dedupeWarnings([
4187
- ...warnings,
4188
- ...candidateWarnings,
4189
- ...lowConfidenceWarnings
4190
- ]);
4191
- if (sorted.length === 0) {
4192
- return {
4193
- status: "unsupported",
4194
- candidates: [],
4195
- warnings: allWarnings
4196
- };
4197
4122
  }
4198
- const [best, second] = sorted;
4199
- if (second !== void 0 && best.confidence - second.confidence <= AMBIGUOUS_CONFIDENCE_DELTA) {
4200
- return {
4201
- status: "ambiguous",
4202
- candidates: sorted,
4203
- warnings: [
4204
- ...allWarnings,
4205
- {
4206
- code: "ambiguous_format_candidates",
4207
- message: `Top trace format candidates are within ${AMBIGUOUS_CONFIDENCE_DELTA} confidence.`,
4208
- severity: "warning"
4209
- }
4210
- ]
4211
- };
4123
+ const differences = raw.filter((d) => kindMatchesFilter(d.kind));
4124
+ let errors = 0;
4125
+ let warnings = 0;
4126
+ let info = 0;
4127
+ for (const d of differences) {
4128
+ if (d.severity === "error") errors += 1;
4129
+ else if (d.severity === "warning") warnings += 1;
4130
+ else info += 1;
4212
4131
  }
4213
- return {
4214
- status: "detected",
4215
- format: best.format,
4216
- candidates: sorted,
4217
- warnings: allWarnings
4132
+ const firstVisible = differences[0];
4133
+ const firstDivergence = firstVisible !== void 0 ? {
4134
+ kind: "first-divergence",
4135
+ severity: firstVisible.severity,
4136
+ message: `First divergence: ${firstVisible.message}`,
4137
+ path: firstVisible.path,
4138
+ left: firstVisible.left,
4139
+ right: firstVisible.right
4140
+ } : void 0;
4141
+ const summary = {
4142
+ leftRunId: left.runId,
4143
+ rightRunId: right.runId,
4144
+ totalDifferences: differences.length,
4145
+ errors,
4146
+ warnings,
4147
+ info,
4148
+ firstDivergence
4218
4149
  };
4150
+ return { summary, differences };
4219
4151
  }
4220
- async function readTrace(input, options = {}) {
4221
- const readers = options.readers ?? DEFAULT_TRACE_READERS;
4222
- const detection = await detectTraceFormat(input, options);
4223
- if (detection.status === "unsupported" || detection.format === void 0) {
4224
- throw new TraceReadError(
4225
- "unsupported_format",
4226
- "No trace reader could detect the input format.",
4227
- detection.warnings
4228
- );
4152
+
4153
+ // packages/core/src/exporters/markdown-exporter.ts
4154
+ function renderTreeAscii(nodes, indent = "") {
4155
+ const lines = [];
4156
+ for (let i = 0; i < nodes.length; i++) {
4157
+ const n = nodes[i];
4158
+ const last = i === nodes.length - 1;
4159
+ const branch = last ? "\u2514\u2500 " : "\u251C\u2500 ";
4160
+ const ev = n.event;
4161
+ const status = ev.status ?? "?";
4162
+ const dur = ev.durationMs !== void 0 && Number.isFinite(ev.durationMs) ? `${ev.durationMs}ms` : "-";
4163
+ lines.push(`${indent}${branch}${escapeMarkdown(ev.name)} [${ev.kind}] ${status} (${dur})`);
4164
+ const nextIndent = indent + (last ? " " : "\u2502 ");
4165
+ if (n.children.length > 0) {
4166
+ const childStr = renderTreeAscii(n.children, nextIndent);
4167
+ if (childStr.length > 0) lines.push(childStr);
4168
+ }
4229
4169
  }
4230
- if (detection.status === "ambiguous") {
4231
- throw new TraceReadError(
4232
- "ambiguous_format",
4233
- "Multiple trace readers matched the input with equal confidence.",
4234
- detection.warnings
4170
+ return lines.join("\n");
4171
+ }
4172
+ function exportMarkdown(tree, options) {
4173
+ const warnings = [];
4174
+ const includeMetadata = options?.includeMetadata ?? true;
4175
+ const includeAttributes = options?.includeAttributes ?? false;
4176
+ const includeErrors = options?.includeErrors ?? true;
4177
+ const maxLen = options?.maxAttributeLength ?? 500;
4178
+ const titleName = tree.name ?? tree.runId;
4179
+ const lines = [];
4180
+ lines.push(`# AgentInspect Run: ${escapeMarkdown(titleName)}`);
4181
+ lines.push("");
4182
+ lines.push("Generated locally by AgentInspect. Review for sensitive data before sharing.");
4183
+ lines.push("");
4184
+ if (includeMetadata) {
4185
+ lines.push("## Summary");
4186
+ lines.push("");
4187
+ lines.push(`- **runId**: ${escapeMarkdown(tree.runId)}`);
4188
+ if (tree.name !== void 0) lines.push(`- **name**: ${escapeMarkdown(tree.name)}`);
4189
+ lines.push(`- **status**: ${escapeMarkdown(String(tree.status ?? "unknown"))}`);
4190
+ lines.push(
4191
+ `- **durationMs**: ${tree.durationMs !== void 0 ? escapeMarkdown(String(tree.durationMs)) : "-"}`
4235
4192
  );
4236
- }
4237
- const reader = findReaderByFormat(detection.format, readers);
4238
- if (!reader) {
4239
- throw new TraceReadError(
4240
- "unsupported_format",
4241
- `No trace reader is registered for format "${detection.format}".`,
4242
- detection.warnings
4193
+ lines.push(
4194
+ `- **startedAt**: ${tree.startedAt !== void 0 ? escapeMarkdown(String(tree.startedAt)) : "-"}`
4195
+ );
4196
+ lines.push(
4197
+ `- **endedAt**: ${tree.endedAt !== void 0 ? escapeMarkdown(String(tree.endedAt)) : "-"}`
4243
4198
  );
4199
+ lines.push(`- **totalEvents**: ${tree.metadata.totalEvents}`);
4200
+ lines.push("");
4201
+ lines.push("### Confidence breakdown");
4202
+ lines.push("");
4203
+ lines.push("| bucket | count |");
4204
+ lines.push("| --- | --- |");
4205
+ for (const k of Object.keys(tree.metadata.confidenceBreakdown).sort()) {
4206
+ const key = k;
4207
+ lines.push(
4208
+ `| ${escapeMarkdown(key)} | ${tree.metadata.confidenceBreakdown[key]} |`
4209
+ );
4210
+ }
4211
+ lines.push("");
4212
+ lines.push("### Kind breakdown");
4213
+ lines.push("");
4214
+ lines.push("| kind | count |");
4215
+ lines.push("| --- | --- |");
4216
+ for (const k of Object.keys(tree.metadata.kinds).sort()) {
4217
+ const key = k;
4218
+ const c = tree.metadata.kinds[key];
4219
+ if (c > 0) lines.push(`| ${escapeMarkdown(key)} | ${c} |`);
4220
+ }
4221
+ lines.push("");
4244
4222
  }
4245
- try {
4246
- const result = await reader.read(input, { format: detection.format });
4247
- return {
4248
- ...result,
4249
- format: result.format || detection.format,
4250
- warnings: [...detection.warnings, ...result.warnings]
4251
- };
4252
- } catch (error) {
4253
- if (error instanceof TraceReadError) {
4254
- throw new TraceReadError(
4255
- error.code,
4256
- error.message,
4257
- dedupeWarnings([...detection.warnings, ...error.warnings])
4223
+ lines.push("## Execution tree");
4224
+ lines.push("");
4225
+ lines.push("```text");
4226
+ lines.push(
4227
+ tree.children.length > 0 ? renderTreeAscii(tree.children) : "(no steps)"
4228
+ );
4229
+ lines.push("```");
4230
+ lines.push("");
4231
+ const flat = flattenTree(tree);
4232
+ const errors = flat.filter((n) => n.event.status === "error");
4233
+ if (includeErrors && errors.length > 0) {
4234
+ lines.push("## Errors");
4235
+ lines.push("");
4236
+ for (const n of errors) {
4237
+ const msg = n.event.attributes && typeof n.event.attributes.error === "object" ? safeString(
4238
+ n.event.attributes.error.message,
4239
+ maxLen
4240
+ ) : "";
4241
+ lines.push(
4242
+ `- **${escapeMarkdown(n.event.name)}** (${escapeMarkdown(n.event.eventId)}): ${escapeMarkdown(msg || "error")}`
4258
4243
  );
4259
4244
  }
4260
- throw new TraceReadError(
4261
- "reader_failed",
4262
- error instanceof Error && error.message.trim() !== "" ? error.message : `Trace reader "${reader.format}" failed.`,
4263
- detection.warnings
4245
+ lines.push("");
4246
+ }
4247
+ if (includeAttributes) {
4248
+ lines.push("## Attributes (bounded)");
4249
+ lines.push("");
4250
+ for (const n of flat) {
4251
+ if (!n.event.attributes || Object.keys(n.event.attributes).length === 0) continue;
4252
+ const compact = compactAttributes3(n.event.attributes, {
4253
+ maxLength: maxLen});
4254
+ lines.push(`### ${escapeMarkdown(n.event.name)}`);
4255
+ lines.push("");
4256
+ lines.push("```json");
4257
+ lines.push(stableJson(compact, true));
4258
+ lines.push("```");
4259
+ lines.push("");
4260
+ }
4261
+ warnings.push(
4262
+ "Attributes may still contain sensitive data; review exports before sharing."
4264
4263
  );
4265
4264
  }
4266
- }
4267
- function openTrace(input, options = {}) {
4268
- return readTrace(input, options);
4265
+ return {
4266
+ format: "markdown",
4267
+ content: lines.join("\n"),
4268
+ contentType: "text/markdown",
4269
+ fileExtension: ".md",
4270
+ warnings
4271
+ };
4269
4272
  }
4270
4273
 
4271
4274
  // packages/mcp-server/src/tools.ts