@mtharrison/loupe 1.3.0 → 1.5.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) {
@@ -21459,7 +21469,7 @@ function App() {
21459
21469
  () => deriveSessionNavItems(sessionNodes, traceById),
21460
21470
  [sessionNodes, traceById]
21461
21471
  );
21462
- const sessionNavById = (0, import_react3.useMemo)(
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(selectedNodePath.map((node) => node.id)),
21482
- [selectedNodePath]
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
  ) })
@@ -21934,9 +21949,6 @@ function getSessionNavBadge(item) {
21934
21949
  if (item.status === "pending") {
21935
21950
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, { variant: "warning", children: "Running" });
21936
21951
  }
21937
- if (item.hasHighlights) {
21938
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, { variant: "secondary", semantic: "guardrail", children: "Insight" });
21939
- }
21940
21952
  return null;
21941
21953
  }
21942
21954
  function BackgroundGlow() {
@@ -21956,7 +21968,7 @@ function HierarchyTree({
21956
21968
  selectedNodeId,
21957
21969
  selectedPathIds,
21958
21970
  selectedTraceId,
21959
- sessionNavById,
21971
+ sessionNavById: sessionNavById2,
21960
21972
  traceById
21961
21973
  }) {
21962
21974
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "tree-root", children: nodes.map((node) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -21972,7 +21984,7 @@ function HierarchyTree({
21972
21984
  selectedNodeId,
21973
21985
  selectedPathIds,
21974
21986
  selectedTraceId,
21975
- sessionNavById,
21987
+ sessionNavById: sessionNavById2,
21976
21988
  traceById
21977
21989
  },
21978
21990
  node.id
@@ -21989,7 +22001,7 @@ function HierarchyTreeNode({
21989
22001
  selectedNodeId,
21990
22002
  selectedPathIds,
21991
22003
  selectedTraceId,
21992
- sessionNavById,
22004
+ sessionNavById: sessionNavById2,
21993
22005
  traceById
21994
22006
  }) {
21995
22007
  const isExpandable = node.children.length > 0;
@@ -21999,20 +22011,28 @@ function HierarchyTreeNode({
21999
22011
  const isInPath = selectedPathIds.has(node.id);
22000
22012
  const nodeCopy = getHierarchyNodeCopy(node, traceById);
22001
22013
  const trace = node.meta.traceId ? traceById.get(node.meta.traceId) ?? null : null;
22002
- const sessionNavItem = node.type === "session" ? sessionNavById.get(node.id) ?? null : null;
22014
+ const sessionNavItem = node.type === "session" ? sessionNavById2.get(node.id) ?? null : null;
22003
22015
  if (node.type === "trace" && trace) {
22004
22016
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22005
- TraceHierarchyLeaf,
22017
+ TraceHierarchyNode,
22006
22018
  {
22019
+ defaultExpandedNodeIds,
22007
22020
  depth,
22021
+ expandedNodeOverrides,
22008
22022
  maxDurationMs,
22009
22023
  node,
22010
22024
  nodeCopy,
22011
22025
  onSelect,
22026
+ onToggle,
22012
22027
  inPath: isInPath,
22028
+ isExpanded,
22013
22029
  selected: selectedNodeId === node.id,
22030
+ selectedNodeId,
22031
+ selectedPathIds,
22014
22032
  selectedTrace: selectedTraceId === trace.id,
22015
- trace
22033
+ selectedTraceId,
22034
+ trace,
22035
+ traceById
22016
22036
  }
22017
22037
  );
22018
22038
  }
@@ -22033,7 +22053,7 @@ function HierarchyTreeNode({
22033
22053
  selectedNodeId,
22034
22054
  selectedPathIds,
22035
22055
  selectedTraceId,
22036
- sessionNavById,
22056
+ sessionNavById: sessionNavById2,
22037
22057
  traceById
22038
22058
  }
22039
22059
  );
@@ -22108,7 +22128,7 @@ function HierarchyTreeNode({
22108
22128
  selectedNodeId,
22109
22129
  selectedPathIds,
22110
22130
  selectedTraceId,
22111
- sessionNavById,
22131
+ sessionNavById: sessionNavById2,
22112
22132
  traceById
22113
22133
  },
22114
22134
  child.id
@@ -22131,7 +22151,7 @@ function SessionHierarchyBranch({
22131
22151
  selectedNodeId,
22132
22152
  selectedPathIds,
22133
22153
  selectedTraceId,
22134
- sessionNavById,
22154
+ sessionNavById: sessionNavById2,
22135
22155
  traceById
22136
22156
  }) {
22137
22157
  const detailLabel = formatList([
@@ -22146,8 +22166,27 @@ function SessionHierarchyBranch({
22146
22166
  sessionSelectedPath,
22147
22167
  selectedTraceId
22148
22168
  );
22169
+ const [showGuardrails, setShowGuardrails] = (0, import_react3.useState)(true);
22149
22170
  const sessionTimelineRow = sessionTimelineModel?.rows[0] ?? null;
22150
- const showEmbeddedTimeline = isExpanded && Boolean(sessionTimelineModel?.rows.length);
22171
+ const embeddedTimelineRows = (0, import_react3.useMemo)(() => {
22172
+ if (!sessionTimelineModel) {
22173
+ return [];
22174
+ }
22175
+ const visibleRows = sessionTimelineModel.rows.slice(1);
22176
+ const baseDepth = visibleRows[0]?.depth ?? 0;
22177
+ if (baseDepth <= 0) {
22178
+ return visibleRows;
22179
+ }
22180
+ return visibleRows.map((row) => ({
22181
+ ...row,
22182
+ depth: Math.max(0, row.depth - baseDepth),
22183
+ ancestorContinuations: row.ancestorContinuations.slice(baseDepth)
22184
+ }));
22185
+ }, [sessionTimelineModel]);
22186
+ const guardrailRowCount = embeddedTimelineRows.filter(
22187
+ (row) => isGuardrailTimelineRow(row)
22188
+ ).length;
22189
+ const showEmbeddedTimeline = isExpanded && embeddedTimelineRows.length > 0;
22151
22190
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
22152
22191
  "div",
22153
22192
  {
@@ -22215,28 +22254,49 @@ function SessionHierarchyBranch({
22215
22254
  ]
22216
22255
  }
22217
22256
  ),
22218
- showEmbeddedTimeline && sessionTimelineModel ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "session-tree-timeline-shell", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22219
- "div",
22220
- {
22221
- className: "session-tree-timeline-list",
22222
- role: "list",
22223
- "aria-label": "Session timeline",
22224
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22225
- HierarchyTimelineRows,
22257
+ showEmbeddedTimeline && sessionTimelineModel ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "session-tree-timeline-shell", children: [
22258
+ guardrailRowCount ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "session-tree-timeline-toolbar", children: [
22259
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "session-tree-timeline-toolbar-copy", children: [
22260
+ formatCountLabel(guardrailRowCount, "guardrail check"),
22261
+ " ",
22262
+ showGuardrails ? "compact" : "hidden"
22263
+ ] }),
22264
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22265
+ BadgeButton,
22226
22266
  {
22227
- className: "is-embedded",
22228
- model: sessionTimelineModel,
22229
- onSelectRow: (nodeId) => {
22230
- const selectedTimelineNode = findSessionNodeById([node], nodeId);
22231
- if (selectedTimelineNode) {
22232
- onSelect(selectedTimelineNode);
22233
- }
22234
- },
22235
- rows: sessionTimelineModel.rows.slice(1)
22267
+ variant: "outline",
22268
+ semantic: "guardrail",
22269
+ onClick: () => (0, import_react3.startTransition)(
22270
+ () => setShowGuardrails((current) => !current)
22271
+ ),
22272
+ children: showGuardrails ? "Hide guardrails" : "Show guardrails"
22236
22273
  }
22237
22274
  )
22238
- }
22239
- ) }) : isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "tree-node-children", children: node.children.map((child) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22275
+ ] }) : null,
22276
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22277
+ "div",
22278
+ {
22279
+ className: "session-tree-timeline-list",
22280
+ role: "list",
22281
+ "aria-label": "Session timeline",
22282
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22283
+ HierarchyTimelineRows,
22284
+ {
22285
+ className: "is-embedded",
22286
+ guardrailVisibility: showGuardrails ? "compact" : "hidden",
22287
+ model: sessionTimelineModel,
22288
+ onSelectRow: (nodeId) => {
22289
+ const selectedTimelineNode = findSessionNodeById([node], nodeId);
22290
+ if (selectedTimelineNode) {
22291
+ onSelect(selectedTimelineNode);
22292
+ }
22293
+ },
22294
+ rows: embeddedTimelineRows
22295
+ }
22296
+ )
22297
+ }
22298
+ )
22299
+ ] }) : isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "tree-node-children", children: node.children.map((child) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22240
22300
  HierarchyTreeNode,
22241
22301
  {
22242
22302
  defaultExpandedNodeIds,
@@ -22249,7 +22309,7 @@ function SessionHierarchyBranch({
22249
22309
  selectedNodeId,
22250
22310
  selectedPathIds,
22251
22311
  selectedTraceId,
22252
- sessionNavById,
22312
+ sessionNavById: sessionNavById2,
22253
22313
  traceById
22254
22314
  },
22255
22315
  child.id
@@ -22258,59 +22318,105 @@ function SessionHierarchyBranch({
22258
22318
  }
22259
22319
  );
22260
22320
  }
22261
- function TraceHierarchyLeaf({
22321
+ function TraceHierarchyNode({
22322
+ defaultExpandedNodeIds,
22262
22323
  depth,
22324
+ expandedNodeOverrides,
22263
22325
  inPath,
22326
+ isExpanded,
22264
22327
  maxDurationMs,
22265
22328
  node,
22266
22329
  nodeCopy,
22267
22330
  onSelect,
22331
+ onToggle,
22268
22332
  selected,
22333
+ selectedNodeId,
22334
+ selectedPathIds,
22269
22335
  selectedTrace,
22270
- trace
22336
+ selectedTraceId,
22337
+ trace,
22338
+ traceById
22271
22339
  }) {
22272
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22340
+ const isExpandable = node.children.length > 0;
22341
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
22273
22342
  "div",
22274
22343
  {
22275
22344
  className: "tree-node-wrap tree-trace-wrap",
22276
22345
  style: { "--depth": String(depth) },
22277
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22278
- "div",
22279
- {
22280
- className: clsx_default(
22281
- "tree-node-card is-trace",
22282
- inPath && "is-in-path",
22283
- selected && "is-active",
22284
- selectedTrace && "is-detail-trace"
22285
- ),
22286
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
22287
- "button",
22288
- {
22289
- type: "button",
22290
- className: "tree-node-select tree-trace-select",
22291
- onClick: () => onSelect(node),
22292
- children: [
22293
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "tree-node-copy", children: [
22294
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "trace-nav-kicker", children: getTraceActorLabel(trace) }),
22295
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "tree-node-label", children: nodeCopy.label }),
22296
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "tree-node-meta", children: formatList([
22297
- formatTimelineTimestamp(trace.startedAt),
22298
- nodeCopy.meta
22299
- ]) })
22300
- ] }),
22301
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22302
- TraceElapsedBar,
22303
- {
22304
- compact: true,
22305
- durationMs: trace.durationMs,
22306
- maxDurationMs
22307
- }
22308
- )
22309
- ]
22310
- }
22311
- )
22312
- }
22313
- )
22346
+ children: [
22347
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
22348
+ "div",
22349
+ {
22350
+ className: clsx_default(
22351
+ "tree-node-card is-trace",
22352
+ inPath && "is-in-path",
22353
+ selected && "is-active",
22354
+ selectedTrace && "is-detail-trace"
22355
+ ),
22356
+ children: [
22357
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22358
+ "button",
22359
+ {
22360
+ type: "button",
22361
+ className: clsx_default("tree-node-toggle", !isExpandable && "is-static"),
22362
+ disabled: !isExpandable,
22363
+ onClick: () => {
22364
+ if (isExpandable) {
22365
+ onToggle(node.id);
22366
+ }
22367
+ },
22368
+ "aria-label": isExpandable ? `${isExpanded ? "Collapse" : "Expand"} ${nodeCopy.label}` : void 0,
22369
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronRight, { className: clsx_default(isExpanded && "is-open") })
22370
+ }
22371
+ ),
22372
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
22373
+ "button",
22374
+ {
22375
+ type: "button",
22376
+ className: "tree-node-select tree-trace-select",
22377
+ onClick: () => onSelect(node),
22378
+ children: [
22379
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "tree-node-copy", children: [
22380
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "trace-nav-kicker", children: getTraceActorLabel(trace) }),
22381
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "tree-node-label", children: nodeCopy.label }),
22382
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "tree-node-meta", children: formatList([
22383
+ formatTimelineTimestamp(trace.startedAt),
22384
+ nodeCopy.meta
22385
+ ]) })
22386
+ ] }),
22387
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22388
+ TraceElapsedBar,
22389
+ {
22390
+ compact: true,
22391
+ durationMs: trace.durationMs,
22392
+ maxDurationMs
22393
+ }
22394
+ )
22395
+ ]
22396
+ }
22397
+ )
22398
+ ]
22399
+ }
22400
+ ),
22401
+ isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "tree-node-children", children: node.children.map((child) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22402
+ HierarchyTreeNode,
22403
+ {
22404
+ defaultExpandedNodeIds,
22405
+ depth: depth + 1,
22406
+ expandedNodeOverrides,
22407
+ maxDurationMs,
22408
+ node: child,
22409
+ onSelect,
22410
+ onToggle,
22411
+ selectedNodeId,
22412
+ selectedPathIds,
22413
+ selectedTraceId,
22414
+ sessionNavById,
22415
+ traceById
22416
+ },
22417
+ child.id
22418
+ )) }) : null
22419
+ ]
22314
22420
  }
