@mtharrison/loupe 1.4.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.
@@ -21949,9 +21949,6 @@ function getSessionNavBadge(item) {
21949
21949
  if (item.status === "pending") {
21950
21950
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, { variant: "warning", children: "Running" });
21951
21951
  }
21952
- if (item.hasHighlights) {
21953
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, { variant: "secondary", semantic: "guardrail", children: "Insight" });
21954
- }
21955
21952
  return null;
21956
21953
  }
21957
21954
  function BackgroundGlow() {
@@ -22169,8 +22166,27 @@ function SessionHierarchyBranch({
22169
22166
  sessionSelectedPath,
22170
22167
  selectedTraceId
22171
22168
  );
22169
+ const [showGuardrails, setShowGuardrails] = (0, import_react3.useState)(true);
22172
22170
  const sessionTimelineRow = sessionTimelineModel?.rows[0] ?? null;
22173
- 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;
22174
22190
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
22175
22191
  "div",
22176
22192
  {
@@ -22238,28 +22254,49 @@ function SessionHierarchyBranch({
22238
22254
  ]
22239
22255
  }
22240
22256
  ),
22241
- showEmbeddedTimeline && sessionTimelineModel ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "session-tree-timeline-shell", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22242
- "div",
22243
- {
22244
- className: "session-tree-timeline-list",
22245
- role: "list",
22246
- "aria-label": "Session timeline",
22247
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22248
- 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,
22249
22266
  {
22250
- className: "is-embedded",
22251
- model: sessionTimelineModel,
22252
- onSelectRow: (nodeId) => {
22253
- const selectedTimelineNode = findSessionNodeById([node], nodeId);
22254
- if (selectedTimelineNode) {
22255
- onSelect(selectedTimelineNode);
22256
- }
22257
- },
22258
- 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"
22259
22273
  }
22260
22274
  )
22261
- }
22262
- ) }) : 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)(
22263
22300
  HierarchyTreeNode,
22264
22301
  {
22265
22302
  defaultExpandedNodeIds,
@@ -22452,24 +22489,45 @@ function SessionTreeTimelineBar({
22452
22489
  }
22453
22490
  function HierarchyTimelineRows({
22454
22491
  className,
22492
+ guardrailVisibility = "compact",
22455
22493
  model,
22456
22494
  onSelectRow,
22457
22495
  rows
22458
22496
  }) {
22459
- return rows.map((row) => {
22460
- 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";
22461
22514
  const rowClassName = cn(
22462
22515
  "hierarchy-timeline-row",
22463
22516
  className,
22464
- isTraceRow ? "is-clickable" : "is-structure",
22517
+ (isTraceRow || item.kind === "guardrail-group") && "is-clickable",
22518
+ !isTraceRow && item.kind !== "guardrail-group" && "is-structure",
22465
22519
  row.depth === 0 && "is-root",
22466
22520
  row.isActive && "is-active",
22467
22521
  row.isDetailTrace && "is-detail-trace",
22468
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",
22469
22527
  `is-${row.type.replace(/[^a-z0-9-]/gi, "-")}`
22470
22528
  );
22471
22529
  const rowStyle = {
22472
- "--timeline-depth": String(row.depth),
22530
+ "--timeline-depth": String(row.depth + (hasSemanticIndent ? 1 : 0)),
22473
22531
  "--timeline-offset": String(
22474
22532
  model.durationMs > 0 ? row.offsetMs / model.durationMs : 0
22475
22533
  ),
@@ -22525,26 +22583,11 @@ function HierarchyTimelineRows({
22525
22583
  {
22526
22584
  variant: "secondary",
22527
22585
  className: "hierarchy-timeline-pill",
22528
- semantic: row.badge,
22529
- children: row.badge
22586
+ semantic: item.kind === "guardrail-group" || row.traceKind === "guardrail" ? "guardrail" : row.badge,
22587
+ children: item.kind === "guardrail-group" ? "Guardrail" : row.badge
22530
22588
  }
22531
22589
  ),
22532
- row.hasStructuredInput ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22533
- "span",
22534
- {
22535
- className: "hierarchy-timeline-row-flag",
22536
- title: "Structured input detected for this call",
22537
- children: "Structured input"
22538
- }
22539
- ) : null,
22540
- row.hasHighlights && !row.hasStructuredInput ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22541
- "span",
22542
- {
22543
- className: "hierarchy-timeline-row-flag is-highlight",
22544
- title: "Trace insights available",
22545
- children: "Insight"
22546
- }
22547
- ) : 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
22548
22591
  ] }),
22549
22592
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hierarchy-timeline-row-meta", children: row.meta })
22550
22593
  ] })
