@mtharrison/loupe 1.2.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.
@@ -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) {
@@ -21395,34 +21405,36 @@ function App() {
21395
21405
  });
21396
21406
  const handleSseMessage = (0, import_react3.useEffectEvent)((data2) => {
21397
21407
  const payload = parseEvent(data2);
21408
+ const nextTrace = payload?.span;
21409
+ const nextTraceId = payload?.spanId;
21398
21410
  if (payload?.type === "ui:reload") {
21399
21411
  window.location.reload();
21400
21412
  return;
21401
21413
  }
21402
- if (payload?.trace && selectedTraceId && payload.traceId === selectedTraceId) {
21403
- (0, import_react3.startTransition)(() => setDetail(payload.trace));
21414
+ if (nextTrace && selectedTraceId && nextTraceId === selectedTraceId) {
21415
+ (0, import_react3.startTransition)(() => setDetail(nextTrace));
21404
21416
  }
21405
21417
  if (!queryString) {
21406
- if (payload?.type === "trace:update" && payload.trace) {
21407
- applyIncrementalTraceUpdate(payload.trace);
21418
+ if ((payload?.type === "span:update" || payload?.type === "span:end") && nextTrace) {
21419
+ applyIncrementalTraceUpdate(nextTrace);
21408
21420
  return;
21409
21421
  }
21410
- if (payload?.type === "trace:add" && payload.trace) {
21411
- applyIncrementalTraceAdd(payload.trace);
21422
+ if (payload?.type === "span:start" && nextTrace) {
21423
+ applyIncrementalTraceAdd(nextTrace);
21412
21424
  scheduleRefresh(180);
21413
21425
  return;
21414
21426
  }
21415
- if (payload?.type === "trace:evict") {
21416
- applyIncrementalTraceEvict(payload.traceId);
21427
+ if (payload?.type === "span:evict") {
21428
+ applyIncrementalTraceEvict(nextTraceId);
21417
21429
  scheduleRefresh(180);
21418
21430
  return;
21419
21431
  }
21420
- if (payload?.type === "trace:clear") {
21432
+ if (payload?.type === "span:clear") {
21421
21433
  clearIncrementalState();
21422
21434
  return;
21423
21435
  }
21424
21436
  }
21425
- scheduleRefresh(payload?.type === "trace:update" ? 700 : 180);
21437
+ scheduleRefresh(payload?.type === "span:update" || payload?.type === "span:end" ? 700 : 180);
21426
21438
  });
21427
21439
  (0, import_react3.useEffect)(() => {
21428
21440
  const events = new EventSource("/api/events");
@@ -21457,7 +21469,7 @@ function App() {
21457
21469
  () => deriveSessionNavItems(sessionNodes, traceById),
21458
21470
  [sessionNodes, traceById]
21459
21471
  );
21460
- const sessionNavById = (0, import_react3.useMemo)(
21472
+ const sessionNavById2 = (0, import_react3.useMemo)(
21461
21473
  () => new Map(sessionNavItems.map((item) => [item.id, item])),
21462
21474
  [sessionNavItems]
21463
21475
  );
@@ -21476,8 +21488,10 @@ function App() {
21476
21488
  [sessionNodes, selectedTraceId]
21477
21489
  );
21478
21490
  const selectedPathIds = (0, import_react3.useMemo)(
21479
- () => new Set(selectedNodePath.map((node) => node.id)),
21480
- [selectedNodePath]
21491
+ () => new Set(
21492
+ [...selectedNodePath, ...selectedTracePath].map((node) => node.id)
21493
+ ),
21494
+ [selectedNodePath, selectedTracePath]
21481
21495
  );
21482
21496
  const selectedSessionNode = (0, import_react3.useMemo)(
21483
21497
  () => (selectedNodePath[0]?.type === "session" ? selectedNodePath[0] : null) ?? (selectedTracePath[0]?.type === "session" ? selectedTracePath[0] : null) ?? sessionNodes[0] ?? null,
@@ -21530,9 +21544,10 @@ function App() {
21530
21544
  () => getDefaultExpandedSessionTreeNodeIds(
21531
21545
  sessionNodes,
21532
21546
  selectedSessionNode?.id ?? null,
21533
- selectedNodeId
21547
+ selectedNodeId,
21548
+ selectedTraceId
21534
21549
  ),
21535
- [selectedNodeId, selectedSessionNode, sessionNodes]
21550
+ [selectedNodeId, selectedSessionNode, selectedTraceId, sessionNodes]
21536
21551
  );
21537
21552
  const activeTabJsonMode = tabModes[detailTab] ?? "formatted";
21538
21553
  const activeTagFilterCount = countTagFilters(filters.tags);
@@ -21625,7 +21640,7 @@ function App() {
21625
21640
  if (!node) {
21626
21641
  return;
21627
21642
  }
21628
- 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);
21629
21644
  (0, import_react3.startTransition)(() => {
21630
21645
  setSelectedNodeId(nodeId);
21631
21646
  if (nextTraceId !== selectedTraceId) {
@@ -21777,7 +21792,7 @@ function App() {
21777
21792
  selectedNodeId,
21778
21793
  selectedPathIds,
21779
21794
  selectedTraceId,
21780
- sessionNavById,
21795
+ sessionNavById: sessionNavById2,
21781
21796
  totalCount: allSessionCount,
21782
21797
  traceById
21783
21798
  }
@@ -21807,6 +21822,7 @@ function App() {
21807
21822
  {
21808
21823
  activeTab,
21809
21824
  detail,
21825
+ detailPath: selectedTracePath.length ? selectedTracePath : selectedNodePath,
21810
21826
  detailTabs,
21811
21827
  fallbackTrace: selectedTraceSummary,
21812
21828
  jsonMode: activeTabJsonMode,
@@ -21819,7 +21835,8 @@ function App() {
21819
21835
  ...current,
21820
21836
  [tabId]: (current[tabId] ?? "formatted") === "formatted" ? "raw" : "formatted"
21821
21837
  }));
21822
- })
21838
+ }),
21839
+ traceById
21823
21840
  }
21824
21841
  ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardContent, { className: "content-scroll", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
21825
21842
  EmptyState,
@@ -21890,7 +21907,7 @@ function SessionTreeNavigator({
21890
21907
  selectedNodeId,
21891
21908
  selectedPathIds,
21892
21909
  selectedTraceId,
21893
- sessionNavById,
21910
+ sessionNavById: sessionNavById2,
21894
21911
  totalCount,
21895
21912
  traceById
21896
21913
  }) {
@@ -21919,7 +21936,7 @@ function SessionTreeNavigator({
21919
21936
  selectedNodeId,
21920
21937
  selectedPathIds,
21921
21938
  selectedTraceId,
21922
- sessionNavById,
21939
+ sessionNavById: sessionNavById2,
21923
21940
  traceById
21924
21941
  }
21925
21942
  ) })
@@ -21954,7 +21971,7 @@ function HierarchyTree({
21954
21971
  selectedNodeId,
21955
21972
  selectedPathIds,
21956
21973
  selectedTraceId,
21957
- sessionNavById,
21974
+ sessionNavById: sessionNavById2,
21958
21975
  traceById
21959
21976
  }) {
21960
21977
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "tree-root", children: nodes.map((node) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -21970,7 +21987,7 @@ function HierarchyTree({
21970
21987
  selectedNodeId,
21971
21988
  selectedPathIds,
21972
21989
  selectedTraceId,
21973
- sessionNavById,
21990
+ sessionNavById: sessionNavById2,
21974
21991
  traceById
21975
21992
  },
21976
21993
  node.id
@@ -21987,7 +22004,7 @@ function HierarchyTreeNode({
21987
22004
  selectedNodeId,
21988
22005
  selectedPathIds,
21989
22006
  selectedTraceId,
21990
- sessionNavById,
22007
+ sessionNavById: sessionNavById2,
21991
22008
  traceById
21992
22009
  }) {
21993
22010
  const isExpandable = node.children.length > 0;
@@ -21997,20 +22014,28 @@ function HierarchyTreeNode({
21997
22014
  const isInPath = selectedPathIds.has(node.id);
21998
22015
  const nodeCopy = getHierarchyNodeCopy(node, traceById);
21999
22016
  const trace = node.meta.traceId ? traceById.get(node.meta.traceId) ?? null : null;
22000
- const sessionNavItem = node.type === "session" ? sessionNavById.get(node.id) ?? null : null;
22017
+ const sessionNavItem = node.type === "session" ? sessionNavById2.get(node.id) ?? null : null;
22001
22018
  if (node.type === "trace" && trace) {
22002
22019
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22003
- TraceHierarchyLeaf,
22020
+ TraceHierarchyNode,
22004
22021
  {
22022
+ defaultExpandedNodeIds,
22005
22023
  depth,
22024
+ expandedNodeOverrides,
22006
22025
  maxDurationMs,
22007
22026
  node,
22008
22027
  nodeCopy,
22009
22028
  onSelect,
22029
+ onToggle,
22010
22030
  inPath: isInPath,
22031
+ isExpanded,
22011
22032
  selected: selectedNodeId === node.id,
22033
+ selectedNodeId,
22034
+ selectedPathIds,
22012
22035
  selectedTrace: selectedTraceId === trace.id,
22013
- trace
22036
+ selectedTraceId,
22037
+ trace,
22038
+ traceById
22014
22039
  }
22015
22040
  );
22016
22041
  }
@@ -22031,7 +22056,7 @@ function HierarchyTreeNode({
22031
22056
  selectedNodeId,
22032
22057
  selectedPathIds,
22033
22058
  selectedTraceId,
22034
- sessionNavById,
22059
+ sessionNavById: sessionNavById2,
22035
22060
  traceById
22036
22061
  }
22037
22062
  );
@@ -22106,7 +22131,7 @@ function HierarchyTreeNode({
22106
22131
  selectedNodeId,
22107
22132
  selectedPathIds,
22108
22133
  selectedTraceId,
22109
- sessionNavById,
22134
+ sessionNavById: sessionNavById2,
22110
22135
  traceById
22111
22136
  },
22112
22137
  child.id
@@ -22129,7 +22154,7 @@ function SessionHierarchyBranch({
22129
22154
  selectedNodeId,
22130
22155
  selectedPathIds,
22131
22156
  selectedTraceId,
22132
- sessionNavById,
22157
+ sessionNavById: sessionNavById2,
22133
22158
  traceById
22134
22159
  }) {
22135
22160
  const detailLabel = formatList([
@@ -22247,7 +22272,7 @@ function SessionHierarchyBranch({
22247
22272
  selectedNodeId,
22248
22273
  selectedPathIds,
22249
22274
  selectedTraceId,
22250
- sessionNavById,
22275
+ sessionNavById: sessionNavById2,
22251
22276
  traceById
22252
22277
  },
22253
22278
  child.id
@@ -22256,59 +22281,105 @@ function SessionHierarchyBranch({
22256
22281
  }
22257
22282
  );
22258
22283
  }
22259
- function TraceHierarchyLeaf({
22284
+ function TraceHierarchyNode({
22285
+ defaultExpandedNodeIds,
22260
22286
  depth,
22287
+ expandedNodeOverrides,
22261
22288
  inPath,
22289
+ isExpanded,
22262
22290
  maxDurationMs,
22263
22291
  node,
22264
22292
  nodeCopy,
22265
22293
  onSelect,
22294
+ onToggle,
22266
22295
  selected,
22296
+ selectedNodeId,
22297
+ selectedPathIds,
22267
22298
  selectedTrace,
22268
- trace
22299
+ selectedTraceId,
22300
+ trace,
22301
+ traceById
22269
22302
  }) {
22270
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22303
+ const isExpandable = node.children.length > 0;
22304
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
22271
22305
  "div",
22272
22306
  {
22273
22307
  className: "tree-node-wrap tree-trace-wrap",
22274
22308
  style: { "--depth": String(depth) },
22275
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22276
- "div",
22277
- {
22278
- className: clsx_default(
22279
- "tree-node-card is-trace",
22280
- inPath && "is-in-path",
22281
- selected && "is-active",
22282
- selectedTrace && "is-detail-trace"
22283
- ),
22284
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
22285
- "button",
22286
- {
22287
- type: "button",
22288
- className: "tree-node-select tree-trace-select",
22289
- onClick: () => onSelect(node),
22290
- children: [
22291
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "tree-node-copy", children: [
22292
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "trace-nav-kicker", children: getTraceActorLabel(trace) }),
22293
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "tree-node-label", children: nodeCopy.label }),
22294
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "tree-node-meta", children: formatList([
22295
- formatTimelineTimestamp(trace.startedAt),
22296
- nodeCopy.meta
22297
- ]) })
22298
- ] }),
22299
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22300
- TraceElapsedBar,
22301
- {
22302
- compact: true,
22303
- durationMs: trace.durationMs,
22304
- maxDurationMs
22305
- }
22306
- )
22307
- ]
22308
- }
22309
- )
22310
- }
22311
- )
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
+ ]
22312
22383
  }
22313
22384
  );
22314
22385
  }
@@ -22523,6 +22594,7 @@ function HierarchyTimelineRows({
22523
22594
  function TraceDetailPanel({
22524
22595
  activeTab,
22525
22596
  detail,
22597
+ detailPath,
22526
22598
  detailTabs,
22527
22599
  fallbackTrace,
22528
22600
  jsonMode,
@@ -22531,11 +22603,12 @@ function TraceDetailPanel({
22531
22603
  onBack,
22532
22604
  onNavigateHierarchyNode,
22533
22605
  onTabChange,
22534
- onToggleJsonMode
22606
+ onToggleJsonMode,
22607
+ traceById
22535
22608
  }) {
22536
22609
  const traceDetailPrimaryRef = (0, import_react3.useRef)(null);
22537
22610
  const [showInlineContextRail, setShowInlineContextRail] = (0, import_react3.useState)(false);
22538
- const detailCopy = detail ? getTraceDisplayCopy(detail) : fallbackTrace ? getTraceDisplayCopy(fallbackTrace) : null;
22611
+ const detailCopy = detailPath.length ? getHierarchyPathDisplayCopy(detailPath, traceById) : detail ? getTraceDisplayCopy(detail) : fallbackTrace ? getTraceDisplayCopy(fallbackTrace) : null;
22539
22612
  const detailStatus = detail?.status ?? fallbackTrace?.status ?? null;
22540
22613
  const detailDuration = detail ? formatTraceDuration(detail) : fallbackTrace ? fallbackTrace.durationMs == null ? "Running" : `${fallbackTrace.durationMs} ms` : null;
22541
22614
  const detailSubtitle = formatTraceProviderSummary(detail ?? fallbackTrace);
@@ -22846,6 +22919,24 @@ function renderTabContent(tab, detail, jsonMode, onApplyTagFilter, onApplyTraceF
22846
22919
  }
22847
22920
  ) : null
22848
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
+ );
22849
22940
  default:
22850
22941
  return null;
22851
22942
  }
@@ -23764,6 +23855,7 @@ function buildDetailTabs(detail) {
23764
23855
  if (detail.stream) {
23765
23856
  tabs.push({ id: "stream", label: "Stream" });
23766
23857
  }
23858
+ tabs.push({ id: "otel", label: "OTel" });
23767
23859
  return tabs;
23768
23860
  }
23769
23861
  function getHierarchyNodeCopy(node, traceById) {
@@ -23822,8 +23914,8 @@ function getHierarchyNodeCopy(node, traceById) {
23822
23914
  trace.provider,
23823
23915
  trace.model,
23824
23916
  formatCostSummaryLabel(
23825
- trace.costUsd ?? getHierarchyNodeCostUsd(node),
23826
- false
23917
+ getHierarchyNodeCostUsd(node) ?? trace.costUsd,
23918
+ node.count > 1
23827
23919
  )
23828
23920
  ]) : formatList([
23829
23921
  formatCountLabel(node.count, "call"),
@@ -23846,55 +23938,24 @@ function getHierarchyNodeCopy(node, traceById) {
23846
23938
  }
23847
23939
  }
23848
23940
  function getTraceDisplayCopy(trace) {
23849
- const actor = trace.hierarchy.childActorId || trace.hierarchy.rootActorId;
23850
- const breadcrumbs = getTraceBreadcrumbs(trace);
23851
- const pathParts = breadcrumbs.map((item) => item.label);
23852
23941
  return {
23853
- breadcrumbs,
23854
- path: pathParts.join(" / "),
23855
- subtitle: formatList([actor, trace.provider, trace.model]),
23942
+ breadcrumbs: [],
23943
+ path: "",
23856
23944
  title: getTraceTitle(trace)
23857
23945
  };
23858
23946
  }
23859
- function getTraceBreadcrumbs(trace) {
23860
- const sessionId = trace.hierarchy.sessionId || "unknown-session";
23861
- const rootActorId = trace.hierarchy.rootActorId || "unknown-actor";
23862
- const breadcrumbs = [
23863
- {
23864
- label: `Session ${shortId2(sessionId)}`,
23865
- nodeId: `session:${sessionId}`
23866
- },
23867
- {
23868
- label: rootActorId,
23869
- nodeId: `actor:${sessionId}:${rootActorId}`
23870
- }
23871
- ];
23872
- if (trace.kind === "guardrail") {
23873
- const label = `${capitalize(trace.hierarchy.guardrailPhase || "guardrail")} guardrail`;
23874
- breadcrumbs.push({
23875
- label,
23876
- nodeId: `guardrail:${sessionId}:${rootActorId}:${trace.hierarchy.guardrailType || label.toLowerCase()}`
23877
- });
23878
- } else if (trace.kind === "child-actor" && trace.hierarchy.childActorId) {
23879
- breadcrumbs.push({
23880
- label: `Child actor: ${trace.hierarchy.childActorId}`,
23881
- nodeId: `child-actor:${sessionId}:${rootActorId}:${trace.hierarchy.childActorId}`
23882
- });
23883
- } else if (trace.kind === "stage") {
23884
- if (trace.hierarchy.childActorId) {
23885
- breadcrumbs.push({
23886
- label: `Child actor: ${trace.hierarchy.childActorId}`,
23887
- nodeId: `child-actor:${sessionId}:${rootActorId}:${trace.hierarchy.childActorId}`
23888
- });
23889
- }
23890
- if (trace.hierarchy.stage) {
23891
- breadcrumbs.push({
23892
- label: `Stage: ${trace.hierarchy.stage}`,
23893
- nodeId: `stage:${sessionId}:${rootActorId}:${trace.hierarchy.childActorId || "root"}:${trace.hierarchy.stage}`
23894
- });
23895
- }
23896
- }
23897
- 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
+ };
23898
23959
  }
23899
23960
  function formatTraceProviderSummary(trace) {
23900
23961
  if (!trace) {
@@ -24045,12 +24106,12 @@ function patchHierarchyNode(node, traceId, previousSummary, nextSummary, costDel
24045
24106
  let nextLabel = node.label;
24046
24107
  if (containsTrace) {
24047
24108
  if (node.type === "trace" && node.meta.traceId === traceId) {
24048
- nextMeta.costUsd = nextSummary.costUsd;
24049
24109
  nextMeta.model = nextSummary.model;
24050
24110
  nextMeta.provider = nextSummary.provider;
24051
24111
  nextMeta.status = nextSummary.status;
24052
24112
  nextLabel = nextSummary.model ? `${nextSummary.model} ${nextSummary.mode}` : traceId;
24053
- } else if (costDelta !== 0 || previousSummary?.costUsd !== null || nextSummary.costUsd !== null) {
24113
+ }
24114
+ if (costDelta !== 0 || previousSummary?.costUsd !== null || nextSummary.costUsd !== null) {
24054
24115
  const currentCost = typeof nextMeta.costUsd === "number" && Number.isFinite(nextMeta.costUsd) ? nextMeta.costUsd : 0;
24055
24116
  nextMeta.costUsd = roundCostUsd2(currentCost + costDelta);
24056
24117
  }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { type ChatModelLike, type LocalLLMTracer, type OpenAIChatCompletionCreateParamsLike, type OpenAIClientLike, type TraceConfig, type TraceContext, type TraceRequest } from './types';
2
- export type { ChatModelLike, HierarchyNode, HierarchyResponse, LocalLLMTracer, NormalizedTraceContext, OpenAIChatCompletionCreateParamsLike, OpenAIChatCompletionStreamLike, OpenAIClientLike, TraceConfig, TraceContext, TraceEvent, TraceFilters, TraceHierarchy, TraceListResponse, TraceMode, TraceRecord, TraceRequest, TraceServer, TraceStatus, TraceSummary, TraceTags, UIReloadEvent, } from './types';
1
+ import { type ChatModelLike, type LocalLLMTracer, type OpenAIChatCompletionCreateParamsLike, type OpenAIClientLike, type SpanEventInput, type SpanStartOptions, type TraceConfig, type TraceContext } from './types';
2
+ export type { ChatModelLike, HierarchyNode, HierarchyResponse, LocalLLMTracer, NormalizedTraceContext, OpenAIChatCompletionCreateParamsLike, OpenAIChatCompletionStreamLike, OpenAIClientLike, SpanAttributes, SpanContext, SpanEvent, SpanEventInput, SpanKind, SpanStartOptions, SpanStatus, SpanStatusCode, TraceConfig, TraceContext, TraceEvent, TraceFilters, TraceHierarchy, TraceListResponse, TraceMode, TraceRecord, TraceRequest, TraceServer, TraceStatus, TraceSummary, TraceTags, UIReloadEvent, } from './types';
3
3
  export declare function isTraceEnabled(): boolean;
4
4
  export declare function getLocalLLMTracer(config?: TraceConfig): LocalLLMTracer;
5
5
  export declare function startTraceServer(config?: TraceConfig): Promise<{
@@ -7,12 +7,10 @@ export declare function startTraceServer(config?: TraceConfig): Promise<{
7
7
  port: number;
8
8
  url: string;
9
9
  }>;
10
- export declare function recordInvokeStart(context: TraceContext, request: TraceRequest, config?: TraceConfig): string;
11
- export declare function recordInvokeFinish(traceId: string, response: unknown, config?: TraceConfig): void;
12
- export declare function recordStreamStart(context: TraceContext, request: TraceRequest, config?: TraceConfig): string;
13
- export declare function recordStreamChunk(traceId: string, chunk: unknown, config?: TraceConfig): void;
14
- export declare function recordStreamFinish(traceId: string, chunk: unknown, config?: TraceConfig): void;
15
- export declare function recordError(traceId: string, error: unknown, config?: TraceConfig): void;
10
+ export declare function startSpan(context: TraceContext, options?: SpanStartOptions, config?: TraceConfig): string;
11
+ export declare function endSpan(spanId: string, response: unknown, config?: TraceConfig): void;
12
+ export declare function addSpanEvent(spanId: string, event: SpanEventInput, config?: TraceConfig): void;
13
+ export declare function recordException(spanId: string, error: unknown, config?: TraceConfig): void;
16
14
  export declare function __resetLocalLLMTracerForTests(): void;
17
15
  export declare function wrapChatModel<TModel extends ChatModelLike<TInput, TOptions, TValue, TChunk>, TInput = any, TOptions = any, TValue = any, TChunk = any>(model: TModel, getContext: () => TraceContext, config?: TraceConfig): TModel;
18
16
  export declare function wrapOpenAIClient<TClient extends OpenAIClientLike<TParams, TOptions, TResponse, TChunk>, TParams extends OpenAIChatCompletionCreateParamsLike = OpenAIChatCompletionCreateParamsLike, TOptions = Record<string, any>, TResponse = any, TChunk = any>(client: TClient, getContext: () => TraceContext, config?: TraceConfig): TClient;