22315
22421
  );
22316
22422
  }
@@ -22383,24 +22489,45 @@ function SessionTreeTimelineBar({
22383
22489
  }
22384
22490
  function HierarchyTimelineRows({
22385
22491
  className,
22492
+ guardrailVisibility = "compact",
22386
22493
  model,
22387
22494
  onSelectRow,
22388
22495
  rows
22389
22496
  }) {
22390
- return rows.map((row) => {
22391
- const isTraceRow = row.type === "trace";
22497
+ const [expandedGuardrailGroups, setExpandedGuardrailGroups] = (0, import_react3.useState)({});
22498
+ const displayItems = (0, import_react3.useMemo)(
22499
+ () => buildHierarchyTimelineDisplayItems(
22500
+ rows,
22501
+ guardrailVisibility,
22502
+ expandedGuardrailGroups
22503
+ ),
22504
+ [expandedGuardrailGroups, guardrailVisibility, rows]
22505
+ );
22506
+ if (!displayItems.length) {
22507
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hierarchy-timeline-empty-state", children: "No visible rows. Show guardrails to inspect them." });
22508
+ }
22509
+ return displayItems.map((item) => {
22510
+ const row = item.row;
22511
+ const isEmbeddedRow = className?.includes("is-embedded") ?? false;
22512
+ const hasSemanticIndent = isEmbeddedRow && row.type === "trace" && row.traceKind === "child-actor";
22513
+ const isTraceRow = item.kind === "row" && row.type === "trace";
22392
22514
  const rowClassName = cn(
22393
22515
  "hierarchy-timeline-row",
22394
22516
  className,
22395
- isTraceRow ? "is-clickable" : "is-structure",
22517
+ (isTraceRow || item.kind === "guardrail-group") && "is-clickable",
22518
+ !isTraceRow && item.kind !== "guardrail-group" && "is-structure",
22396
22519
  row.depth === 0 && "is-root",
22397
22520
  row.isActive && "is-active",
22398
22521
  row.isDetailTrace && "is-detail-trace",
22399
22522
  row.isInPath && "is-in-path",
22523
+ hasSemanticIndent && "has-semantic-indent is-child-actor-trace",
22524
+ row.traceKind === "guardrail" && "is-guardrail-trace",
22525
+ item.kind === "guardrail-group" && "is-structure is-guardrail-group",
22526
+ item.kind === "row" && item.compact && "is-guardrail-compact",
22400
22527
  `is-${row.type.replace(/[^a-z0-9-]/gi, "-")}`
22401
22528
  );
22402
22529
  const rowStyle = {
22403
- "--timeline-depth": String(row.depth),
22530
+ "--timeline-depth": String(row.depth + (hasSemanticIndent ? 1 : 0)),
22404
22531
  "--timeline-offset": String(
22405
22532
  model.durationMs > 0 ? row.offsetMs / model.durationMs : 0
22406
22533
  ),
@@ -22456,26 +22583,11 @@ function HierarchyTimelineRows({
22456
22583
  {
22457
22584
  variant: "secondary",
22458
22585
  className: "hierarchy-timeline-pill",
22459
- semantic: row.badge,
22460
- children: row.badge
22586
+ semantic: item.kind === "guardrail-group" || row.traceKind === "guardrail" ? "guardrail" : row.badge,
22587
+ children: item.kind === "guardrail-group" ? "Guardrail" : row.badge
22461
22588
  }
22462
22589
  ),
22463
- row.hasStructuredInput ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22464
- "span",
22465
- {
22466
- className: "hierarchy-timeline-row-flag",
22467
- title: "Structured input detected for this call",
22468
- children: "Structured input"
22469
- }
22470
- ) : null,
22471
- row.hasHighlights && !row.hasStructuredInput ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22472
- "span",
22473
- {
22474
- className: "hierarchy-timeline-row-flag is-highlight",
22475
- title: "Trace insights available",
22476
- children: "Insight"
22477
- }
22478
- ) : null
22590
+ item.kind === "guardrail-group" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "hierarchy-timeline-row-toggle-label", children: item.isExpanded ? "Hide" : `Show ${item.itemCount}` }) : null
22479
22591
  ] }),
22480
22592
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hierarchy-timeline-row-meta", children: row.meta })
22481
22593
  ] })