@@ -22554,6 +22597,35 @@ function HierarchyTimelineRows({
22554
22597
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hierarchy-timeline-row-duration", children: formatElapsedLabel(row.durationMs) })
22555
22598
  ] })
22556
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
+ }
22557
22629
  if (isTraceRow) {
22558
22630
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22559
22631
  "div",
@@ -22807,10 +22879,8 @@ function renderTabContent(tab, detail, jsonMode, onApplyTagFilter, onApplyTraceF
22807
22879
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22808
22880
  MessagesCard,
22809
22881
  {
22810
- insights: detail.insights,
22811
22882
  title: "Request messages",
22812
- messages: requestMessages,
22813
- sourcePrefix: "request"
22883
+ messages: requestMessages
22814
22884
  }
22815
22885
  ),
22816
22886
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(JsonCard, { title: "Request options", value: detail.request.options }),
@@ -22839,10 +22909,8 @@ function renderTabContent(tab, detail, jsonMode, onApplyTagFilter, onApplyTraceF
22839
22909
  responseMessage ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22840
22910
  MessagesCard,
22841
22911
  {
22842
- insights: detail.insights,
22843
22912
  title: "Assistant message",
22844
- messages: [responseMessage],
22845
- sourcePrefix: "response"
22913
+ messages: [responseMessage]
22846
22914
  }
22847
22915
  ) : null,
22848
22916
  toolCalls.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ToolCallsCard, { title: "Tool calls", toolCalls }) : null,
@@ -22905,10 +22973,8 @@ function renderTabContent(tab, detail, jsonMode, onApplyTagFilter, onApplyTraceF
22905
22973
  detail.stream.reconstructed?.message ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22906
22974
  MessagesCard,
22907
22975
  {
22908
- insights: detail.insights,
22909
22976
  title: "Reconstructed output",
22910
- messages: [detail.stream.reconstructed.message],
22911
- sourcePrefix: "stream"
22977
+ messages: [detail.stream.reconstructed.message]
22912
22978
  }
22913
22979
  ) : null,
22914
22980
  detail.stream.reconstructed?.tool_calls?.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -22952,7 +23018,6 @@ function ConversationView({
22952
23018
  );
22953
23019
  const costUsd = getUsageCostUsd(detail.usage);
22954
23020
  const costLabel = formatUsdCost(costUsd);
22955
- const traceInsights = detail.insights ?? EMPTY_TRACE_INSIGHTS;
22956
23021
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "conversation-layout", children: [
22957
23022
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "conversation-meta", children: [
22958
23023
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "conversation-meta-primary", children: [
@@ -23011,26 +23076,21 @@ function ConversationView({
23011
23076
  ) : null
23012
23077
  ] })
23013
23078
  ] }),
23014
- traceInsights.highlights.length || traceInsights.structuredInputs.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TraceInsightsSummary, { detail, insights: traceInsights }) : null,
23015
23079
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "conversation-thread", children: [
23016
23080
  requestMessages.length ? requestMessages.map((message, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
23017
23081
  ConversationMessage,
23018
23082
  {
23019
- insights: traceInsights,
23020
- message,
23021
- source: `request:${index}`
23083
+ message
23022
23084
  },
23023
23085
  `${message.role}-${index}`
23024
23086
  )) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "muted-copy", children: "No request messages recorded." }),
