@mtharrison/loupe 1.3.0 → 1.4.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/README.md +2 -0
- package/dist/client/app.css +115 -20
- package/dist/client/app.js +171 -112
- package/dist/session-nav.d.ts +1 -1
- package/dist/session-nav.js +8 -1
- package/dist/store.d.ts +1 -0
- package/dist/store.js +84 -39
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
# @mtharrison/loupe
|
|
6
6
|
|
|
7
|
+
|
|
8
|
+
|
|
7
9
|
Loupe is a lightweight local tracing dashboard for LLM applications and agent systems. It captures full request and response payloads with tags and hierarchy context, then serves an inspector UI on `127.0.0.1` with no database, no containers, and no persistence.
|
|
8
10
|
|
|
9
11
|
This package is for local development. Traces live in memory and are cleared on restart.
|
package/dist/client/app.css
CHANGED
|
@@ -402,7 +402,7 @@ pre {
|
|
|
402
402
|
.workspace-grid {
|
|
403
403
|
display: grid;
|
|
404
404
|
flex: 1;
|
|
405
|
-
grid-template-columns:
|
|
405
|
+
grid-template-columns: clamp(34rem, 38vw, 42rem) minmax(0, 1fr);
|
|
406
406
|
gap: 0.9rem;
|
|
407
407
|
align-items: stretch;
|
|
408
408
|
min-height: 0;
|
|
@@ -493,7 +493,7 @@ pre {
|
|
|
493
493
|
min-height: 0;
|
|
494
494
|
flex: 1;
|
|
495
495
|
overflow: auto;
|
|
496
|
-
padding-right: 0.
|
|
496
|
+
padding-right: 0.4rem;
|
|
497
497
|
scrollbar-gutter: stable;
|
|
498
498
|
}
|
|
499
499
|
.session-sidebar-empty {
|
|
@@ -1189,7 +1189,12 @@ pre {
|
|
|
1189
1189
|
--timeline-indent: 1rem;
|
|
1190
1190
|
--timeline-gutter-base: 1.25rem;
|
|
1191
1191
|
--timeline-connector-base: 0.22rem;
|
|
1192
|
+
--timeline-row-padding-x: 0.55rem;
|
|
1193
|
+
--timeline-card-radius: 12px;
|
|
1194
|
+
--timeline-card-inset-y: 0.14rem;
|
|
1192
1195
|
--timeline-gutter-width: calc( var(--timeline-depth, 0) * var(--timeline-indent) + var(--timeline-gutter-base) );
|
|
1196
|
+
--timeline-card-left: calc( var(--timeline-row-padding-x) + var(--timeline-time-column) + var(--timeline-column-gap) + var(--timeline-gutter-width) );
|
|
1197
|
+
--timeline-card-right: var(--timeline-row-padding-x);
|
|
1193
1198
|
--timeline-row-color: rgba(92, 121, 171, 0.9);
|
|
1194
1199
|
--timeline-bar-stroke: color-mix(in srgb, var(--timeline-row-color) 74%, white);
|
|
1195
1200
|
--timeline-connector-color: color-mix( in srgb, var(--timeline-row-color) 28%, rgba(84, 100, 125, 0.18) );
|
|
@@ -1201,7 +1206,7 @@ pre {
|
|
|
1201
1206
|
width: 100%;
|
|
1202
1207
|
border: 0;
|
|
1203
1208
|
background: transparent;
|
|
1204
|
-
padding: 0.12rem
|
|
1209
|
+
padding: 0.12rem var(--timeline-row-padding-x);
|
|
1205
1210
|
color: inherit;
|
|
1206
1211
|
cursor: default;
|
|
1207
1212
|
font: inherit;
|
|
@@ -1213,11 +1218,11 @@ pre {
|
|
|
1213
1218
|
.hierarchy-timeline-row::before {
|
|
1214
1219
|
content: "";
|
|
1215
1220
|
position: absolute;
|
|
1216
|
-
top:
|
|
1217
|
-
right:
|
|
1218
|
-
bottom:
|
|
1219
|
-
left:
|
|
1220
|
-
border-radius:
|
|
1221
|
+
top: var(--timeline-card-inset-y);
|
|
1222
|
+
right: var(--timeline-card-right);
|
|
1223
|
+
bottom: var(--timeline-card-inset-y);
|
|
1224
|
+
left: var(--timeline-card-left);
|
|
1225
|
+
border-radius: var(--timeline-card-radius);
|
|
1221
1226
|
border: 1px solid transparent;
|
|
1222
1227
|
background: rgba(255, 255, 255, 0.42);
|
|
1223
1228
|
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.6), 0 4px 12px rgba(32, 50, 76, 0.03);
|
|
@@ -1248,8 +1253,18 @@ pre {
|
|
|
1248
1253
|
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.6), 0 4px 12px rgba(32, 50, 76, 0.02);
|
|
1249
1254
|
}
|
|
1250
1255
|
.hierarchy-timeline-row:focus-visible {
|
|
1251
|
-
outline:
|
|
1252
|
-
|
|
1256
|
+
outline: none;
|
|
1257
|
+
}
|
|
1258
|
+
.hierarchy-timeline-row:focus-visible::after {
|
|
1259
|
+
content: "";
|
|
1260
|
+
position: absolute;
|
|
1261
|
+
top: calc(var(--timeline-card-inset-y) - 0.08rem);
|
|
1262
|
+
right: calc(var(--timeline-card-right) - 0.08rem);
|
|
1263
|
+
bottom: calc(var(--timeline-card-inset-y) - 0.08rem);
|
|
1264
|
+
left: calc(var(--timeline-card-left) - 0.08rem);
|
|
1265
|
+
border-radius: calc(var(--timeline-card-radius) + 2px);
|
|
1266
|
+
box-shadow: 0 0 0 2px rgba(40, 93, 168, 0.18);
|
|
1267
|
+
pointer-events: none;
|
|
1253
1268
|
}
|
|
1254
1269
|
.hierarchy-timeline-row.is-in-path::before {
|
|
1255
1270
|
background: rgba(240, 246, 255, 0.54);
|
|
@@ -1900,27 +1915,67 @@ pre {
|
|
|
1900
1915
|
flex-wrap: wrap;
|
|
1901
1916
|
}
|
|
1902
1917
|
.session-tree-timeline-shell {
|
|
1903
|
-
padding: 0 0.1rem 0.
|
|
1918
|
+
padding: 0.08rem 0.1rem 0.24rem 0.5rem;
|
|
1904
1919
|
}
|
|
1905
1920
|
.session-tree-timeline-list {
|
|
1906
1921
|
display: flex;
|
|
1907
1922
|
flex-direction: column;
|
|
1908
|
-
gap: 0
|
|
1923
|
+
gap: 0;
|
|
1909
1924
|
}
|
|
1910
1925
|
.session-tree-timeline-list .hierarchy-timeline-row {
|
|
1911
|
-
|
|
1912
|
-
gap: 0.4rem;
|
|
1926
|
+
--timeline-time-column: 4.8rem;
|
|
1927
|
+
--timeline-column-gap: 0.4rem;
|
|
1928
|
+
--timeline-indent: 0.72rem;
|
|
1929
|
+
--timeline-gutter-base: 0.75rem;
|
|
1930
|
+
--embedded-bars-space: clamp(10.75rem, 49%, 15.6rem);
|
|
1931
|
+
--timeline-row-padding-x: 0.18rem;
|
|
1932
|
+
--timeline-card-radius: 18px;
|
|
1933
|
+
--timeline-card-inset-y: 0.24rem;
|
|
1934
|
+
--timeline-card-left: calc( var(--timeline-row-padding-x) + var(--timeline-time-column) + var(--timeline-column-gap) + var(--timeline-gutter-width) - 0.12rem );
|
|
1935
|
+
--timeline-card-right: 0.1rem;
|
|
1936
|
+
display: flex;
|
|
1937
|
+
gap: var(--timeline-column-gap);
|
|
1938
|
+
align-items: stretch;
|
|
1939
|
+
max-width: 100%;
|
|
1940
|
+
overflow: visible;
|
|
1941
|
+
}
|
|
1942
|
+
.session-tree-timeline-list .hierarchy-timeline-row-time {
|
|
1943
|
+
flex: 0 0 var(--timeline-time-column);
|
|
1944
|
+
justify-content: flex-start;
|
|
1945
|
+
text-align: left;
|
|
1946
|
+
padding-right: 0;
|
|
1947
|
+
padding-left: 0.08rem;
|
|
1948
|
+
overflow: visible;
|
|
1949
|
+
font-size: 0.74rem;
|
|
1950
|
+
}
|
|
1951
|
+
.session-tree-timeline-list .hierarchy-timeline-row-branch {
|
|
1952
|
+
flex: 1 1 0;
|
|
1953
|
+
min-width: 0;
|
|
1913
1954
|
}
|
|
1914
1955
|
.session-tree-timeline-list .hierarchy-timeline-row-labels {
|
|
1915
|
-
|
|
1956
|
+
min-width: 0;
|
|
1957
|
+
gap: 0.24rem;
|
|
1958
|
+
padding: 0.84rem calc(var(--embedded-bars-space) + 0.8rem) 0.84rem 0.92rem;
|
|
1916
1959
|
}
|
|
1917
1960
|
.session-tree-timeline-list .hierarchy-timeline-row-bars {
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1961
|
+
position: absolute;
|
|
1962
|
+
top: 0.84rem;
|
|
1963
|
+
right: 0.92rem;
|
|
1964
|
+
width: var(--embedded-bars-space);
|
|
1965
|
+
min-width: 0;
|
|
1966
|
+
display: flex;
|
|
1967
|
+
align-items: center;
|
|
1968
|
+
justify-content: flex-end;
|
|
1969
|
+
gap: 0.36rem;
|
|
1970
|
+
padding: 0;
|
|
1971
|
+
overflow: hidden;
|
|
1972
|
+
z-index: 2;
|
|
1921
1973
|
}
|
|
1922
1974
|
.session-tree-timeline-list .hierarchy-timeline-row-title {
|
|
1923
|
-
|
|
1975
|
+
position: relative;
|
|
1976
|
+
display: block;
|
|
1977
|
+
min-height: 0;
|
|
1978
|
+
padding-top: 2.05rem;
|
|
1924
1979
|
}
|
|
1925
1980
|
.session-tree-timeline-list .hierarchy-timeline-row-title-text,
|
|
1926
1981
|
.session-tree-timeline-list .hierarchy-timeline-row-meta {
|
|
@@ -1929,13 +1984,53 @@ pre {
|
|
|
1929
1984
|
white-space: nowrap;
|
|
1930
1985
|
}
|
|
1931
1986
|
.session-tree-timeline-list .hierarchy-timeline-row-title-text {
|
|
1932
|
-
|
|
1987
|
+
display: block;
|
|
1988
|
+
font-size: 0.86rem;
|
|
1989
|
+
}
|
|
1990
|
+
.session-tree-timeline-list .hierarchy-timeline-pill {
|
|
1991
|
+
position: absolute;
|
|
1992
|
+
top: 0;
|
|
1993
|
+
left: 0;
|
|
1994
|
+
}
|
|
1995
|
+
.session-tree-timeline-list .hierarchy-timeline-row-flag {
|
|
1996
|
+
margin-top: 0.35rem;
|
|
1997
|
+
margin-right: 0.35rem;
|
|
1933
1998
|
}
|
|
1934
1999
|
.session-tree-timeline-list .hierarchy-timeline-row-meta,
|
|
1935
2000
|
.session-tree-timeline-list .hierarchy-timeline-row-time,
|
|
1936
2001
|
.session-tree-timeline-list .hierarchy-timeline-row-duration {
|
|
1937
2002
|
font-size: 0.72rem;
|
|
1938
2003
|
}
|
|
2004
|
+
.session-tree-timeline-list .hierarchy-timeline-row-track {
|
|
2005
|
+
flex: 1 1 auto;
|
|
2006
|
+
min-width: 0;
|
|
2007
|
+
height: 1.3rem;
|
|
2008
|
+
}
|
|
2009
|
+
.session-tree-timeline-list .hierarchy-timeline-row-track::before {
|
|
2010
|
+
height: 0.4rem;
|
|
2011
|
+
}
|
|
2012
|
+
.session-tree-timeline-list .hierarchy-timeline-row-bar {
|
|
2013
|
+
width: max(0.65rem, calc(var(--timeline-span, 0.1) * 100%));
|
|
2014
|
+
height: 0.65rem;
|
|
2015
|
+
}
|
|
2016
|
+
.session-tree-timeline-list .hierarchy-timeline-row-bar::before,
|
|
2017
|
+
.session-tree-timeline-list .hierarchy-timeline-row-bar::after {
|
|
2018
|
+
height: 0.9rem;
|
|
2019
|
+
}
|
|
2020
|
+
.session-tree-timeline-list .hierarchy-timeline-row-duration {
|
|
2021
|
+
flex: 0 0 auto;
|
|
2022
|
+
}
|
|
2023
|
+
.session-tree-timeline-list .hierarchy-timeline-row.is-active::before {
|
|
2024
|
+
border-color: rgba(40, 93, 168, 0.16);
|
|
2025
|
+
background: rgba(236, 244, 255, 0.9);
|
|
2026
|
+
box-shadow: inset 2px 0 0 rgba(40, 93, 168, 0.54), 0 8px 20px rgba(32, 50, 76, 0.06);
|
|
2027
|
+
}
|
|
2028
|
+
.session-tree-timeline-list .hierarchy-timeline-row.is-detail-trace:not(.is-active)::before {
|
|
2029
|
+
box-shadow: inset 2px 0 0 rgba(40, 93, 168, 0.42), 0 6px 18px rgba(32, 50, 76, 0.05);
|
|
2030
|
+
}
|
|
2031
|
+
.session-tree-timeline-list .hierarchy-timeline-row:focus-visible::after {
|
|
2032
|
+
box-shadow: 0 0 0 2px rgba(40, 93, 168, 0.14);
|
|
2033
|
+
}
|
|
1939
2034
|
.session-tree-timeline {
|
|
1940
2035
|
display: inline-flex;
|
|
1941
2036
|
min-width: 0;
|
package/dist/client/app.js
CHANGED
|
@@ -21048,7 +21048,7 @@ function resolveSessionTreeSelection(sessionNodes, selectedNodeId, selectedTrace
|
|
|
21048
21048
|
selectedTraceId: nextSelectedTraceId
|
|
21049
21049
|
};
|
|
21050
21050
|
}
|
|
21051
|
-
function getDefaultExpandedSessionTreeNodeIds(sessionNodes, activeSessionId, selectedNodeId) {
|
|
21051
|
+
function getDefaultExpandedSessionTreeNodeIds(sessionNodes, activeSessionId, selectedNodeId, selectedTraceId = null) {
|
|
21052
21052
|
const expanded = /* @__PURE__ */ new Set();
|
|
21053
21053
|
const activeSession = (activeSessionId ? sessionNodes.find((node) => node.id === activeSessionId) ?? null : null) ?? sessionNodes[0] ?? null;
|
|
21054
21054
|
if (!activeSession) {
|
|
@@ -21069,6 +21069,16 @@ function getDefaultExpandedSessionTreeNodeIds(sessionNodes, activeSessionId, sel
|
|
|
21069
21069
|
}
|
|
21070
21070
|
}
|
|
21071
21071
|
}
|
|
21072
|
+
if (selectedTraceId) {
|
|
21073
|
+
for (const node of findSessionNodePath(
|
|
21074
|
+
[activeSession],
|
|
21075
|
+
`trace:${selectedTraceId}`
|
|
21076
|
+
)) {
|
|
21077
|
+
if (node.children.length) {
|
|
21078
|
+
expanded.add(node.id);
|
|
21079
|
+
}
|
|
21080
|
+
}
|
|
21081
|
+
}
|
|
21072
21082
|
return expanded;
|
|
21073
21083
|
}
|
|
21074
21084
|
function deriveSessionNavItem(node, traceById) {
|
|
@@ -21459,7 +21469,7 @@ function App() {
|
|
|
21459
21469
|
() => deriveSessionNavItems(sessionNodes, traceById),
|
|
21460
21470
|
[sessionNodes, traceById]
|
|
21461
21471
|
);
|
|
21462
|
-
const
|
|
21472
|
+
const sessionNavById2 = (0, import_react3.useMemo)(
|
|
21463
21473
|
() => new Map(sessionNavItems.map((item) => [item.id, item])),
|
|
21464
21474
|
[sessionNavItems]
|
|
21465
21475
|
);
|
|
@@ -21478,8 +21488,10 @@ function App() {
|
|
|
21478
21488
|
[sessionNodes, selectedTraceId]
|
|
21479
21489
|
);
|
|
21480
21490
|
const selectedPathIds = (0, import_react3.useMemo)(
|
|
21481
|
-
() => new Set(
|
|
21482
|
-
|
|
21491
|
+
() => new Set(
|
|
21492
|
+
[...selectedNodePath, ...selectedTracePath].map((node) => node.id)
|
|
21493
|
+
),
|
|
21494
|
+
[selectedNodePath, selectedTracePath]
|
|
21483
21495
|
);
|
|
21484
21496
|
const selectedSessionNode = (0, import_react3.useMemo)(
|
|
21485
21497
|
() => (selectedNodePath[0]?.type === "session" ? selectedNodePath[0] : null) ?? (selectedTracePath[0]?.type === "session" ? selectedTracePath[0] : null) ?? sessionNodes[0] ?? null,
|
|
@@ -21532,9 +21544,10 @@ function App() {
|
|
|
21532
21544
|
() => getDefaultExpandedSessionTreeNodeIds(
|
|
21533
21545
|
sessionNodes,
|
|
21534
21546
|
selectedSessionNode?.id ?? null,
|
|
21535
|
-
selectedNodeId
|
|
21547
|
+
selectedNodeId,
|
|
21548
|
+
selectedTraceId
|
|
21536
21549
|
),
|
|
21537
|
-
[selectedNodeId, selectedSessionNode, sessionNodes]
|
|
21550
|
+
[selectedNodeId, selectedSessionNode, selectedTraceId, sessionNodes]
|
|
21538
21551
|
);
|
|
21539
21552
|
const activeTabJsonMode = tabModes[detailTab] ?? "formatted";
|
|
21540
21553
|
const activeTagFilterCount = countTagFilters(filters.tags);
|
|
@@ -21627,7 +21640,7 @@ function App() {
|
|
|
21627
21640
|
if (!node) {
|
|
21628
21641
|
return;
|
|
21629
21642
|
}
|
|
21630
|
-
const nextTraceId = selectedTraceId && node.traceIds.includes(selectedTraceId) ? selectedTraceId : getNewestTraceIdForNode(node);
|
|
21643
|
+
const nextTraceId = node.type === "trace" ? node.meta.traceId ?? node.traceIds[0] ?? null : selectedTraceId && node.traceIds.includes(selectedTraceId) ? selectedTraceId : getNewestTraceIdForNode(node);
|
|
21631
21644
|
(0, import_react3.startTransition)(() => {
|
|
21632
21645
|
setSelectedNodeId(nodeId);
|
|
21633
21646
|
if (nextTraceId !== selectedTraceId) {
|
|
@@ -21779,7 +21792,7 @@ function App() {
|
|
|
21779
21792
|
selectedNodeId,
|
|
21780
21793
|
selectedPathIds,
|
|
21781
21794
|
selectedTraceId,
|
|
21782
|
-
sessionNavById,
|
|
21795
|
+
sessionNavById: sessionNavById2,
|
|
21783
21796
|
totalCount: allSessionCount,
|
|
21784
21797
|
traceById
|
|
21785
21798
|
}
|
|
@@ -21809,6 +21822,7 @@ function App() {
|
|
|
21809
21822
|
{
|
|
21810
21823
|
activeTab,
|
|
21811
21824
|
detail,
|
|
21825
|
+
detailPath: selectedTracePath.length ? selectedTracePath : selectedNodePath,
|
|
21812
21826
|
detailTabs,
|
|
21813
21827
|
fallbackTrace: selectedTraceSummary,
|
|
21814
21828
|
jsonMode: activeTabJsonMode,
|
|
@@ -21821,7 +21835,8 @@ function App() {
|
|
|
21821
21835
|
...current,
|
|
21822
21836
|
[tabId]: (current[tabId] ?? "formatted") === "formatted" ? "raw" : "formatted"
|
|
21823
21837
|
}));
|
|
21824
|
-
})
|
|
21838
|
+
}),
|
|
21839
|
+
traceById
|
|
21825
21840
|
}
|
|
21826
21841
|
) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardContent, { className: "content-scroll", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
21827
21842
|
EmptyState,
|
|
@@ -21892,7 +21907,7 @@ function SessionTreeNavigator({
|
|
|
21892
21907
|
selectedNodeId,
|
|
21893
21908
|
selectedPathIds,
|
|
21894
21909
|
selectedTraceId,
|
|
21895
|
-
sessionNavById,
|
|
21910
|
+
sessionNavById: sessionNavById2,
|
|
21896
21911
|
totalCount,
|
|
21897
21912
|
traceById
|
|
21898
21913
|
}) {
|
|
@@ -21921,7 +21936,7 @@ function SessionTreeNavigator({
|
|
|
21921
21936
|
selectedNodeId,
|
|
21922
21937
|
selectedPathIds,
|
|
21923
21938
|
selectedTraceId,
|
|
21924
|
-
sessionNavById,
|
|
21939
|
+
sessionNavById: sessionNavById2,
|
|
21925
21940
|
traceById
|
|
21926
21941
|
}
|
|
21927
21942
|
) })
|
|
@@ -21956,7 +21971,7 @@ function HierarchyTree({
|
|
|
21956
21971
|
selectedNodeId,
|
|
21957
21972
|
selectedPathIds,
|
|
21958
21973
|
selectedTraceId,
|
|
21959
|
-
sessionNavById,
|
|
21974
|
+
sessionNavById: sessionNavById2,
|
|
21960
21975
|
traceById
|
|
21961
21976
|
}) {
|
|
21962
21977
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "tree-root", children: nodes.map((node) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
@@ -21972,7 +21987,7 @@ function HierarchyTree({
|
|
|
21972
21987
|
selectedNodeId,
|
|
21973
21988
|
selectedPathIds,
|
|
21974
21989
|
selectedTraceId,
|
|
21975
|
-
sessionNavById,
|
|
21990
|
+
sessionNavById: sessionNavById2,
|
|
21976
21991
|
traceById
|
|
21977
21992
|
},
|
|
21978
21993
|
node.id
|
|
@@ -21989,7 +22004,7 @@ function HierarchyTreeNode({
|
|
|
21989
22004
|
selectedNodeId,
|
|
21990
22005
|
selectedPathIds,
|
|
21991
22006
|
selectedTraceId,
|
|
21992
|
-
sessionNavById,
|
|
22007
|
+
sessionNavById: sessionNavById2,
|
|
21993
22008
|
traceById
|
|
21994
22009
|
}) {
|
|
21995
22010
|
const isExpandable = node.children.length > 0;
|
|
@@ -21999,20 +22014,28 @@ function HierarchyTreeNode({
|
|
|
21999
22014
|
const isInPath = selectedPathIds.has(node.id);
|
|
22000
22015
|
const nodeCopy = getHierarchyNodeCopy(node, traceById);
|
|
22001
22016
|
const trace = node.meta.traceId ? traceById.get(node.meta.traceId) ?? null : null;
|
|
22002
|
-
const sessionNavItem = node.type === "session" ?
|
|
22017
|
+
const sessionNavItem = node.type === "session" ? sessionNavById2.get(node.id) ?? null : null;
|
|
22003
22018
|
if (node.type === "trace" && trace) {
|
|
22004
22019
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
22005
|
-
|
|
22020
|
+
TraceHierarchyNode,
|
|
22006
22021
|
{
|
|
22022
|
+
defaultExpandedNodeIds,
|
|
22007
22023
|
depth,
|
|
22024
|
+
expandedNodeOverrides,
|
|
22008
22025
|
maxDurationMs,
|
|
22009
22026
|
node,
|
|
22010
22027
|
nodeCopy,
|
|
22011
22028
|
onSelect,
|
|
22029
|
+
onToggle,
|
|
22012
22030
|
inPath: isInPath,
|
|
22031
|
+
isExpanded,
|
|
22013
22032
|
selected: selectedNodeId === node.id,
|
|
22033
|
+
selectedNodeId,
|
|
22034
|
+
selectedPathIds,
|
|
22014
22035
|
selectedTrace: selectedTraceId === trace.id,
|
|
22015
|
-
|
|
22036
|
+
selectedTraceId,
|
|
22037
|
+
trace,
|
|
22038
|
+
traceById
|
|
22016
22039
|
}
|
|
22017
22040
|
);
|
|
22018
22041
|
}
|
|
@@ -22033,7 +22056,7 @@ function HierarchyTreeNode({
|
|
|
22033
22056
|
selectedNodeId,
|
|
22034
22057
|
selectedPathIds,
|
|
22035
22058
|
selectedTraceId,
|
|
22036
|
-
sessionNavById,
|
|
22059
|
+
sessionNavById: sessionNavById2,
|
|
22037
22060
|
traceById
|
|
22038
22061
|
}
|
|
22039
22062
|
);
|
|
@@ -22108,7 +22131,7 @@ function HierarchyTreeNode({
|
|
|
22108
22131
|
selectedNodeId,
|
|
22109
22132
|
selectedPathIds,
|
|
22110
22133
|
selectedTraceId,
|
|
22111
|
-
sessionNavById,
|
|
22134
|
+
sessionNavById: sessionNavById2,
|
|
22112
22135
|
traceById
|
|
22113
22136
|
},
|
|
22114
22137
|
child.id
|
|
@@ -22131,7 +22154,7 @@ function SessionHierarchyBranch({
|
|
|
22131
22154
|
selectedNodeId,
|
|
22132
22155
|
selectedPathIds,
|
|
22133
22156
|
selectedTraceId,
|
|
22134
|
-
sessionNavById,
|
|
22157
|
+
sessionNavById: sessionNavById2,
|
|
22135
22158
|
traceById
|
|
22136
22159
|
}) {
|
|
22137
22160
|
const detailLabel = formatList([
|
|
@@ -22249,7 +22272,7 @@ function SessionHierarchyBranch({
|
|
|
22249
22272
|
selectedNodeId,
|
|
22250
22273
|
selectedPathIds,
|
|
22251
22274
|
selectedTraceId,
|
|
22252
|
-
sessionNavById,
|
|
22275
|
+
sessionNavById: sessionNavById2,
|
|
22253
22276
|
traceById
|
|
22254
22277
|
},
|
|
22255
22278
|
child.id
|
|
@@ -22258,59 +22281,105 @@ function SessionHierarchyBranch({
|
|
|
22258
22281
|
}
|
|
22259
22282
|
);
|
|
22260
22283
|
}
|
|
22261
|
-
function
|
|
22284
|
+
function TraceHierarchyNode({
|
|
22285
|
+
defaultExpandedNodeIds,
|
|
22262
22286
|
depth,
|
|
22287
|
+
expandedNodeOverrides,
|
|
22263
22288
|
inPath,
|
|
22289
|
+
isExpanded,
|
|
22264
22290
|
maxDurationMs,
|
|
22265
22291
|
node,
|
|
22266
22292
|
nodeCopy,
|
|
22267
22293
|
onSelect,
|
|
22294
|
+
onToggle,
|
|
22268
22295
|
selected,
|
|
22296
|
+
selectedNodeId,
|
|
22297
|
+
selectedPathIds,
|
|
22269
22298
|
selectedTrace,
|
|
22270
|
-
|
|
22299
|
+
selectedTraceId,
|
|
22300
|
+
trace,
|
|
22301
|
+
traceById
|
|
22271
22302
|
}) {
|
|
22272
|
-
|
|
22303
|
+
const isExpandable = node.children.length > 0;
|
|
22304
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
22273
22305
|
"div",
|
|
22274
22306
|
{
|
|
22275
22307
|
className: "tree-node-wrap tree-trace-wrap",
|
|
22276
22308
|
style: { "--depth": String(depth) },
|
|
22277
|
-
children:
|
|
22278
|
-
|
|
22279
|
-
|
|
22280
|
-
|
|
22281
|
-
|
|
22282
|
-
|
|
22283
|
-
|
|
22284
|
-
|
|
22285
|
-
|
|
22286
|
-
|
|
22287
|
-
|
|
22288
|
-
|
|
22289
|
-
|
|
22290
|
-
|
|
22291
|
-
|
|
22292
|
-
|
|
22293
|
-
|
|
22294
|
-
|
|
22295
|
-
|
|
22296
|
-
|
|
22297
|
-
|
|
22298
|
-
|
|
22299
|
-
|
|
22300
|
-
|
|
22301
|
-
|
|
22302
|
-
|
|
22303
|
-
|
|
22304
|
-
|
|
22305
|
-
|
|
22306
|
-
|
|
22307
|
-
|
|
22308
|
-
|
|
22309
|
-
|
|
22310
|
-
|
|
22311
|
-
|
|
22312
|
-
|
|
22313
|
-
|
|
22309
|
+
children: [
|
|
22310
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
22311
|
+
"div",
|
|
22312
|
+
{
|
|
22313
|
+
className: clsx_default(
|
|
22314
|
+
"tree-node-card is-trace",
|
|
22315
|
+
inPath && "is-in-path",
|
|
22316
|
+
selected && "is-active",
|
|
22317
|
+
selectedTrace && "is-detail-trace"
|
|
22318
|
+
),
|
|
22319
|
+
children: [
|
|
22320
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
22321
|
+
"button",
|
|
22322
|
+
{
|
|
22323
|
+
type: "button",
|
|
22324
|
+
className: clsx_default("tree-node-toggle", !isExpandable && "is-static"),
|
|
22325
|
+
disabled: !isExpandable,
|
|
22326
|
+
onClick: () => {
|
|
22327
|
+
if (isExpandable) {
|
|
22328
|
+
onToggle(node.id);
|
|
22329
|
+
}
|
|
22330
|
+
},
|
|
22331
|
+
"aria-label": isExpandable ? `${isExpanded ? "Collapse" : "Expand"} ${nodeCopy.label}` : void 0,
|
|
22332
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronRight, { className: clsx_default(isExpanded && "is-open") })
|
|
22333
|
+
}
|
|
22334
|
+
),
|
|
22335
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
22336
|
+
"button",
|
|
22337
|
+
{
|
|
22338
|
+
type: "button",
|
|
22339
|
+
className: "tree-node-select tree-trace-select",
|
|
22340
|
+
onClick: () => onSelect(node),
|
|
22341
|
+
children: [
|
|
22342
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "tree-node-copy", children: [
|
|
22343
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "trace-nav-kicker", children: getTraceActorLabel(trace) }),
|
|
22344
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "tree-node-label", children: nodeCopy.label }),
|
|
22345
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "tree-node-meta", children: formatList([
|
|
22346
|
+
formatTimelineTimestamp(trace.startedAt),
|
|
22347
|
+
nodeCopy.meta
|
|
22348
|
+
]) })
|
|
22349
|
+
] }),
|
|
22350
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
22351
|
+
TraceElapsedBar,
|
|
22352
|
+
{
|
|
22353
|
+
compact: true,
|
|
22354
|
+
durationMs: trace.durationMs,
|
|
22355
|
+
maxDurationMs
|
|
22356
|
+
}
|
|
22357
|
+
)
|
|
22358
|
+
]
|
|
22359
|
+
}
|
|
22360
|
+
)
|
|
22361
|
+
]
|
|
22362
|
+
}
|
|
22363
|
+
),
|
|
22364
|
+
isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "tree-node-children", children: node.children.map((child) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
22365
|
+
HierarchyTreeNode,
|
|
22366
|
+
{
|
|
22367
|
+
defaultExpandedNodeIds,
|
|
22368
|
+
depth: depth + 1,
|
|
22369
|
+
expandedNodeOverrides,
|
|
22370
|
+
maxDurationMs,
|
|
22371
|
+
node: child,
|
|
22372
|
+
onSelect,
|
|
22373
|
+
onToggle,
|
|
22374
|
+
selectedNodeId,
|
|
22375
|
+
selectedPathIds,
|
|
22376
|
+
selectedTraceId,
|
|
22377
|
+
sessionNavById,
|
|
22378
|
+
traceById
|
|
22379
|
+
},
|
|
22380
|
+
child.id
|
|
22381
|
+
)) }) : null
|
|
22382
|
+
]
|
|
22314
22383
|
}
|
|
22315
22384
|
);
|
|
22316
22385
|
}
|
|
@@ -22525,6 +22594,7 @@ function HierarchyTimelineRows({
|
|
|
22525
22594
|
function TraceDetailPanel({
|
|
22526
22595
|
activeTab,
|
|
22527
22596
|
detail,
|
|
22597
|
+
detailPath,
|
|
22528
22598
|
detailTabs,
|
|
22529
22599
|
fallbackTrace,
|
|
22530
22600
|
jsonMode,
|
|
@@ -22533,11 +22603,12 @@ function TraceDetailPanel({
|
|
|
22533
22603
|
onBack,
|
|
22534
22604
|
onNavigateHierarchyNode,
|
|
22535
22605
|
onTabChange,
|
|
22536
|
-
onToggleJsonMode
|
|
22606
|
+
onToggleJsonMode,
|
|
22607
|
+
traceById
|
|
22537
22608
|
}) {
|
|
22538
22609
|
const traceDetailPrimaryRef = (0, import_react3.useRef)(null);
|
|
22539
22610
|
const [showInlineContextRail, setShowInlineContextRail] = (0, import_react3.useState)(false);
|
|
22540
|
-
const detailCopy = detail ? getTraceDisplayCopy(detail) : fallbackTrace ? getTraceDisplayCopy(fallbackTrace) : null;
|
|
22611
|
+
const detailCopy = detailPath.length ? getHierarchyPathDisplayCopy(detailPath, traceById) : detail ? getTraceDisplayCopy(detail) : fallbackTrace ? getTraceDisplayCopy(fallbackTrace) : null;
|
|
22541
22612
|
const detailStatus = detail?.status ?? fallbackTrace?.status ?? null;
|
|
22542
22613
|
const detailDuration = detail ? formatTraceDuration(detail) : fallbackTrace ? fallbackTrace.durationMs == null ? "Running" : `${fallbackTrace.durationMs} ms` : null;
|
|
22543
22614
|
const detailSubtitle = formatTraceProviderSummary(detail ?? fallbackTrace);
|
|
@@ -22848,6 +22919,24 @@ function renderTabContent(tab, detail, jsonMode, onApplyTagFilter, onApplyTraceF
|
|
|
22848
22919
|
}
|
|
22849
22920
|
) : null
|
|
22850
22921
|
] });
|
|
22922
|
+
case "otel":
|
|
22923
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
22924
|
+
JsonCard,
|
|
22925
|
+
{
|
|
22926
|
+
title: "OTel span",
|
|
22927
|
+
value: {
|
|
22928
|
+
name: detail.name,
|
|
22929
|
+
spanContext: detail.spanContext,
|
|
22930
|
+
parentSpanId: detail.parentSpanId,
|
|
22931
|
+
spanKind: detail.spanKind,
|
|
22932
|
+
spanStatus: detail.spanStatus,
|
|
22933
|
+
startTime: detail.startedAt,
|
|
22934
|
+
endTime: detail.endedAt,
|
|
22935
|
+
attributes: detail.attributes,
|
|
22936
|
+
events: detail.events
|
|
22937
|
+
}
|
|
22938
|
+
}
|
|
22939
|
+
);
|
|
22851
22940
|
default:
|
|
22852
22941
|
return null;
|
|
22853
22942
|
}
|
|
@@ -23766,6 +23855,7 @@ function buildDetailTabs(detail) {
|
|
|
23766
23855
|
if (detail.stream) {
|
|
23767
23856
|
tabs.push({ id: "stream", label: "Stream" });
|
|
23768
23857
|
}
|
|
23858
|
+
tabs.push({ id: "otel", label: "OTel" });
|
|
23769
23859
|
return tabs;
|
|
23770
23860
|
}
|
|
23771
23861
|
function getHierarchyNodeCopy(node, traceById) {
|
|
@@ -23824,8 +23914,8 @@ function getHierarchyNodeCopy(node, traceById) {
|
|
|
23824
23914
|
trace.provider,
|
|
23825
23915
|
trace.model,
|
|
23826
23916
|
formatCostSummaryLabel(
|
|
23827
|
-
|
|
23828
|
-
|
|
23917
|
+
getHierarchyNodeCostUsd(node) ?? trace.costUsd,
|
|
23918
|
+
node.count > 1
|
|
23829
23919
|
)
|
|
23830
23920
|
]) : formatList([
|
|
23831
23921
|
formatCountLabel(node.count, "call"),
|
|
@@ -23848,55 +23938,24 @@ function getHierarchyNodeCopy(node, traceById) {
|
|
|
23848
23938
|
}
|
|
23849
23939
|
}
|
|
23850
23940
|
function getTraceDisplayCopy(trace) {
|
|
23851
|
-
const actor = trace.hierarchy.childActorId || trace.hierarchy.rootActorId;
|
|
23852
|
-
const breadcrumbs = getTraceBreadcrumbs(trace);
|
|
23853
|
-
const pathParts = breadcrumbs.map((item) => item.label);
|
|
23854
23941
|
return {
|
|
23855
|
-
breadcrumbs,
|
|
23856
|
-
path:
|
|
23857
|
-
subtitle: formatList([actor, trace.provider, trace.model]),
|
|
23942
|
+
breadcrumbs: [],
|
|
23943
|
+
path: "",
|
|
23858
23944
|
title: getTraceTitle(trace)
|
|
23859
23945
|
};
|
|
23860
23946
|
}
|
|
23861
|
-
function
|
|
23862
|
-
const
|
|
23863
|
-
|
|
23864
|
-
|
|
23865
|
-
|
|
23866
|
-
|
|
23867
|
-
|
|
23868
|
-
|
|
23869
|
-
|
|
23870
|
-
|
|
23871
|
-
|
|
23872
|
-
|
|
23873
|
-
];
|
|
23874
|
-
if (trace.kind === "guardrail") {
|
|
23875
|
-
const label = `${capitalize(trace.hierarchy.guardrailPhase || "guardrail")} guardrail`;
|
|
23876
|
-
breadcrumbs.push({
|
|
23877
|
-
label,
|
|
23878
|
-
nodeId: `guardrail:${sessionId}:${rootActorId}:${trace.hierarchy.guardrailType || label.toLowerCase()}`
|
|
23879
|
-
});
|
|
23880
|
-
} else if (trace.kind === "child-actor" && trace.hierarchy.childActorId) {
|
|
23881
|
-
breadcrumbs.push({
|
|
23882
|
-
label: `Child actor: ${trace.hierarchy.childActorId}`,
|
|
23883
|
-
nodeId: `child-actor:${sessionId}:${rootActorId}:${trace.hierarchy.childActorId}`
|
|
23884
|
-
});
|
|
23885
|
-
} else if (trace.kind === "stage") {
|
|
23886
|
-
if (trace.hierarchy.childActorId) {
|
|
23887
|
-
breadcrumbs.push({
|
|
23888
|
-
label: `Child actor: ${trace.hierarchy.childActorId}`,
|
|
23889
|
-
nodeId: `child-actor:${sessionId}:${rootActorId}:${trace.hierarchy.childActorId}`
|
|
23890
|
-
});
|
|
23891
|
-
}
|
|
23892
|
-
if (trace.hierarchy.stage) {
|
|
23893
|
-
breadcrumbs.push({
|
|
23894
|
-
label: `Stage: ${trace.hierarchy.stage}`,
|
|
23895
|
-
nodeId: `stage:${sessionId}:${rootActorId}:${trace.hierarchy.childActorId || "root"}:${trace.hierarchy.stage}`
|
|
23896
|
-
});
|
|
23897
|
-
}
|
|
23898
|
-
}
|
|
23899
|
-
return breadcrumbs;
|
|
23947
|
+
function getHierarchyPathDisplayCopy(path, traceById) {
|
|
23948
|
+
const breadcrumbs = path.map((node) => ({
|
|
23949
|
+
label: getHierarchyNodeCopy(node, traceById).label,
|
|
23950
|
+
nodeId: node.id
|
|
23951
|
+
}));
|
|
23952
|
+
const lastNode = path[path.length - 1] ?? null;
|
|
23953
|
+
const lastTrace = lastNode?.type === "trace" && lastNode.meta.traceId ? traceById.get(lastNode.meta.traceId) ?? null : null;
|
|
23954
|
+
return {
|
|
23955
|
+
breadcrumbs,
|
|
23956
|
+
path: breadcrumbs.map((item) => item.label).join(" / "),
|
|
23957
|
+
title: lastTrace ? getTraceTitle(lastTrace) : breadcrumbs.at(-1)?.label || "Trace"
|
|
23958
|
+
};
|
|
23900
23959
|
}
|
|
23901
23960
|
function formatTraceProviderSummary(trace) {
|
|
23902
23961
|
if (!trace) {
|
|
@@ -24047,12 +24106,12 @@ function patchHierarchyNode(node, traceId, previousSummary, nextSummary, costDel
|
|
|
24047
24106
|
let nextLabel = node.label;
|
|
24048
24107
|
if (containsTrace) {
|
|
24049
24108
|
if (node.type === "trace" && node.meta.traceId === traceId) {
|
|
24050
|
-
nextMeta.costUsd = nextSummary.costUsd;
|
|
24051
24109
|
nextMeta.model = nextSummary.model;
|
|
24052
24110
|
nextMeta.provider = nextSummary.provider;
|
|
24053
24111
|
nextMeta.status = nextSummary.status;
|
|
24054
24112
|
nextLabel = nextSummary.model ? `${nextSummary.model} ${nextSummary.mode}` : traceId;
|
|
24055
|
-
}
|
|
24113
|
+
}
|
|
24114
|
+
if (costDelta !== 0 || previousSummary?.costUsd !== null || nextSummary.costUsd !== null) {
|
|
24056
24115
|
const currentCost = typeof nextMeta.costUsd === "number" && Number.isFinite(nextMeta.costUsd) ? nextMeta.costUsd : 0;
|
|
24057
24116
|
nextMeta.costUsd = roundCostUsd2(currentCost + costDelta);
|
|
24058
24117
|
}
|
package/dist/session-nav.d.ts
CHANGED
|
@@ -41,4 +41,4 @@ export declare function findSessionNodePath(nodes: SessionNavHierarchyNode[], id
|
|
|
41
41
|
export declare function findSessionNodeById(nodes: SessionNavHierarchyNode[], id: string): SessionNavHierarchyNode | null;
|
|
42
42
|
export declare function getNewestTraceIdForNode(node: SessionNavHierarchyNode | null | undefined): string | null;
|
|
43
43
|
export declare function resolveSessionTreeSelection(sessionNodes: SessionNavHierarchyNode[], selectedNodeId: string | null, selectedTraceId: string | null): SessionTreeSelection;
|
|
44
|
-
export declare function getDefaultExpandedSessionTreeNodeIds(sessionNodes: SessionNavHierarchyNode[], activeSessionId: string | null, selectedNodeId: string | null): Set<string>;
|
|
44
|
+
export declare function getDefaultExpandedSessionTreeNodeIds(sessionNodes: SessionNavHierarchyNode[], activeSessionId: string | null, selectedNodeId: string | null, selectedTraceId?: string | null): Set<string>;
|
package/dist/session-nav.js
CHANGED
|
@@ -66,7 +66,7 @@ function resolveSessionTreeSelection(sessionNodes, selectedNodeId, selectedTrace
|
|
|
66
66
|
selectedTraceId: nextSelectedTraceId,
|
|
67
67
|
};
|
|
68
68
|
}
|
|
69
|
-
function getDefaultExpandedSessionTreeNodeIds(sessionNodes, activeSessionId, selectedNodeId) {
|
|
69
|
+
function getDefaultExpandedSessionTreeNodeIds(sessionNodes, activeSessionId, selectedNodeId, selectedTraceId = null) {
|
|
70
70
|
const expanded = new Set();
|
|
71
71
|
const activeSession = (activeSessionId
|
|
72
72
|
? sessionNodes.find((node) => node.id === activeSessionId) ?? null
|
|
@@ -89,6 +89,13 @@ function getDefaultExpandedSessionTreeNodeIds(sessionNodes, activeSessionId, sel
|
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
|
+
if (selectedTraceId) {
|
|
93
|
+
for (const node of findSessionNodePath([activeSession], `trace:${selectedTraceId}`)) {
|
|
94
|
+
if (node.children.length) {
|
|
95
|
+
expanded.add(node.id);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
92
99
|
return expanded;
|
|
93
100
|
}
|
|
94
101
|
function deriveSessionNavItem(node, traceById) {
|
package/dist/store.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export declare class TraceStore extends EventEmitter {
|
|
|
17
17
|
clear(): void;
|
|
18
18
|
hierarchy(filters?: TraceFilters): HierarchyResponse;
|
|
19
19
|
private recordStart;
|
|
20
|
+
private findTraceBySpanReference;
|
|
20
21
|
private evictIfNeeded;
|
|
21
22
|
private cloneTrace;
|
|
22
23
|
private filteredTraces;
|
package/dist/store.js
CHANGED
|
@@ -141,51 +141,49 @@ class TraceStore extends node_events_1.EventEmitter {
|
|
|
141
141
|
};
|
|
142
142
|
}
|
|
143
143
|
const roots = new Map();
|
|
144
|
+
const traceBySpanId = new Map();
|
|
145
|
+
const traceNodeByTraceId = new Map();
|
|
146
|
+
const traceSessionByTraceId = new Map();
|
|
147
|
+
const parentNodeById = new Map();
|
|
144
148
|
for (const trace of traces) {
|
|
145
|
-
const sessionId = trace
|
|
149
|
+
const sessionId = getTraceSessionId(trace);
|
|
146
150
|
const sessionNode = getOrCreateNode(roots, `session:${sessionId}`, 'session', `Session ${sessionId}`, {
|
|
147
151
|
sessionId,
|
|
148
152
|
chatId: trace.hierarchy.chatId,
|
|
149
|
-
|
|
150
|
-
const lineage = [sessionNode];
|
|
151
|
-
const rootActorId = trace.hierarchy.rootActorId || 'unknown-actor';
|
|
152
|
-
const actorNode = getOrCreateNode(sessionNode.children, `actor:${sessionId}:${rootActorId}`, 'actor', rootActorId, {
|
|
153
|
-
actorId: rootActorId,
|
|
154
|
-
rootActorId,
|
|
155
|
-
sessionId,
|
|
153
|
+
rootActorId: trace.hierarchy.rootActorId,
|
|
156
154
|
topLevelAgentId: trace.hierarchy.topLevelAgentId,
|
|
157
155
|
});
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
170
|
-
else if (trace.hierarchy.childActorId) {
|
|
171
|
-
currentNode = getOrCreateNode(currentNode.children, `child-actor:${sessionId}:${rootActorId}:${trace.hierarchy.childActorId}`, 'child-actor', trace.hierarchy.childActorId, {
|
|
172
|
-
actorId: trace.hierarchy.childActorId,
|
|
173
|
-
childActorId: trace.hierarchy.childActorId,
|
|
174
|
-
delegatedAgentId: trace.hierarchy.delegatedAgentId,
|
|
175
|
-
});
|
|
176
|
-
lineage.push(currentNode);
|
|
156
|
+
const traceNode = createTraceNode(trace);
|
|
157
|
+
traceBySpanId.set(trace.spanContext.spanId, trace);
|
|
158
|
+
traceNodeByTraceId.set(trace.id, traceNode);
|
|
159
|
+
traceSessionByTraceId.set(trace.id, sessionId);
|
|
160
|
+
}
|
|
161
|
+
for (const trace of traces) {
|
|
162
|
+
const sessionId = traceSessionByTraceId.get(trace.id) || 'unknown-session';
|
|
163
|
+
const sessionNode = roots.get(`session:${sessionId}`);
|
|
164
|
+
const traceNode = traceNodeByTraceId.get(trace.id);
|
|
165
|
+
if (!sessionNode || !traceNode) {
|
|
166
|
+
continue;
|
|
177
167
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
168
|
+
const parentTrace = trace.parentSpanId ? traceBySpanId.get(trace.parentSpanId) : null;
|
|
169
|
+
const parentTraceNode = parentTrace &&
|
|
170
|
+
traceSessionByTraceId.get(parentTrace.id) === sessionId &&
|
|
171
|
+
parentTrace.id !== trace.id
|
|
172
|
+
? traceNodeByTraceId.get(parentTrace.id) || null
|
|
173
|
+
: null;
|
|
174
|
+
parentNodeById.set(traceNode.id, parentTraceNode || sessionNode);
|
|
175
|
+
}
|
|
176
|
+
for (const trace of traces) {
|
|
177
|
+
const traceNode = traceNodeByTraceId.get(trace.id);
|
|
178
|
+
const parentNode = traceNode ? parentNodeById.get(traceNode.id) || null : null;
|
|
179
|
+
if (!traceNode || !parentNode) {
|
|
180
|
+
continue;
|
|
184
181
|
}
|
|
185
|
-
|
|
186
|
-
currentNode
|
|
187
|
-
|
|
188
|
-
applyTraceRollup(
|
|
182
|
+
parentNode.children.set(traceNode.id, traceNode);
|
|
183
|
+
let currentNode = parentNode;
|
|
184
|
+
while (currentNode) {
|
|
185
|
+
applyTraceRollup(currentNode, trace);
|
|
186
|
+
currentNode = parentNodeById.get(currentNode.id) || null;
|
|
189
187
|
}
|
|
190
188
|
}
|
|
191
189
|
return {
|
|
@@ -195,9 +193,9 @@ class TraceStore extends node_events_1.EventEmitter {
|
|
|
195
193
|
};
|
|
196
194
|
}
|
|
197
195
|
recordStart(mode, context, request, options = {}) {
|
|
198
|
-
const traceContext = (0, utils_1.normalizeTraceContext)(context, mode);
|
|
196
|
+
const traceContext = applyConversationIdToContext((0, utils_1.normalizeTraceContext)(context, mode), options.attributes);
|
|
199
197
|
const traceId = randomId();
|
|
200
|
-
const parentSpan =
|
|
198
|
+
const parentSpan = this.findTraceBySpanReference(options.parentSpanId);
|
|
201
199
|
const startedAt = new Date().toISOString();
|
|
202
200
|
const trace = {
|
|
203
201
|
attributes: buildSpanAttributes(traceContext, mode, request, options.attributes),
|
|
@@ -253,6 +251,21 @@ class TraceStore extends node_events_1.EventEmitter {
|
|
|
253
251
|
this.publish('span:start', traceId, { trace: this.cloneTrace(trace) });
|
|
254
252
|
return traceId;
|
|
255
253
|
}
|
|
254
|
+
findTraceBySpanReference(spanReference) {
|
|
255
|
+
if (!spanReference) {
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
const byTraceId = this.traces.get(spanReference);
|
|
259
|
+
if (byTraceId) {
|
|
260
|
+
return byTraceId;
|
|
261
|
+
}
|
|
262
|
+
for (const trace of this.traces.values()) {
|
|
263
|
+
if (trace.spanContext.spanId === spanReference) {
|
|
264
|
+
return trace;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
256
269
|
evictIfNeeded() {
|
|
257
270
|
while (this.order.length > this.maxTraces) {
|
|
258
271
|
const oldest = this.order.shift();
|
|
@@ -420,6 +433,35 @@ function buildGroupHierarchy(traces, groupBy) {
|
|
|
420
433
|
}
|
|
421
434
|
return [...groups.values()].map(serialiseNode);
|
|
422
435
|
}
|
|
436
|
+
function getTraceSessionId(trace) {
|
|
437
|
+
const conversationId = toNonEmptyString(trace.attributes?.['gen_ai.conversation.id']);
|
|
438
|
+
return conversationId || trace.hierarchy.sessionId || 'unknown-session';
|
|
439
|
+
}
|
|
440
|
+
function applyConversationIdToContext(context, extraAttributes) {
|
|
441
|
+
const conversationId = toNonEmptyString(extraAttributes?.['gen_ai.conversation.id']);
|
|
442
|
+
if (!conversationId || conversationId === context.sessionId) {
|
|
443
|
+
return context;
|
|
444
|
+
}
|
|
445
|
+
return {
|
|
446
|
+
...context,
|
|
447
|
+
sessionId: conversationId,
|
|
448
|
+
chatId: conversationId,
|
|
449
|
+
rootSessionId: context.rootSessionId || conversationId,
|
|
450
|
+
rootChatId: context.rootChatId || conversationId,
|
|
451
|
+
tags: {
|
|
452
|
+
...context.tags,
|
|
453
|
+
sessionId: conversationId,
|
|
454
|
+
chatId: conversationId,
|
|
455
|
+
rootSessionId: context.rootSessionId || conversationId,
|
|
456
|
+
rootChatId: context.rootChatId || conversationId,
|
|
457
|
+
},
|
|
458
|
+
hierarchy: {
|
|
459
|
+
...context.hierarchy,
|
|
460
|
+
sessionId: conversationId,
|
|
461
|
+
chatId: conversationId,
|
|
462
|
+
},
|
|
463
|
+
};
|
|
464
|
+
}
|
|
423
465
|
function randomId() {
|
|
424
466
|
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
425
467
|
}
|
|
@@ -469,6 +511,9 @@ function getDefaultSpanName(context, mode) {
|
|
|
469
511
|
const prefix = context.provider || 'llm';
|
|
470
512
|
return `${prefix}.${mode}`;
|
|
471
513
|
}
|
|
514
|
+
function toNonEmptyString(value) {
|
|
515
|
+
return typeof value === 'string' && value.trim() ? value : null;
|
|
516
|
+
}
|
|
472
517
|
function buildSpanAttributes(context, mode, request, extraAttributes) {
|
|
473
518
|
const base = {
|
|
474
519
|
'gen_ai.conversation.id': context.sessionId || undefined,
|