@@ -22485,6 +22597,35 @@ function HierarchyTimelineRows({
22485
22597
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hierarchy-timeline-row-duration", children: formatElapsedLabel(row.durationMs) })
22486
22598
  ] })
22487
22599
  ] });
22600
+ if (item.kind === "guardrail-group") {
22601
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22602
+ "div",
22603
+ {
22604
+ role: "listitem",
22605
+ title: buildHierarchyTimelineRowTooltip(row),
22606
+ "data-hierarchy-row-id": row.id,
22607
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22608
+ "button",
22609
+ {
22610
+ type: "button",
22611
+ className: rowClassName,
22612
+ style: rowStyle,
22613
+ onClick: () => (0, import_react3.startTransition)(
22614
+ () => setExpandedGuardrailGroups(
22615
+ (current) => current[item.id] ? Object.fromEntries(
22616
+ Object.entries(current).filter(
22617
+ ([key]) => key !== item.id
22618
+ )
22619
+ ) : { ...current, [item.id]: true }
22620
+ )
22621
+ ),
22622
+ children: rowContent
22623
+ }
22624
+ )
22625
+ },
22626
+ item.id
22627
+ );
22628
+ }
22488
22629
  if (isTraceRow) {
22489
22630
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22490
22631
  "div",
@@ -22525,6 +22666,7 @@ function HierarchyTimelineRows({
22525
22666
  function TraceDetailPanel({
22526
22667
  activeTab,
22527
22668
  detail,
22669
+ detailPath,
22528
22670
  detailTabs,
22529
22671
  fallbackTrace,
22530
22672
  jsonMode,
@@ -22533,11 +22675,12 @@ function TraceDetailPanel({
22533
22675
  onBack,
22534
22676
  onNavigateHierarchyNode,
22535
22677
  onTabChange,
22536
- onToggleJsonMode
22678
+ onToggleJsonMode,
22679
+ traceById
22537
22680
  }) {
22538
22681
  const traceDetailPrimaryRef = (0, import_react3.useRef)(null);
22539
22682
  const [showInlineContextRail, setShowInlineContextRail] = (0, import_react3.useState)(false);
22540
- const detailCopy = detail ? getTraceDisplayCopy(detail) : fallbackTrace ? getTraceDisplayCopy(fallbackTrace) : null;
22683
+ const detailCopy = detailPath.length ? getHierarchyPathDisplayCopy(detailPath, traceById) : detail ? getTraceDisplayCopy(detail) : fallbackTrace ? getTraceDisplayCopy(fallbackTrace) : null;
22541
22684
  const detailStatus = detail?.status ?? fallbackTrace?.status ?? null;
22542
22685
  const detailDuration = detail ? formatTraceDuration(detail) : fallbackTrace ? fallbackTrace.durationMs == null ? "Running" : `${fallbackTrace.durationMs} ms` : null;
22543
22686
  const detailSubtitle = formatTraceProviderSummary(detail ?? fallbackTrace);
@@ -22736,10 +22879,8 @@ function renderTabContent(tab, detail, jsonMode, onApplyTagFilter, onApplyTraceF
22736
22879
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22737
22880
  MessagesCard,
22738
22881
  {
22739
- insights: detail.insights,
22740
22882
  title: "Request messages",
22741
- messages: requestMessages,
22742
- sourcePrefix: "request"
22883
+ messages: requestMessages
22743
22884
  }
22744
22885
  ),
22745
22886
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(JsonCard, { title: "Request options", value: detail.request.options }),
@@ -22768,10 +22909,8 @@ function renderTabContent(tab, detail, jsonMode, onApplyTagFilter, onApplyTraceF
22768
22909
  responseMessage ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22769
22910
  MessagesCard,
22770
22911
  {
22771
- insights: detail.insights,
22772
22912
  title: "Assistant message",
22773
- messages: [responseMessage],
22774
- sourcePrefix: "response"
22913
+ messages: [responseMessage]
22775
22914
  }
22776
22915
  ) : null,
22777
22916
  toolCalls.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ToolCallsCard, { title: "Tool calls", toolCalls }) : null,
@@ -22834,10 +22973,8 @@ function renderTabContent(tab, detail, jsonMode, onApplyTagFilter, onApplyTraceF
22834
22973
  detail.stream.reconstructed?.message ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22835
22974
  MessagesCard,
22836
22975
  {
22837
- insights: detail.insights,
22838
22976
  title: "Reconstructed output",
22839
- messages: [detail.stream.reconstructed.message],
22840
- sourcePrefix: "stream"
22977
+ messages: [detail.stream.reconstructed.message]
22841
22978
  }
22842
22979
  ) : null,
22843
22980
  detail.stream.reconstructed?.tool_calls?.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -22848,6 +22985,24 @@ function renderTabContent(tab, detail, jsonMode, onApplyTagFilter, onApplyTraceF
22848
22985
  }
22849
22986
  ) : null
22850
22987
  ] });
22988
+ case "otel":
22989
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22990
+ JsonCard,
22991
+ {
22992
+ title: "OTel span",
22993
+ value: {
22994
+ name: detail.name,
22995
+ spanContext: detail.spanContext,
22996
+ parentSpanId: detail.parentSpanId,
22997
+ spanKind: detail.spanKind,
22998
+ spanStatus: detail.spanStatus,
22999
+ startTime: detail.startedAt,
23000
+ endTime: detail.endedAt,
23001
+ attributes: detail.attributes,
23002
+ events: detail.events
23003
+ }
23004
+ }
23005
+ );
22851
23006
  default:
