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