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