22852
23007
  return null;
22853
23008
  }
@@ -22863,7 +23018,6 @@ function ConversationView({
22863
23018
  );
22864
23019
  const costUsd = getUsageCostUsd(detail.usage);
22865
23020
  const costLabel = formatUsdCost(costUsd);
22866
- const traceInsights = detail.insights ?? EMPTY_TRACE_INSIGHTS;
22867
23021
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "conversation-layout", children: [
22868
23022
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "conversation-meta", children: [
22869
23023
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "conversation-meta-primary", children: [
@@ -22922,26 +23076,21 @@ function ConversationView({
22922
23076
  ) : null
22923
23077
  ] })
22924
23078
  ] }),
22925
- traceInsights.highlights.length || traceInsights.structuredInputs.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TraceInsightsSummary, { detail, insights: traceInsights }) : null,
22926
23079
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "conversation-thread", children: [
22927
23080
  requestMessages.length ? requestMessages.map((message, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22928
23081
  ConversationMessage,
22929
23082
  {
22930
- insights: traceInsights,
22931
- message,
22932
- source: `request:${index}`
23083
+ message
22933
23084
  },
22934
23085
  `${message.role}-${index}`
22935
23086
  )) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "muted-copy", children: "No request messages recorded." }),
22936
23087
  hasRenderableContent(responseMessage?.content) ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22937
23088
  ConversationMessage,
22938
23089
  {
22939
- insights: traceInsights,
22940
23090
  message: {
22941
23091
  role: responseMessage?.role || "assistant",
22942
23092
  content: responseMessage?.content
22943
- },
22944
- source: "response:0"
23093
+ }
22945
23094
  }
22946
23095
  ) : null,
22947
23096
  responseToolCalls.map((toolCall, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -22956,59 +23105,8 @@ function ConversationView({
22956
23105
  ] })
22957
23106
  ] });
22958
23107
  }