23025
23087
  hasRenderableContent(responseMessage?.content) ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
23026
23088
  ConversationMessage,
23027
23089
  {
23028
- insights: traceInsights,
23029
23090
  message: {
23030
23091
  role: responseMessage?.role || "assistant",
23031
23092
  content: responseMessage?.content
23032
- },
23033
- source: "response:0"
23093
+ }
23034
23094
  }
23035
23095
  ) : null,
23036
23096
  responseToolCalls.map((toolCall, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -23045,59 +23105,8 @@ function ConversationView({
23045
23105
  ] })
23046
23106
  ] });
23047
23107
  }
23048
- function TraceInsightsSummary({
23049
- detail,
23050
- insights
23051
- }) {
23052
- const title = detail.kind === "guardrail" ? "Why this guardrail ran" : "Trace insights";
23053
- const highlights = insights.highlights.filter(
23054
- (item) => item.kind !== "structured-input" || !insights.structuredInputs.length
23055
- );
23056
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card, { className: "detail-section trace-insights-card", children: [
23057
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardHeader, { children: [
23058
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardTitle, { children: title }),
23059
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardDescription, { children: "Deterministic signals extracted from the trace payload." })
23060
- ] }),
23061
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardContent, { className: "trace-insights-content", children: [
23062
- highlights.map((highlight, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
23063
- "div",
23064
- {
23065
- className: "trace-insight-item",
23066
- children: [
23067
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "trace-insight-item-header", children: [
23068
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, { semantic: "guardrail", variant: "secondary", children: highlight.title }),
23069
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "trace-insight-item-source", children: formatInsightSource(highlight.source) })
23070
- ] }),
23071
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "trace-insight-item-copy", children: highlight.description })
23072
- ]
23073
- },
23074
- `${highlight.kind}-${highlight.source}-${index}`
23075
- )),
23076
- insights.structuredInputs.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
23077
- "div",
23078
- {
23079
- className: "trace-insight-item is-structured",
23080
- children: [
23081
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "trace-insight-item-header", children: [
23082
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Badge, { semantic: "guardrail", variant: "secondary", children: [
23083
- "Structured ",
23084
- item.role,
23085
- " input"
23086
- ] }),
23087
- /* @__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)) })
23088
- ] }),
23089
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "trace-insight-item-copy", children: item.snippet })
23090
- ]
23091
- },
23092
- `${item.role}-${item.snippet}-${index}`
23093
- ))
23094
- ] })
23095
- ] });
23096
- }
23097
23108
  function ConversationMessage({
23098
- insights = EMPTY_TRACE_INSIGHTS,
23099
- message,
23100
- source
23109
+ message
23101
23110
  }) {
23102
23111
  const toolCalls = getMessageToolCalls(message);
23103
23112
  if (message.role === "tool") {
@@ -23111,21 +23120,8 @@ function ConversationMessage({
23111
23120
  );
23112
23121
  }
23113
23122
  if (toolCalls.length) {
23114
- const messageInsights2 = getMessageInsightMatches(
23115
- insights,
23116
- source,
23117
- message.role,
23118
- message.content
23119
- );
23120
23123
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react3.Fragment, { children: [
23121
- hasRenderableContent(message.content) ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
23122
- ConversationBubble,
23123
- {
23124
- role: message.role,
23125
- content: message.content,
23126
- insights: messageInsights2
23127
- }
23128
- ) : null,
23124
+ hasRenderableContent(message.content) ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConversationBubble, { role: message.role, content: message.content }) : null,
23129
23125
  toolCalls.map((toolCall, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
23130
23126
  ToolCallBubble,
23131
23127
  {
@@ -23139,33 +23135,16 @@ function ConversationMessage({
23139
23135
  if (!hasRenderableContent(message.content)) {
23140
23136
  return null;
23141
23137
  }
23142
- const messageInsights = getMessageInsightMatches(
23143
- insights,
23144
- source,
23145
- message.role,
23146
- message.content
23147
- );
23148
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
23149
- ConversationBubble,
23150
- {
23151
- content: message.content,
23152
- insights: messageInsights,
23153
- role: message.role
23154
- }
23155
- );
23138
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConversationBubble, { content: message.content, role: message.role });
23156
23139
  }
23157
23140
  function ConversationBubble({
23158
23141
  content,
23159
- insights,
23160
23142
  role
23161
23143
  }) {
23162
23144
  const tone = role === "user" ? "is-user" : role === "assistant" ? "is-assistant" : "is-system";
23163
23145
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: clsx_default("conversation-row", tone), children: [
23164
23146
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "conversation-role", children: role }),
23165
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "conversation-bubble", children: [
23166
- insights.structuredInputs.length || insights.highlights.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MessageInsightBanner, { insights }) : null,
23167
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RichMessageContent, { content, role })
23168
- ] })
23147
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "conversation-bubble", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RichMessageContent, { content, role }) })
23169
23148
  ] });
23170
23149
  }
23171
23150
  function ToolResultBubble({
@@ -23199,54 +23178,6 @@ function ToolCallBubble({ index, toolCall }) {
23199
23178
  ] })
23200
23179
  ] });
23201
23180
  }
23202
- function MessageInsightBanner({
23203
- insights
23204
- }) {
23205
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "message-insight-banner", children: [
23206
- insights.structuredInputs.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
23207
- "div",
23208
- {
23209
- className: "message-insight-card",
23210
- children: [
23211
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "message-insight-card-header", children: [
23212
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, { semantic: "guardrail", variant: "secondary", children: "Structured input" }),
23213
- /* @__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)) })
23214
- ] }),
23215
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "message-insight-card-copy", children: [
23216
- "XML-like markup detected in this ",
23217
- item.role,
23218
- " message."
23219
- ] })
23220
- ]
23221
- },
23222
- `${item.role}-${item.snippet}-${index}`
23223
- )),
23224
- insights.highlights.map((highlight, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
23225
- "div",
23226
- {
23227
- className: "message-insight-card is-highlight",
23228
- children: [
23229
- /* @__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 }) }),
23230
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "message-insight-card-copy", children: highlight.description })
23231
- ]
23232
- },
23233
- `${highlight.kind}-${highlight.source}-${index}`
23234
- ))
23235
- ] });
23236
- }
23237
- function getMessageInsightMatches(insights, source, role, content) {
23238
- const text = extractPlainMessageText(content);
23239
- const normalizedText = text ? text.replace(/\s+/g, " ") : null;
23240
- const structuredInputs = insights.structuredInputs.filter(
23241
- (item) => item.role === role && normalizedText !== null && normalizedText.includes(item.snippet.replace(/\.\.\.$/, ""))
23242
- );
23243
- return {
23244
- highlights: insights.highlights.filter(
23245
- (highlight) => !(highlight.kind === "structured-input" && structuredInputs.length) && (highlight.source === source || normalizedText !== null && highlight.snippet.length > 0 && normalizedText.includes(highlight.snippet.replace(/\.\.\.$/, "")))
23246
- ),
23247
- structuredInputs
23248
- };
23249
- }
23250
23181
  function RichMessageContent({
23251
23182
  content,
23252
23183
  role
@@ -23631,9 +23562,7 @@ function MetadataActionButton({
23631
23562
  );
23632
23563
  }
23633
23564
  function MessagesCard({
23634
- insights = EMPTY_TRACE_INSIGHTS,
23635
23565
  messages,
23636
- sourcePrefix,
23637
23566
  title
23638
23567
  }) {
23639
23568
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card, { className: "detail-section", children: [
@@ -23641,9 +23570,7 @@ function MessagesCard({
23641
23570
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardContent, { className: "message-stack", children: messages.length ? messages.map((message, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
23642
23571
  StructuredMessageCard,
23643
23572
  {
23644
- insights,
23645
- message,
23646
- source: `${sourcePrefix}:${index}`
23573
+ message
23647
23574
  },
23648
23575
  `${message.role}-${index}`
23649
23576
  )) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "muted-copy", children: "No messages recorded." }) })
@@ -23659,20 +23586,12 @@ function ToolCallsCard({
23659
23586
  ] });
23660
23587
  }
23661
23588
  function StructuredMessageCard({
23662
- insights = EMPTY_TRACE_INSIGHTS,
23663
- message,
23664
- source
23589
+ message
23665
23590
  }) {
23666
23591
  const toolCalls = getMessageToolCalls(message);
23667
23592
  const hasContent = hasRenderableContent(message.content);
23668
23593
  const isToolCallTurn = toolCalls.length > 0;
23669
23594
  const isToolResult = message.role === "tool";
23670
- const messageInsights = getMessageInsightMatches(
23671
- insights,
23672
- source,
23673
- message.role,
23674
- message.content
23675
- );
23676
23595
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: cn("message-card", `role-${message.role}`), children: [
23677
23596
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "message-card-header", children: [
23678
23597
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, { semantic: message.role, variant: "secondary", children: message.role }),
@@ -23683,7 +23602,6 @@ function StructuredMessageCard({
23683
23602
  ] })
23684
23603
  ] }),
23685
23604
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "message-card-body", children: [
23686
- messageInsights.structuredInputs.length || messageInsights.highlights.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MessageInsightBanner, { insights: messageInsights }) : null,
23687
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." }),
23688
23606
  isToolCallTurn ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ToolCallStack, { toolCalls, compact: true }) : null
23689
23607
  ] })
@@ -23983,7 +23901,7 @@ function getTraceTitle(trace) {
23983
23901
  return trace.hierarchy.stage ? `Stage run: ${trace.hierarchy.stage}` : "Stage run";
23984
23902
  }
23985
23903
  if (trace.kind === "child-actor") {
23986
- return `Child actor ${trace.mode}`;
23904
+ return trace.hierarchy.childActorId ? `Child actor ${trace.mode}: ${trace.hierarchy.childActorId}` : `Child actor ${trace.mode}`;
23987
23905
  }
23988
23906
  if (trace.kind === "actor") {
23989
23907
  return trace.mode === "stream" ? "Actor response stream" : "Actor response invoke";
@@ -24169,6 +24087,7 @@ function buildHierarchyTimelineModel(rootNode, traceById, selectedNodeId, select
24169
24087
  costUsd: getHierarchyNodeCostUsd(node),
24170
24088
  depth,
24171
24089
  durationMs: timing.durationMs,
24090
+ guardrailPhase: trace?.hierarchy.guardrailPhase ?? null,
24172
24091
  hasHighlights: Boolean(trace?.flags?.hasHighlights),
24173
24092
  hasStructuredInput: Boolean(trace?.flags?.hasStructuredInput),
24174
24093
  hasVisibleChildren: children.length > 0,
@@ -24179,8 +24098,12 @@ function buildHierarchyTimelineModel(rootNode, traceById, selectedNodeId, select
24179
24098
  isLastSibling,
24180
24099
  label: copy.label,
24181
24100
  meta: copy.meta,
24101
+ model: trace?.model ?? null,
24182
24102
  offsetMs: Math.max(0, timing.startMs - rootTiming.startMs),
24103
+ provider: trace?.provider ?? null,
24183
24104
  startedAt: timing.startedAt,
24105
+ traceId: node.meta?.traceId ?? null,
24106
+ traceKind: trace?.kind ?? null,
24184
24107
  type: node.type
24185
24108
  });
24186
24109
  for (const [index, child] of children.entries()) {
@@ -24201,6 +24124,128 @@ function buildHierarchyTimelineModel(rootNode, traceById, selectedNodeId, select
24201
24124
  startedAt: rootTiming.startedAt
24202
24125
  };
24203
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
+ }
24204
24249
  function compareHierarchyNodesForTimeline(left, right, getTiming) {
24205
24250
  const leftTiming = getTiming(left);
24206
24251
  const rightTiming = getTiming(right);
@@ -24279,32 +24324,12 @@ function getTimelineTypeLabel(type) {
24279
24324
  return startCase(type);
24280
24325
  }
24281
24326
  }
24282
- function formatInsightSource(source) {
24283
- if (source.startsWith("request:")) {
24284
- return `Request ${Number(source.split(":")[1] || 0) + 1}`;
24285
- }
24286
- if (source.startsWith("response:")) {
24287
- return "Response";
24288
- }
24289
- if (source.startsWith("stream:")) {
24290
- return "Stream";
24291
- }
24292
- if (source === "trace:guardrail") {
24293
- return "Guardrail context";
24294
- }
24295
- return source;
24296
- }
24297
24327
  function buildHierarchyTimelineRowTooltip(row) {
24298
24328
  const parts = [
24299
24329
  `${row.label} (${getTimelineTypeLabel(row.type)})`,
24300
24330
  `Started ${formatTimelineTimestamp(row.startedAt)}`,
24301
24331
  `Duration ${formatElapsedLabel(row.durationMs)}`
24302
24332
  ];
24303
- if (row.hasStructuredInput) {
24304
- parts.push("Structured input detected");
24305
- } else if (row.hasHighlights) {
24306
- parts.push("Trace insights available");
24307
- }
24308
24333
  if (row.costUsd !== null) {
24309
24334
  parts.push(`Cost ${formatUsdCost(row.costUsd)}`);
24310
24335
  }
@@ -24768,16 +24793,6 @@ function toMarkdownText(content) {
24768
24793
  }
24769
24794
  return null;
24770
24795
  }
24771
- function extractPlainMessageText(content) {
24772
- const markdown = toMarkdownText(content);
24773
- if (markdown !== null) {
24774
- return markdown;
24775
- }
24776
- if (typeof content === "string") {
24777
- return content;
24778
- }
24779
- return null;
24780
- }
24781
24796
  function detectStructuredMarkup(text) {
24782
24797
  const tagRegex = /<\/?([A-Za-z][\w:-]*)\b[^>]*>/g;
24783
24798
  const tagNames = [...text.matchAll(tagRegex)].map(
@@ -25111,9 +25126,6 @@ function CardHeader({ children }) {
25111
25126
  function CardTitle({ children }) {
25112
25127
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: "ui-card-title", children });
25113
25128
  }
25114
- function CardDescription({ children }) {
25115
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "ui-card-description", children });
25116
- }
25117
25129
  function CardContent({
25118
25130
  children,
25119
25131
  className
@@ -25211,7 +25223,7 @@ function parseEvent(data) {
25211
25223
  return null;
25212
25224
  }
25213
25225
  }
25214
- 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;
25215
25227
  var init_app = __esm({
25216
25228
  "client/src/app.tsx"() {
25217
25229
  init_lucide_react();
@@ -25229,10 +25241,6 @@ var init_app = __esm({
25229
25241
  MESSAGE_COLLAPSE_LINE_LIMIT_SYSTEM = 5;
25230
25242
  MESSAGE_COLLAPSE_HEIGHT_PROSE_SYSTEM = "9rem";
25231
25243
  MESSAGE_COLLAPSE_HEIGHT_STRUCTURED_SYSTEM = "9rem";
25232
- EMPTY_TRACE_INSIGHTS = {
25233
- highlights: [],
25234
- structuredInputs: []
25235
- };
25236
25244
  MarkdownBlock = (0, import_react3.lazy)(
25237
25245
  () => import("./markdown-block-DMQHS3E5.js").then((module) => ({
25238
25246
  default: module.MarkdownBlock