22959
- function TraceInsightsSummary({
22960
- detail,
22961
- insights
22962
- }) {
22963
- const title = detail.kind === "guardrail" ? "Why this guardrail ran" : "Trace insights";
22964
- const highlights = insights.highlights.filter(
22965
- (item) => item.kind !== "structured-input" || !insights.structuredInputs.length
22966
- );
22967
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card, { className: "detail-section trace-insights-card", children: [
22968
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardHeader, { children: [
22969
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardTitle, { children: title }),
22970
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardDescription, { children: "Deterministic signals extracted from the trace payload." })
22971
- ] }),
22972
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardContent, { className: "trace-insights-content", children: [
22973
- highlights.map((highlight, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
22974
- "div",
22975
- {
22976
- className: "trace-insight-item",
22977
- children: [
22978
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "trace-insight-item-header", children: [
22979
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, { semantic: "guardrail", variant: "secondary", children: highlight.title }),
22980
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "trace-insight-item-source", children: formatInsightSource(highlight.source) })
22981
- ] }),
22982
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "trace-insight-item-copy", children: highlight.description })
22983
- ]
22984
- },
22985
- `${highlight.kind}-${highlight.source}-${index}`
22986
- )),
22987
- insights.structuredInputs.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
22988
- "div",
22989
- {
22990
- className: "trace-insight-item is-structured",
22991
- children: [
22992
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "trace-insight-item-header", children: [
22993
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Badge, { semantic: "guardrail", variant: "secondary", children: [
22994
- "Structured ",
22995
- item.role,
22996
- " input"
22997
- ] }),
22998
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "trace-insight-item-tags", children: item.tags.map((tag) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, { variant: "outline", children: tag }, tag)) })
22999
- ] }),
23000
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "trace-insight-item-copy", children: item.snippet })
23001
- ]
23002
- },
23003
- `${item.role}-${item.snippet}-${index}`
23004
- ))
23005
- ] })
23006
- ] });
23007
- }
23008
23108
  function ConversationMessage({
23009
- insights = EMPTY_TRACE_INSIGHTS,
23010
- message,
23011
- source
23109
+ message
23012
23110
  }) {
23013
23111
  const toolCalls = getMessageToolCalls(message);
23014
23112
  if (message.role === "tool") {
@@ -23022,21 +23120,8 @@ function ConversationMessage({
23022
23120
  );
23023
23121
  }
23024
23122
  if (toolCalls.length) {
23025
- const messageInsights2 = getMessageInsightMatches(
23026
- insights,
23027
- source,
23028
- message.role,
23029
- message.content
23030
- );
23031
23123
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react3.Fragment, { children: [
23032
- hasRenderableContent(message.content) ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
23033
- ConversationBubble,
23034
- {
23035
- role: message.role,
23036
- content: message.content,
23037
- insights: messageInsights2
23038
- }
23039
- ) : null,
23124
+ hasRenderableContent(message.content) ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConversationBubble, { role: message.role, content: message.content }) : null,
23040
23125
  toolCalls.map((toolCall, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
23041
23126
  ToolCallBubble,
23042
23127
  {
@@ -23050,33 +23135,16 @@ function ConversationMessage({
23050
23135
  if (!hasRenderableContent(message.content)) {
23051
23136
  return null;
23052
23137
  }
23053
- const messageInsights = getMessageInsightMatches(
23054
- insights,
23055
- source,
23056
- message.role,
23057
- message.content
23058
- );
23059
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
23060
- ConversationBubble,
23061
- {
23062
- content: message.content,
23063
- insights: messageInsights,
23064
- role: message.role
23065
- }
23066
- );
23138
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConversationBubble, { content: message.content, role: message.role });
23067
23139
  }
23068
23140
  function ConversationBubble({
23069
23141
  content,
23070
- insights,
23071
23142
  role
23072
23143
  }) {
23073
23144
  const tone = role === "user" ? "is-user" : role === "assistant" ? "is-assistant" : "is-system";
23074
23145
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: clsx_default("conversation-row", tone), children: [
23075
23146
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "conversation-role", children: role }),
23076
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "conversation-bubble", children: [
23077
- insights.structuredInputs.length || insights.highlights.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MessageInsightBanner, { insights }) : null,
23078
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RichMessageContent, { content, role })
23079
- ] })
23147
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "conversation-bubble", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RichMessageContent, { content, role }) })
23080
23148
  ] });
23081
23149
  }
23082
23150
  function ToolResultBubble({
@@ -23110,54 +23178,6 @@ function ToolCallBubble({ index, toolCall }) {
23110
23178
  ] })
23111
23179
  ] });
23112
23180
  }
23113
- function MessageInsightBanner({
23114
- insights
23115
- }) {
23116
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "message-insight-banner", children: [
23117
- insights.structuredInputs.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
23118
- "div",
23119
- {
23120
- className: "message-insight-card",
23121
- children: [
23122
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "message-insight-card-header", children: [
23123
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, { semantic: "guardrail", variant: "secondary", children: "Structured input" }),
23124
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "message-insight-card-tags", children: item.tags.slice(0, 3).map((tag) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, { variant: "outline", children: tag }, tag)) })
23125
- ] }),
23126
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "message-insight-card-copy", children: [
23127
- "XML-like markup detected in this ",
23128
- item.role,
23129
- " message."
23130
- ] })
23131
- ]
23132
- },
23133
- `${item.role}-${item.snippet}-${index}`
23134
- )),
23135
- insights.highlights.map((highlight, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
23136
- "div",
23137
- {
23138
- className: "message-insight-card is-highlight",
23139
- children: [
23140
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "message-insight-card-header", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, { semantic: "guardrail", variant: "secondary", children: highlight.title }) }),
23141
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "message-insight-card-copy", children: highlight.description })
23142
- ]
23143
- },
23144
- `${highlight.kind}-${highlight.source}-${index}`
23145
- ))
23146
- ] });
23147
- }
23148
- function getMessageInsightMatches(insights, source, role, content) {
23149
- const text = extractPlainMessageText(content);
23150
- const normalizedText = text ? text.replace(/\s+/g, " ") : null;
23151
- const structuredInputs = insights.structuredInputs.filter(
23152
- (item) => item.role === role && normalizedText !== null && normalizedText.includes(item.snippet.replace(/\.\.\.$/, ""))
23153
- );
23154
- return {
23155
- highlights: insights.highlights.filter(
23156
- (highlight) => !(highlight.kind === "structured-input" && structuredInputs.length) && (highlight.source === source || normalizedText !== null && highlight.snippet.length > 0 && normalizedText.includes(highlight.snippet.replace(/\.\.\.$/, "")))
23157
- ),
23158
- structuredInputs
23159
- };
23160
- }
23161
23181
  function RichMessageContent({
23162
23182
  content,
23163
23183
  role
@@ -23542,9 +23562,7 @@ function MetadataActionButton({
23542
23562
  );
23543
23563
  }
23544
23564
  function MessagesCard({
23545
- insights = EMPTY_TRACE_INSIGHTS,
23546
23565
  messages,
23547
- sourcePrefix,
23548
23566
  title
23549
23567
  }) {
23550
23568
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card, { className: "detail-section", children: [
@@ -23552,9 +23570,7 @@ function MessagesCard({
23552
23570
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardContent, { className: "message-stack", children: messages.length ? messages.map((message, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
23553
23571
  StructuredMessageCard,
23554
23572
  {
23555
- insights,
23556
- message,
23557
- source: `${sourcePrefix}:${index}`
23573
+ message
23558
23574
  },
23559
23575
  `${message.role}-${index}`
23560
23576
  )) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "muted-copy", children: "No messages recorded." }) })
@@ -23570,20 +23586,12 @@ function ToolCallsCard({
23570
23586
  ] });
23571
23587
  }
23572
23588
  function StructuredMessageCard({
23573
- insights = EMPTY_TRACE_INSIGHTS,
23574
- message,
23575
- source
23589
+ message
23576
23590
  }) {
23577
23591
  const toolCalls = getMessageToolCalls(message);
23578
23592
  const hasContent = hasRenderableContent(message.content);
23579
23593
  const isToolCallTurn = toolCalls.length > 0;
23580
23594
  const isToolResult = message.role === "tool";
23581
- const messageInsights = getMessageInsightMatches(
23582
- insights,
23583
- source,
23584
- message.role,
23585
- message.content
23586
- );
23587
23595
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: cn("message-card", `role-${message.role}`), children: [
23588
23596
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "message-card-header", children: [
23589
23597
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, { semantic: message.role, variant: "secondary", children: message.role }),
@@ -23594,7 +23602,6 @@ function StructuredMessageCard({
23594
23602
  ] })
23595
23603
  ] }),
23596
23604
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "message-card-body", children: [
23597
- messageInsights.structuredInputs.length || messageInsights.highlights.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MessageInsightBanner, { insights: messageInsights }) : null,
23598
23605
  hasContent ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RichMessageContent, { content: message.content, role: message.role }) : isToolCallTurn ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "message-card-hint", children: "Tool call emitted." }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "message-card-hint", children: "No content recorded." }),
23599
23606
  isToolCallTurn ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ToolCallStack, { toolCalls, compact: true }) : null
23600
23607
  ] })
@@ -23766,6 +23773,7 @@ function buildDetailTabs(detail) {
23766
23773
  if (detail.stream) {
23767
23774
  tabs.push({ id: "stream", label: "Stream" });
23768
23775
  }
23776
+ tabs.push({ id: "otel", label: "OTel" });
23769
23777
  return tabs;
23770
23778
  }
23771
23779
  function getHierarchyNodeCopy(node, traceById) {
@@ -23824,8 +23832,8 @@ function getHierarchyNodeCopy(node, traceById) {
23824
23832
  trace.provider,
23825
23833
  trace.model,
23826
23834
  formatCostSummaryLabel(
23827
- trace.costUsd ?? getHierarchyNodeCostUsd(node),
23828
- false
23835
+ getHierarchyNodeCostUsd(node) ?? trace.costUsd,
23836
+ node.count > 1
23829
23837
  )
23830
23838
  ]) : formatList([
23831
23839
  formatCountLabel(node.count, "call"),
@@ -23848,55 +23856,24 @@ function getHierarchyNodeCopy(node, traceById) {
23848
23856
  }
23849
23857
  }
23850
23858
  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
23859
  return {
23855
- breadcrumbs,
23856
- path: pathParts.join(" / "),
23857
- subtitle: formatList([actor, trace.provider, trace.model]),
23860
+ breadcrumbs: [],
23861
+ path: "",
23858
23862
  title: getTraceTitle(trace)
23859
23863
  };
23860
23864
  }
23861
- function getTraceBreadcrumbs(trace) {
23862
- const sessionId = trace.hierarchy.sessionId || "unknown-session";
23863
- const rootActorId = trace.hierarchy.rootActorId || "unknown-actor";
23864
- const breadcrumbs = [
23865
- {
23866
- label: `Session ${shortId2(sessionId)}`,
23867
- nodeId: `session:${sessionId}`
23868
- },
23869
- {
23870
- label: rootActorId,
23871
- nodeId: `actor:${sessionId}:${rootActorId}`
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;
23865
+ function getHierarchyPathDisplayCopy(path, traceById) {
23866
+ const breadcrumbs = path.map((node) => ({
23867
+ label: getHierarchyNodeCopy(node, traceById).label,
23868
+ nodeId: node.id
23869
+ }));
23870
+ const lastNode = path[path.length - 1] ?? null;
23871
+ const lastTrace = lastNode?.type === "trace" && lastNode.meta.traceId ? traceById.get(lastNode.meta.traceId) ?? null : null;
23872
+ return {
23873
+ breadcrumbs,
23874
+ path: breadcrumbs.map((item) => item.label).join(" / "),
23875
+ title: lastTrace ? getTraceTitle(lastTrace) : breadcrumbs.at(-1)?.label || "Trace"
23876
+ };
23900
23877
  }
23901
23878
  function formatTraceProviderSummary(trace) {
23902
23879
  if (!trace) {
@@ -23924,7 +23901,7 @@ function getTraceTitle(trace) {
23924
23901
  return trace.hierarchy.stage ? `Stage run: ${trace.hierarchy.stage}` : "Stage run";
23925
23902
  }
23926
23903
  if (trace.kind === "child-actor") {
23927
- return `Child actor ${trace.mode}`;
23904
+ return trace.hierarchy.childActorId ? `Child actor ${trace.mode}: ${trace.hierarchy.childActorId}` : `Child actor ${trace.mode}`;
23928
23905
  }
23929
23906
  if (trace.kind === "actor") {
23930
23907
  return trace.mode === "stream" ? "Actor response stream" : "Actor response invoke";
@@ -24047,12 +24024,12 @@ function patchHierarchyNode(node, traceId, previousSummary, nextSummary, costDel
24047
24024
  let nextLabel = node.label;
24048
24025
  if (containsTrace) {
24049
24026
  if (node.type === "trace" && node.meta.traceId === traceId) {
24050
- nextMeta.costUsd = nextSummary.costUsd;
24051
24027
  nextMeta.model = nextSummary.model;
24052
24028
  nextMeta.provider = nextSummary.provider;
24053
24029
  nextMeta.status = nextSummary.status;
24054
24030
  nextLabel = nextSummary.model ? `${nextSummary.model} ${nextSummary.mode}` : traceId;
24055
- } else if (costDelta !== 0 || previousSummary?.costUsd !== null || nextSummary.costUsd !== null) {
24031
+ }
24032
+ if (costDelta !== 0 || previousSummary?.costUsd !== null || nextSummary.costUsd !== null) {
24056
24033
  const currentCost = typeof nextMeta.costUsd === "number" && Number.isFinite(nextMeta.costUsd) ? nextMeta.costUsd : 0;
24057
24034
  nextMeta.costUsd = roundCostUsd2(currentCost + costDelta);
24058
24035
  }
@@ -24110,6 +24087,7 @@ function buildHierarchyTimelineModel(rootNode, traceById, selectedNodeId, select
24110
24087
  costUsd: getHierarchyNodeCostUsd(node),
24111
24088
  depth,
24112
24089
  durationMs: timing.durationMs,
24090
+ guardrailPhase: trace?.hierarchy.guardrailPhase ?? null,
24113
24091
  hasHighlights: Boolean(trace?.flags?.hasHighlights),
24114
24092
  hasStructuredInput: Boolean(trace?.flags?.hasStructuredInput),
24115
24093
  hasVisibleChildren: children.length > 0,
@@ -24120,8 +24098,12 @@ function buildHierarchyTimelineModel(rootNode, traceById, selectedNodeId, select
24120
24098
  isLastSibling,
24121
24099
  label: copy.label,
24122
24100
  meta: copy.meta,
24101
+ model: trace?.model ?? null,
24123
24102
  offsetMs: Math.max(0, timing.startMs - rootTiming.startMs),
24103
+ provider: trace?.provider ?? null,
24124
24104
  startedAt: timing.startedAt,
24105
+ traceId: node.meta?.traceId ?? null,
24106
+ traceKind: trace?.kind ?? null,
24125
24107
  type: node.type
24126
24108
  });
24127
24109
  for (const [index, child] of children.entries()) {
@@ -24142,6 +24124,128 @@ function buildHierarchyTimelineModel(rootNode, traceById, selectedNodeId, select
24142
24124
  startedAt: rootTiming.startedAt
24143
24125
  };
24144
24126
  }
24127
+ function isGuardrailTimelineRow(row) {
24128
+ return row.type === "trace" && row.traceKind === "guardrail";
24129
+ }
24130
+ function buildHierarchyTimelineDisplayItems(rows, guardrailVisibility, expandedGuardrailGroups) {
24131
+ const items = [];
24132
+ for (let index = 0; index < rows.length; index += 1) {
24133
+ const row = rows[index];
24134
+ if (!isGuardrailTimelineRow(row)) {
24135
+ items.push({ compact: false, id: row.id, kind: "row", row });
24136
+ continue;
24137
+ }
24138
+ let groupEnd = index + 1;
24139
+ while (groupEnd < rows.length && isGuardrailTimelineRow(rows[groupEnd]) && rows[groupEnd].depth === row.depth) {
24140
+ groupEnd += 1;
24141
+ }
24142
+ const groupRows = rows.slice(index, groupEnd);
24143
+ index = groupEnd - 1;
24144
+ if (guardrailVisibility === "hidden") {
24145
+ continue;
24146
+ }
24147
+ if (groupRows.length === 1) {
24148
+ items.push({
24149
+ compact: true,
24150
+ id: row.id,
24151
+ kind: "row",
24152
+ row
24153
+ });
24154
+ continue;
24155
+ }
24156
+ const groupId = getGuardrailGroupId(groupRows);
24157
+ const forceExpanded = groupRows.some(
24158
+ (item) => item.isActive || item.isDetailTrace || item.hasVisibleChildren
24159
+ );
24160
+ const isExpanded = forceExpanded || Boolean(expandedGuardrailGroups[groupId]);
24161
+ if (!isExpanded) {
24162
+ items.push({
24163
+ id: groupId,
24164
+ isExpanded: false,
24165
+ itemCount: groupRows.length,
24166
+ kind: "guardrail-group",
24167
+ row: buildGuardrailGroupRow(groupRows)
24168
+ });
24169
+ continue;
24170
+ }
24171
+ if (expandedGuardrailGroups[groupId]) {
24172
+ items.push({
24173
+ id: groupId,
24174
+ isExpanded: true,
24175
+ itemCount: groupRows.length,
24176
+ kind: "guardrail-group",
24177
+ row: buildGuardrailGroupRow(groupRows)
24178
+ });
24179
+ }
24180
+ items.push(
24181
+ ...groupRows.map((item) => ({
24182
+ compact: false,
24183
+ id: item.id,
24184
+ kind: "row",
24185
+ row: item
24186
+ }))
24187
+ );
24188
+ }
24189
+ return items;
24190
+ }
24191
+ function getGuardrailGroupId(rows) {
24192
+ return `guardrail-group:${rows[0]?.id || "unknown"}:${rows.at(-1)?.id || "unknown"}`;
24193
+ }
24194
+ function buildGuardrailGroupRow(rows) {
24195
+ const firstRow = rows[0];
24196
+ const maxEndMs = rows.reduce(
24197
+ (maxValue, row) => Math.max(maxValue, row.offsetMs + row.durationMs),
24198
+ firstRow.offsetMs + firstRow.durationMs
24199
+ );
24200
+ const guardrailPhase = getSharedTimelineRowValue(rows, "guardrailPhase");
24201
+ const provider = getSharedTimelineRowValue(rows, "provider");
24202
+ const model = getSharedTimelineRowValue(rows, "model");
24203
+ return {
24204
+ ...firstRow,
24205
+ badge: "Guardrail",
24206
+ costUsd: sumTimelineRowCosts(rows),
24207
+ durationMs: Math.max(1, maxEndMs - firstRow.offsetMs),
24208
+ guardrailPhase,
24209
+ hasHighlights: rows.some((row) => row.hasHighlights),
24210
+ hasStructuredInput: rows.some((row) => row.hasStructuredInput),
24211
+ hasVisibleChildren: rows.at(-1)?.hasVisibleChildren ?? false,
24212
+ id: getGuardrailGroupId(rows),
24213
+ isActive: false,
24214
+ isDetailTrace: false,
24215
+ isInPath: rows.some((row) => row.isInPath),
24216
+ isLastSibling: rows.at(-1)?.isLastSibling ?? firstRow.isLastSibling,
24217
+ label: getGuardrailGroupLabel(rows, guardrailPhase),
24218
+ meta: formatList([
24219
+ formatList([provider, model]),
24220
+ formatCostSummaryLabel(sumTimelineRowCosts(rows), rows.length > 1)
24221
+ ]) || formatCountLabel(rows.length, "guardrail check"),
24222
+ model,
24223
+ provider,
24224
+ traceId: null,
24225
+ traceKind: "guardrail",
24226
+ type: "guardrail"
24227
+ };
24228
+ }
24229
+ function getGuardrailGroupLabel(rows, guardrailPhase) {
24230
+ if (rows.length === 1) {
24231
+ return rows[0].label;
24232
+ }
24233
+ if (guardrailPhase) {
24234
+ return formatCountLabel(rows.length, `${guardrailPhase} guardrail check`);
24235
+ }
24236
+ return formatCountLabel(rows.length, "guardrail check");
24237
+ }
24238
+ function getSharedTimelineRowValue(rows, key) {
24239
+ const firstValue = rows[0]?.[key] ?? null;
24240
+ return rows.every((row) => (row[key] ?? null) === firstValue) ? firstValue : null;
24241
+ }
24242
+ function sumTimelineRowCosts(rows) {
24243
+ const costRows = rows.map((row) => row.costUsd).filter((cost) => typeof cost === "number");
24244
+ if (!costRows.length) {
24245
+ return null;
24246
+ }
24247
+ return roundCostUsd2(costRows.reduce((sum, cost) => sum + cost, 0));
24248
+ }
24145
24249
  function compareHierarchyNodesForTimeline(left, right, getTiming) {
24146
24250
  const leftTiming = getTiming(left);
24147
24251
  const rightTiming = getTiming(right);
@@ -24220,32 +24324,12 @@ function getTimelineTypeLabel(type) {
24220
24324
  return startCase(type);
24221
24325
  }
24222
24326
  }
24223
- function formatInsightSource(source) {
24224
- if (source.startsWith("request:")) {
24225
- return `Request ${Number(source.split(":")[1] || 0) + 1}`;
24226
- }
24227
- if (source.startsWith("response:")) {
24228
- return "Response";
24229
- }
24230
- if (source.startsWith("stream:")) {
24231
- return "Stream";
24232
- }
24233
- if (source === "trace:guardrail") {
24234
- return "Guardrail context";
24235
- }
24236
- return source;
24237
- }
24238
24327
  function buildHierarchyTimelineRowTooltip(row) {
24239
24328
  const parts = [
24240
24329
  `${row.label} (${getTimelineTypeLabel(row.type)})`,
24241
24330
  `Started ${formatTimelineTimestamp(row.startedAt)}`,
24242
24331
  `Duration ${formatElapsedLabel(row.durationMs)}`
24243
24332
  ];
24244
- if (row.hasStructuredInput) {
24245
- parts.push("Structured input detected");
24246
- } else if (row.hasHighlights) {
24247
- parts.push("Trace insights available");
24248
- }
24249
24333
  if (row.costUsd !== null) {
24250
24334
  parts.push(`Cost ${formatUsdCost(row.costUsd)}`);
24251
24335
  }
@@ -24709,16 +24793,6 @@ function toMarkdownText(content) {
24709
24793
  }
24710
24794
  return null;
24711
24795
  }
24712
- function extractPlainMessageText(content) {
24713
- const markdown = toMarkdownText(content);
24714
- if (markdown !== null) {
24715
- return markdown;
24716
- }
24717
- if (typeof content === "string") {
24718
- return content;
24719
- }
24720
- return null;
24721
- }
24722
24796
  function detectStructuredMarkup(text) {
24723
24797
  const tagRegex = /<\/?([A-Za-z][\w:-]*)\b[^>]*>/g;
24724
24798
  const tagNames = [...text.matchAll(tagRegex)].map(
@@ -25052,9 +25126,6 @@ function CardHeader({ children }) {
25052
25126
  function CardTitle({ children }) {
25053
25127
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: "ui-card-title", children });
25054
25128
  }
25055
- function CardDescription({ children }) {
25056
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "ui-card-description", children });
25057
- }
25058
25129
  function CardContent({
25059
25130
  children,
25060
25131
  className
@@ -25152,7 +25223,7 @@ function parseEvent(data) {
25152
25223
  return null;
25153
25224
  }
25154
25225
  }
25155
- var import_react3, import_jsx_runtime, MESSAGE_COLLAPSE_CHAR_LIMIT, MESSAGE_COLLAPSE_LINE_LIMIT, MESSAGE_COLLAPSE_HEIGHT_PROSE, MESSAGE_COLLAPSE_HEIGHT_STRUCTURED, MESSAGE_COLLAPSE_CHAR_LIMIT_SYSTEM, MESSAGE_COLLAPSE_LINE_LIMIT_SYSTEM, MESSAGE_COLLAPSE_HEIGHT_PROSE_SYSTEM, MESSAGE_COLLAPSE_HEIGHT_STRUCTURED_SYSTEM, EMPTY_TRACE_INSIGHTS, MarkdownBlock, STATUS_OPTIONS, KIND_OPTIONS, INITIAL_FILTERS;
25226
+ var import_react3, import_jsx_runtime, MESSAGE_COLLAPSE_CHAR_LIMIT, MESSAGE_COLLAPSE_LINE_LIMIT, MESSAGE_COLLAPSE_HEIGHT_PROSE, MESSAGE_COLLAPSE_HEIGHT_STRUCTURED, MESSAGE_COLLAPSE_CHAR_LIMIT_SYSTEM, MESSAGE_COLLAPSE_LINE_LIMIT_SYSTEM, MESSAGE_COLLAPSE_HEIGHT_PROSE_SYSTEM, MESSAGE_COLLAPSE_HEIGHT_STRUCTURED_SYSTEM, MarkdownBlock, STATUS_OPTIONS, KIND_OPTIONS, INITIAL_FILTERS;
25156
25227
  var init_app = __esm({
25157
25228
  "client/src/app.tsx"() {
25158
25229
  init_lucide_react();
@@ -25170,10 +25241,6 @@ var init_app = __esm({
25170
25241
  MESSAGE_COLLAPSE_LINE_LIMIT_SYSTEM = 5;
25171
25242
  MESSAGE_COLLAPSE_HEIGHT_PROSE_SYSTEM = "9rem";
25172
25243
  MESSAGE_COLLAPSE_HEIGHT_STRUCTURED_SYSTEM = "9rem";
25173
- EMPTY_TRACE_INSIGHTS = {
25174
- highlights: [],
25175
- structuredInputs: []
25176
- };
25177
25244
  MarkdownBlock = (0, import_react3.lazy)(
25178
25245
  () => import("./markdown-block-DMQHS3E5.js").then((module) => ({
25179
25246
  default: module.MarkdownBlock