Files
deepseek-harness/packages/client/ui-trajectory/src/client/TrajectoryTable.tsx
T

2586 lines
92 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/** Turn-aware trajectory event ledger with a local record inspector. */
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
import type { CSSProperties, ReactNode } from 'react'
import {
IconChevronRightOutline14,
IconSettingsOutline16,
IconSparkle16,
IconUserOutline16,
JsonTree,
MarkdownText,
Tooltip,
} from '@deepseek-ai/dsh-client-ui-primitives'
import { structuredPatch } from 'diff'
import type {
AssistantRequestConfig, ConversationPromptSnapshot,
} from '@deepseek-ai/dsh-client-runtime/client'
import type {
AssistantMetricDetail, TrajectoryCellKind, TrajectoryCellProps, TrajectorySourceBlock,
} from './trajectory-record.ts'
import { formatElapsedSeconds } from './trajectory-record.ts'
import { trajectoryPreviewText, type TrajectoryTurnModel } from './layout.ts'
import css from './TrajectoryTable.module.css'
const BOTTOM_FOLLOW_THRESHOLD_PX = 2
const KIND_LABEL: Record<TrajectoryCellKind, string> = {
system: 'SYSTEM',
user: 'USER',
context: 'CONTEXT',
compacted: 'COMPACTED',
message: 'ASSISTANT',
tool: 'TOOL',
subtool: 'SUBTOOL',
}
function ToolWrenchIcon(): ReactNode {
return (
<svg
width="13"
height="13"
viewBox="0 0 16 16"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
data-role-icon="wrench"
aria-hidden="true"
>
<path d="M14 3.3a3.8 3.8 0 0 1-4.8 4.8l-5.1 5.1a1.6 1.6 0 1 1-2.3-2.3l5.1-5.1A3.8 3.8 0 0 1 11.7 1l-2.3 2.3 2.3 2.3L14 3.3Z" />
</svg>
)
}
function InformationIcon(): ReactNode {
return (
<svg
width="14"
height="14"
viewBox="0 0 16 16"
fill="none"
stroke="currentColor"
strokeWidth="1.4"
strokeLinecap="round"
data-role-icon="information"
aria-hidden="true"
>
<circle cx="8" cy="8" r="6.7" />
<circle cx="8" cy="5.5" r=".85" fill="currentColor" stroke="none" />
<path d="M8 7.75v3.4" strokeWidth="1.8" />
</svg>
)
}
function CompactedIcon(): ReactNode {
return (
<svg
width="13"
height="13"
viewBox="0 0 16 16"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
data-role-icon="compacted"
aria-hidden="true"
>
<path d="m2.5 2.5 3.75 3.75M3 6.25h3.25V3" />
<path d="m13.5 2.5-3.75 3.75M13 6.25H9.75V3" />
<path d="m2.5 13.5 3.75-3.75M3 9.75h3.25V13" />
<path d="m13.5 13.5-3.75-3.75M13 9.75H9.75V13" />
</svg>
)
}
const KIND_ICON: Record<TrajectoryCellKind, ReactNode> = {
system: <IconSettingsOutline16 size={13} />,
user: <IconUserOutline16 size={13} />,
context: <InformationIcon />,
compacted: <CompactedIcon />,
message: <IconSparkle16 size={13} />,
tool: <ToolWrenchIcon />,
subtool: <ToolWrenchIcon />,
}
interface TableRecord {
turn: number | null
section: number
group: string
groupStart: boolean
turnStart: boolean
cell: TrajectoryCellProps
turnEnd: boolean
collapsedSummary?: string
collapsedSummaryKind?: 'turn' | 'assistant'
}
type DetailTab =
| 'system-prompt'
| 'tools'
| 'overview'
| 'rendered'
| 'source'
| 'origin'
| 'input'
| 'output'
| 'schema'
| 'options'
| 'usage'
| 'timing'
| 'diff'
type RecordState = 'complete' | 'running' | 'error'
interface DetailTabItem {
id: DetailTab
label: string
}
interface ParentRecords {
message?: TableRecord
tool?: TableRecord
}
interface ToolCallTextParts {
name: string
args?: string
}
interface SelectedRequest {
turn: number | null
section: number
number: number
group: string
}
interface DetailsResizeDrag {
pointerId: number
startX: number
startWidth: number
splitWidth: number
startToolRequestOffset: number
}
const DETAILS_MIN_WIDTH = 320
const DETAILS_MAX_WIDTH = 720
const TABLE_MIN_WIDTH = 280
const DETAILS_RESIZE_STEP = 16
const TOOL_REQUEST_SHARE = 0.58
const TOOL_REQUEST_MIN_WIDTH = 180
const TOOL_REQUEST_MAX_WIDTH = 480
const DEFAULT_TOOL_REQUEST_SHARE = 0.36
const DEFAULT_TOOL_REQUEST_OFFSET = 56
const SYSTEM_PROMPT_TABS: readonly DetailTabItem[] = [
{ id: 'system-prompt', label: 'System Prompt' },
{ id: 'tools', label: 'Tools' },
]
const SYSTEM_UPDATE_TABS: readonly DetailTabItem[] = [
{ id: 'diff', label: 'Diff' },
...SYSTEM_PROMPT_TABS,
]
const REQUEST_TABS: readonly DetailTabItem[] = [
{ id: 'overview', label: 'Summary' },
{ id: 'options', label: 'Options' },
{ id: 'usage', label: 'Usage' },
{ id: 'timing', label: 'Timing' },
]
type TrajectorySplitStyle = CSSProperties & {
'--trajectory-tool-request-width': string
}
type RequestBoundaryStyle = CSSProperties & {
'--request-boundary-offset': string
}
function clampDetailsWidth(width: number, splitWidth: number): number {
const maxWidth = Math.max(
DETAILS_MIN_WIDTH,
Math.min(DETAILS_MAX_WIDTH, splitWidth - TABLE_MIN_WIDTH),
)
return Math.round(Math.min(Math.max(width, DETAILS_MIN_WIDTH), maxWidth))
}
function defaultToolRequestWidth(splitWidth: number): number {
return Math.min(
Math.max(
splitWidth * DEFAULT_TOOL_REQUEST_SHARE - DEFAULT_TOOL_REQUEST_OFFSET,
TOOL_REQUEST_MIN_WIDTH,
),
TOOL_REQUEST_MAX_WIDTH,
)
}
function formatDurationMs(milliseconds: number): string {
if (milliseconds < 1_000) return `${Math.round(milliseconds)} ms`
return `${(milliseconds / 1_000).toFixed(milliseconds < 10_000 ? 2 : 1)} s`
}
function formatStartedAt(timestamp: number | null): string {
if (timestamp === null || !Number.isFinite(timestamp)) return 'Not available'
const date = new Date(timestamp)
const two = (value: number) => String(value).padStart(2, '0')
const three = (value: number) => String(value).padStart(3, '0')
const time = `${two(date.getHours())}:${two(date.getMinutes())}:${two(date.getSeconds())}.${three(date.getMilliseconds())}`
const day = `${date.getFullYear()}-${two(date.getMonth() + 1)}-${two(date.getDate())}`
return `${day} ${time}`
}
function StartedAtValue({ timestamp }: { timestamp: number | null }) {
const [showUnix, setShowUnix] = useState(false)
if (timestamp === null || !Number.isFinite(timestamp)) return <dd>Not available</dd>
return (
<dd>
<button
type="button"
className={css.timestampToggle}
title={showUnix ? 'Show local time' : 'Show Unix timestamp'}
onClick={(event) => {
const selection = window.getSelection()
if (
selection !== null
&& !selection.isCollapsed
&& selection.rangeCount > 0
&& selection.getRangeAt(0).intersectsNode(event.currentTarget)
) return
setShowUnix(current => !current)
}}
>
{showUnix ? (timestamp / 1_000).toFixed(3) : formatStartedAt(timestamp)}
</button>
</dd>
)
}
function totalTime(metrics: AssistantMetricDetail): string {
if (!metrics.timingRecorded) return 'Not recorded'
if (metrics.stepStartTime === null) return 'Step start unavailable'
if (metrics.completedTime === null) return 'Pending'
return formatDurationMs(Math.max(0, metrics.completedTime - metrics.stepStartTime))
}
function ttft(metrics: AssistantMetricDetail): string {
if (!metrics.timingRecorded) return 'Not recorded'
if (metrics.stepStartTime === null) return 'Step start unavailable'
if (metrics.firstTokenTime === null) return 'First token unavailable'
return formatDurationMs(Math.max(0, metrics.firstTokenTime - metrics.stepStartTime))
}
function generationTime(metrics: AssistantMetricDetail): string {
if (!metrics.timingRecorded || metrics.firstTokenTime === null) return 'First token unavailable'
if (metrics.completedTime === null) return 'Pending'
return formatDurationMs(Math.max(0, metrics.completedTime - metrics.firstTokenTime))
}
function throughput(metrics: AssistantMetricDetail): string {
if (!metrics.usageProvided) return 'Usage unavailable'
if (metrics.outputTokens === null) return 'Output tokens unavailable'
if (!metrics.timingRecorded || metrics.firstTokenTime === null) return 'First token unavailable'
if (metrics.completedTime === null) return 'Pending'
const generationSeconds = (metrics.completedTime - metrics.firstTokenTime) / 1_000
if (generationSeconds <= 0) return 'Duration too short'
return `${(metrics.outputTokens / generationSeconds).toFixed(1)} tok/s`
}
function AssistantTimingPanel({ metrics }: { metrics: AssistantMetricDetail }) {
return (
<dl className={css.overview}>
<div><dt>Started</dt><StartedAtValue timestamp={metrics.stepStartTime} /></div>
<div><dt>Total duration</dt><dd>{totalTime(metrics)}</dd></div>
<div><dt>TTFT</dt><dd>{ttft(metrics)}</dd></div>
<div><dt>Generation</dt><dd>{generationTime(metrics)}</dd></div>
<div><dt>Throughput</dt><dd>{throughput(metrics)}</dd></div>
</dl>
)
}
/** Props for the trajectory ledger. */
export interface TrajectoryTableProps {
/** Session-global request numbers for the request groups visible in this context. */
requestNumbers?: readonly TrajectoryRequestNumber[]
/** Grouped records in display order. */
turns: readonly TrajectoryTurnModel[]
/** Record indexes emphasized by the active timeline focus. */
timelineFocusIndexes?: ReadonlySet<number> | null
/** Record indexes retained by the active live search, or null without a query. */
searchMatchIndexes?: ReadonlySet<number> | null
/** Report the record currently selected in the local inspector. */
onSelectedIndexChange?: (index: number | null) => void
/** Report a direct user selection from a ledger row. */
onRecordSelect?: (index: number) => void
/** One externally requested record selection; a new object repeats the request. */
recordSelection?: { readonly index: number } | null
/** Clear selection state owned by the ledger host. */
onClearSelection?: () => void
/** Turn ids whose rows after the first are folded into a summary. */
collapsedTurns: ReadonlySet<number>
/** Toggle one turn between folded and expanded. */
onToggleTurn: (turn: number) => void
/** Assistant record indexes whose tool calls are folded. */
collapsedAssistants: ReadonlySet<number>
/** Toggle tool calls under one assistant record. */
onToggleAssistant: (index: number) => void
/** One-shot cross-view inspect: open and scroll to this call's record. */
inspectCallId?: string | null
/** Acknowledge a consumed (or unresolvable) inspect request. */
onInspectApplied?: (() => void) | undefined
}
/** Request-inspector fields shared by ordinary generation and compaction. */
interface TrajectoryRequestNumberBase {
/** Request anchor event sequence; absent for the currently streaming ordinary request. */
seq?: number
group: string
number: number
status?: 'complete' | 'running' | 'error'
startedAt?: number
completedAt?: number | null
error?: string
retry?: number
maxRetries?: number
retryDelayMs?: number
resultSeq?: number
provider?: string
model?: string
requestConfig?: AssistantRequestConfig
usage?: TrajectoryUsage
cumulativeUsage?: TrajectoryUsage
}
/** One purpose-discriminated request identity paired with its session-global number. */
export type TrajectoryRequestNumber = TrajectoryRequestNumberBase & (
| {
purpose?: 'assistant'
turn: number
step: number
}
| {
purpose: 'compaction'
turn: number | null
step: 0
}
)
/** Disjoint provider token buckets for one request or a session prefix. */
export interface TrajectoryUsage {
input?: number
cacheRead?: number
cacheWrite?: number
output?: number
reasoning?: number
}
function flattenRecords(turns: readonly TrajectoryTurnModel[]): TableRecord[] {
return turns.flatMap((turn, section) => {
let firstInSection = true
const records = turn.groups.flatMap((group) => {
return group.cells.map((cell, index) => {
const turnStart = firstInSection
&& cell.requestOnly !== true
&& cell.kind !== 'system'
&& (cell.kind !== 'compacted' || turn.turn === null)
if (turnStart) firstInSection = false
return {
turn: turn.turn,
section,
group: group.title,
groupStart: index === 0,
turnStart,
cell,
turnEnd: false,
}
})
})
const last = records.at(-1)
if (last !== undefined) last.turnEnd = true
return records
})
}
function filterRecords(
records: readonly TableRecord[],
matches: ReadonlySet<number>,
): TableRecord[] {
const filtered = records
.filter(record =>
record.cell.requestOnly !== true && matches.has(record.cell.index),
)
.map(record => ({ ...record, groupStart: false, turnStart: false, turnEnd: false }))
const startedSections = new Set<number>()
for (const [index, record] of filtered.entries()) {
const previous = filtered[index - 1]
const next = filtered[index + 1]
record.groupStart = previous === undefined
|| previous.section !== record.section
|| previous.group !== record.group
record.turnStart = !startedSections.has(record.section)
&& record.cell.kind !== 'system'
&& (record.cell.kind !== 'compacted' || record.turn === null)
if (record.turnStart) startedSections.add(record.section)
record.turnEnd = next === undefined || next.section !== record.section
}
return filtered
}
function requestStep(group: string): number | undefined {
if (!group.startsWith('Step ')) return undefined
const value = Number(group.slice('Step '.length))
return Number.isInteger(value) && value > 0 ? value : undefined
}
function requestKey(turn: number | null, group: string): string {
return `${turn}\u0000${group}`
}
function sectionLabel(turn: number | null): string {
return turn === null ? 'Between turns' : `Turn ${turn}`
}
function indexRequestNumbers(
records: readonly TableRecord[],
sessionNumbers: readonly TrajectoryRequestNumber[] | undefined,
): ReadonlyMap<string, number> {
const numbers = new Map<string, number>()
for (const request of sessionNumbers ?? []) {
numbers.set(requestKey(request.turn, request.group), request.number)
}
let next = Math.max(0, ...numbers.values()) + 1
const boundaries = records
.filter(record => record.groupStart && requestStep(record.group) !== undefined)
.sort((left, right) => left.cell.index - right.cell.index)
for (const record of boundaries) {
const key = requestKey(record.turn, record.group)
if (!numbers.has(key)) numbers.set(key, next++)
}
return numbers
}
function indexRequestBoundaryRuns(records: readonly TableRecord[]): ReadonlyMap<number, number> {
const indexes = new Map<number, number>()
let runLength = 0
for (const record of records) {
if (record.cell.requestOnly === true) {
indexes.set(record.cell.index, runLength++)
continue
}
if (runLength > 0 && record.groupStart && requestStep(record.group) !== undefined) {
indexes.set(record.cell.index, runLength)
}
runLength = 0
}
return indexes
}
function summarizeTurn(records: readonly TableRecord[]): string {
const steps = new Set(
records
.map(record => record.group)
.filter(group => group.startsWith('Step ')),
).size
const toolCalls = records.filter(record =>
record.cell.kind === 'tool' || record.cell.kind === 'subtool',
).length
return [
`${steps} ${steps === 1 ? 'step' : 'steps'}`,
`${toolCalls} tool ${toolCalls === 1 ? 'call' : 'calls'}`,
].join(' · ')
}
function collapseTurnRecords(
records: readonly TableRecord[],
collapsedTurns: ReadonlySet<number>,
): TableRecord[] {
if (collapsedTurns.size === 0) return [...records]
const recordsByTurn = new Map<number, TableRecord[]>()
for (const record of records) {
if (record.turn === null) continue
const turnRecords = recordsByTurn.get(record.turn) ?? []
turnRecords.push(record)
recordsByTurn.set(record.turn, turnRecords)
}
return records.flatMap((record) => {
if (record.turn === null || !collapsedTurns.has(record.turn)) return [record]
const turnRecords = recordsByTurn.get(record.turn) ?? [record]
if (record.cell.requestOnly === true || record.cell.kind === 'system') return [record]
const contentRecords = turnRecords.filter(candidate =>
candidate.cell.requestOnly !== true && candidate.cell.kind !== 'system')
if (contentRecords.length <= 1) return [record]
if (record.cell.index !== contentRecords[0]?.cell.index) return []
return [
{ ...record, turnEnd: false },
{
...record,
groupStart: false,
turnStart: false,
turnEnd: true,
collapsedSummary: summarizeTurn(contentRecords.slice(1)),
collapsedSummaryKind: 'turn',
},
]
})
}
function assistantToolCalls(
records: readonly TableRecord[],
assistantIndex: number,
): readonly TableRecord[] {
const at = records.findIndex(record => record.cell.index === assistantIndex)
if (at === -1 || records[at]?.cell.kind !== 'message') return []
const calls: TableRecord[] = []
for (let i = at + 1; i < records.length; i++) {
const record = records[i]
if (record === undefined) break
if (record.cell.kind !== 'tool' && record.cell.kind !== 'subtool') break
calls.push(record)
}
return calls
}
function summarizeAssistantTools(records: readonly TableRecord[]): string {
const names = [...new Set(records.map((record) => {
const separator = record.cell.text.indexOf(' · ')
return separator === -1 ? record.cell.text : record.cell.text.slice(0, separator)
}).filter(name => name !== ''))]
const count = records.length
const summary = `${count} tool ${count === 1 ? 'call' : 'calls'}`
return names.length > 0 ? `${summary} · ${names.join(', ')}` : summary
}
function collapseAssistantRecords(
records: readonly TableRecord[],
collapsedAssistants: ReadonlySet<number>,
): TableRecord[] {
if (collapsedAssistants.size === 0) return [...records]
const out: TableRecord[] = []
for (let i = 0; i < records.length; i++) {
const record = records[i]
if (record === undefined) continue
out.push(record)
if (record.cell.kind !== 'message' || !collapsedAssistants.has(record.cell.index)) continue
const calls: TableRecord[] = []
for (let j = i + 1; j < records.length; j++) {
const candidate = records[j]
if (
candidate === undefined
|| candidate.collapsedSummary !== undefined
|| (candidate.cell.kind !== 'tool' && candidate.cell.kind !== 'subtool')
) break
calls.push(candidate)
}
if (calls.length === 0) continue
const last = calls.at(-1)
out[out.length - 1] = { ...record, turnEnd: false }
out.push({
...record,
groupStart: false,
turnStart: false,
turnEnd: last?.turnEnd ?? false,
collapsedSummary: summarizeAssistantTools(calls),
collapsedSummaryKind: 'assistant',
})
i += calls.length
}
return out
}
function stateOf(record: TableRecord): RecordState {
if (record.cell.isError) return 'error'
if (record.cell.kind === 'compacted' && record.cell.timeSeconds === null) return 'running'
if (
(record.cell.kind === 'tool' || record.cell.kind === 'subtool')
&& record.cell.outputDetail === undefined
) return 'running'
return 'complete'
}
function statusLabel(state: RecordState): string {
if (state === 'error') return 'Failed'
if (state === 'running') return 'Pending'
return 'Completed'
}
function TokenRows({ cell }: { cell: TrajectoryCellProps }) {
const content = cell.output !== undefined && cell.think !== undefined
? Math.max(0, cell.output - cell.think)
: undefined
return (
<>
<div>
<dt>Tokens</dt>
<dd>{cell.output === undefined ? '—' : `${cell.output} tok`}</dd>
</div>
{cell.think !== undefined && (
<div className={css.requestTokenDetail}>
<dt>Reasoning</dt>
<dd>{cell.think} tok</dd>
</div>
)}
{content !== undefined && (
<div className={css.requestTokenDetail}>
<dt>Content</dt>
<dd>{content} tok</dd>
</div>
)}
</>
)
}
function inputTotal(usage: TrajectoryUsage): number | undefined {
if (
usage.input === undefined
&& usage.cacheRead === undefined
&& usage.cacheWrite === undefined
) return undefined
return (usage.input ?? 0) + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0)
}
function UsageRows({ usage }: { usage: TrajectoryUsage | undefined }) {
if (usage === undefined) return <p className={css.noPayload}>Usage not reported</p>
const totalInput = inputTotal(usage)
const otherOutput = usage.output !== undefined && usage.reasoning !== undefined
? usage.output - usage.reasoning
: undefined
return (
<dl className={css.overview}>
{totalInput !== undefined && (
<div><dt>Input</dt><dd>{totalInput} tok</dd></div>
)}
{usage.cacheRead !== undefined && (
<div className={css.requestTokenDetail}>
<dt>Cached</dt>
<dd>{usage.cacheRead} tok</dd>
</div>
)}
{usage.cacheWrite !== undefined && (
<div className={css.requestTokenDetail}>
<dt>Cache created</dt>
<dd>{usage.cacheWrite} tok</dd>
</div>
)}
{usage.input !== undefined && (
<div className={css.requestTokenDetail}>
<dt>Other</dt>
<dd>{usage.input} tok</dd>
</div>
)}
{usage.output !== undefined && (
<div><dt>Output</dt><dd>{usage.output} tok</dd></div>
)}
{usage.reasoning !== undefined && (
<div className={css.requestTokenDetail}>
<dt>Reasoning</dt>
<dd>{usage.reasoning} tok</dd>
</div>
)}
{otherOutput !== undefined && (
<div className={css.requestTokenDetail}>
<dt>Content</dt>
<dd>{otherOutput} tok</dd>
</div>
)}
</dl>
)
}
function RequestUsagePanel({
usage,
cumulative,
}: {
usage: TrajectoryUsage | undefined
cumulative: TrajectoryUsage | undefined
}) {
return (
<div className={css.usagePanel}>
<section className={css.usageGroup}>
<h4 className={css.usageHeading}>This request</h4>
<UsageRows usage={usage} />
</section>
<section className={css.usageGroup}>
<h4 className={css.usageHeading}>Session cumulative</h4>
<UsageRows usage={cumulative} />
</section>
</div>
)
}
function RequestOptions({
options,
preview = false,
}: {
options: AssistantRequestConfig | undefined
preview?: boolean
}) {
if (options === undefined) {
return <p className={css.noPayload}>Options not recorded</p>
}
return (
<JsonTree
data={options}
label="Request options JSON"
className={preview ? css.jsonPreview : css.jsonPayload}
/>
)
}
function messageOriginLabel(source: unknown): string {
if (typeof source !== 'object' || source === null || Array.isArray(source)) {
return 'Unknown'
}
const properties = source as Record<string, unknown>
const kind = properties.kind
if (kind === 'user') return 'User'
if (kind === 'plugin') {
const plugin = properties.plugin
return typeof plugin === 'string' && plugin !== ''
? `Plugin · ${plugin}`
: 'Plugin'
}
if (kind === 'goal') {
const round = properties.round
return typeof round === 'number' && round > 0
? `Goal · Round ${round}`
: 'Goal'
}
if (typeof kind !== 'string' || kind === '') return 'Unknown'
return `${kind[0]?.toUpperCase() ?? ''}${kind.slice(1)}`
}
function MessageOrigin({ record }: { record: TableRecord }) {
const source = record.cell.messageSource
if (source === undefined) return <p className={css.noPayload}>Origin not recorded</p>
const data = typeof source === 'object' && source !== null
? source
: { value: source }
return (
<JsonTree
data={data}
label="Message origin JSON"
className={css.jsonPayload}
/>
)
}
function isMarkdownRecord(record: TableRecord): boolean {
return record.cell.kind === 'user'
|| record.cell.kind === 'context'
|| record.cell.kind === 'message'
}
function parentRecords(
records: readonly TableRecord[],
record: TableRecord,
): ParentRecords {
if (record.cell.kind !== 'tool' && record.cell.kind !== 'subtool') return {}
const at = records.findIndex(candidate => candidate.cell.index === record.cell.index)
if (at === -1) return {}
let tool: TableRecord | undefined
if (record.cell.kind === 'subtool') {
for (let i = at - 1; i >= 0; i--) {
const candidate = records[i]
if (
candidate === undefined
|| candidate.turn !== record.turn
|| candidate.group !== record.group
) break
if (candidate.cell.kind === 'tool') {
tool = candidate
break
}
}
}
const parentCallId = tool?.cell.callId ?? record.cell.callId
let message: TableRecord | undefined
if (parentCallId !== undefined) {
message = records.find(candidate =>
candidate.turn === record.turn
&& candidate.cell.kind === 'message'
&& candidate.cell.sourceBlocks?.some(block => block.callId === parentCallId) === true,
)
}
return { ...(message === undefined ? {} : { message }), ...(tool === undefined ? {} : { tool }) }
}
function markdownSource(record: TableRecord): string | undefined {
if (record.cell.kind === 'user' || record.cell.kind === 'context') {
return record.cell.inputDetail
}
if (record.cell.kind === 'message' || record.cell.kind === 'compacted') {
return record.cell.outputDetail
}
return undefined
}
function detailTabs(record: TableRecord): readonly DetailTabItem[] {
if (record.cell.kind === 'system') {
return record.cell.previousPromptDetail === undefined
? SYSTEM_PROMPT_TABS
: SYSTEM_UPDATE_TABS
}
if (record.cell.kind === 'compacted') {
return [
{ id: 'overview', label: 'Summary' },
{ id: 'source', label: 'Raw Output' },
]
}
if (isMarkdownRecord(record)) {
return [
{ id: 'overview', label: 'Summary' },
{ id: 'rendered', label: 'Preview' },
{ id: 'source', label: 'Source' },
...(record.cell.messageSource === undefined
? []
: [{ id: 'origin', label: 'Origin' } as const]),
]
}
return [
{ id: 'overview', label: 'Summary' },
...(record.cell.inputDetail ? [{ id: 'input', label: 'Payload' } as const] : []),
...(record.cell.outputDetail ? [{ id: 'output', label: 'Result' } as const] : []),
{ id: 'schema', label: 'Schema' },
{ id: 'timing', label: 'Timing' },
]
}
function recordDisplayText(cell: TrajectoryCellProps): string {
if (isToolCallOnly(cell)) return ''
if (cell.text !== '') return cell.text
const markdown = cell.kind === 'user' || cell.kind === 'context'
? cell.inputDetail
: cell.kind === 'message'
? cell.outputDetail ?? cell.thinkingDetail
: undefined
return markdown === undefined ? '' : trajectoryPreviewText(markdown)
}
function toolCallTextParts(
kind: TrajectoryCellKind,
text: string,
): ToolCallTextParts | undefined {
if (kind !== 'tool' && kind !== 'subtool') return undefined
const separator = text.indexOf(' · ')
if (separator === -1) return { name: text }
return {
name: text.slice(0, separator),
args: text.slice(separator + 3),
}
}
function isToolCallOnly(cell: TrajectoryCellProps): boolean {
return cell.kind === 'message'
&& !cell.outputDetail
&& !cell.thinkingDetail
&& cell.text === 'Tool call only'
}
function MarkdownFragment({
text,
rendered,
preview,
}: {
text: string
rendered: boolean
preview: boolean
}) {
if (rendered) {
return (
<div className={preview ? css.markdownPreview : css.markdownPayload}>
<MarkdownText text={text} />
</div>
)
}
return (
<pre className={`${css.payload} ${preview ? css.payloadPreview : ''}`}>
{text}
</pre>
)
}
function SourceBlocks({
blocks,
onOpenCall,
}: {
blocks: readonly TrajectorySourceBlock[]
onOpenCall: (callId: string) => void
}) {
return (
<div className={css.sourceBlocks}>
{blocks.map((block, index) => (
<section className={css.sourceBlock} key={index}>
{block.callId !== undefined
? (
<button
type="button"
className={css.sourceBlockJumpTarget}
aria-label={`Open Block #${index + 1} tool call summary`}
title="Open tool call summary"
onClick={() => {
if (block.callId !== undefined) onOpenCall(block.callId)
}}
>
<span className={css.sourceBlockLabel}>
{`Block #${index + 1} ${block.type}`}
</span>
<IconChevronRightOutline14 className={css.sourceBlockJumpIcon} size={12} />
</button>
)
: (
<div className={css.sourceBlockHeader}>
<span className={css.sourceBlockLabel}>
{`Block #${index + 1} ${block.type}`}
</span>
</div>
)}
{block.imageSrc !== undefined
? <PanelImage block={block} />
: <pre className={css.sourceBlockContent}>{block.content}</pre>}
</section>
))}
</div>
)
}
function PanelImage({
block,
preview = false,
}: {
block: TrajectorySourceBlock
preview?: boolean
}) {
if (block.imageSrc === undefined) return null
return (
<a
className={preview ? `${css.panelImageLink} ${css.panelImageLinkPreview}` : css.panelImageLink}
href={block.imageSrc}
target="_blank"
rel="noopener noreferrer"
title="Open image"
>
<img
className={css.panelImage}
src={block.imageSrc}
alt={block.imageAlt ?? ''}
/>
</a>
)
}
function MessageImages({
blocks,
preview,
}: {
blocks: readonly TrajectorySourceBlock[] | undefined
preview: boolean
}) {
const images = blocks?.filter(block => block.imageSrc !== undefined) ?? []
if (images.length === 0) return null
return (
<div className={preview ? `${css.messageImages} ${css.messageImagesPreview}` : css.messageImages}>
{images.map((block, index) => <PanelImage block={block} preview={preview} key={index} />)}
</div>
)
}
function AssistantToolCalls({
blocks,
preview,
onOpenCall,
}: {
blocks: readonly TrajectorySourceBlock[] | undefined
preview: boolean
onOpenCall: (callId: string) => void
}) {
const calls = blocks?.filter(block => block.type === 'tool-call') ?? []
if (calls.length === 0) return null
return (
<ul className={preview
? `${css.assistantToolCalls} ${css.assistantToolCallsPreview}`
: css.assistantToolCalls}
>
{calls.map((call, index) => (
<li key={call.callId ?? index}>
<button
type="button"
className={css.assistantToolCallButton}
title="Open tool call summary"
onClick={() => {
if (call.callId !== undefined) onOpenCall(call.callId)
}}
>
<svg
className={css.assistantToolCallIcon}
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
aria-hidden="true"
>
<path
d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94z"
stroke="currentColor"
strokeWidth="1.8"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
<span className={css.assistantToolCallText}>
<span className={css.assistantToolCallName}>
{call.toolName ?? 'tool-call'}
</span>
{call.content !== '' && (
<span className={css.assistantToolCallArgs}>{call.content}</span>
)}
</span>
</button>
</li>
))}
</ul>
)
}
function ToolGlyph() {
return (
<svg
className={css.toolCatalogIcon}
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
aria-hidden="true"
>
<path
d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94z"
stroke="currentColor"
strokeWidth="1.8"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
}
function ToolCatalog({ tools }: { tools: ConversationPromptSnapshot['tools'] }) {
if (tools.length === 0) return <p className={css.noPayload}>No tools in this request</p>
return (
<div className={css.toolCatalog}>
{tools.map((tool, index) => (
<details className={css.toolCatalogItem} key={`${tool.name}:${index}`}>
<summary className={css.toolCatalogSummary}>
<IconChevronRightOutline14 className={css.toolCatalogChevron} size={12} />
<ToolGlyph />
<span className={css.toolCatalogName}>{tool.name}</span>
<span className={css.toolCatalogDescription}>{tool.description}</span>
</summary>
<div className={css.toolCatalogDefinition}>
{tool.description !== '' && (
<p className={css.toolCatalogFullDescription}>{tool.description}</p>
)}
<JsonTree
data={tool.parameters}
label={`${tool.name} parameters JSON`}
className={css.toolCatalogTree}
/>
</div>
</details>
))}
</div>
)
}
interface PromptDiffLine {
kind: 'meta' | 'context' | 'added' | 'removed'
text: string
}
function promptDiffLines(before: string, after: string): readonly PromptDiffLine[] {
const patch = structuredPatch('', '', before, after, undefined, undefined, { context: 3 })
return patch.hunks.flatMap((hunk, hunkIndex) => [
...(hunkIndex === 0 ? [] : [{ kind: 'meta' as const, text: '' }]),
{
kind: 'meta' as const,
text: `@@ -${hunk.oldStart},${hunk.oldLines} +${hunk.newStart},${hunk.newLines} @@`,
},
...hunk.lines.flatMap((line): PromptDiffLine[] => {
if (line.startsWith('\\')) return []
if (line.startsWith('+')) return [{ kind: 'added', text: line }]
if (line.startsWith('-')) return [{ kind: 'removed', text: line }]
return [{ kind: 'context', text: line }]
}),
])
}
function PromptDiffSection({
title,
before,
after,
}: {
title: string
before: string
after: string
}) {
const lines = promptDiffLines(before, after)
if (lines.length === 0) return null
return (
<section className={css.promptDiffSection}>
<h3 className={css.promptDiffTitle}>{title}</h3>
<pre className={css.promptDiff}>
{lines.map((line, index) => (
<span className={css[`promptDiffLine${line.kind}`]} key={index}>
{line.text || ' '}
{'\n'}
</span>
))}
</pre>
</section>
)
}
function SystemPromptDiff({
before,
after,
}: {
before: ConversationPromptSnapshot
after: ConversationPromptSnapshot
}) {
const toolsBefore = JSON.stringify(before.tools, null, 2)
const toolsAfter = JSON.stringify(after.tools, null, 2)
return (
<div className={css.promptDiffSections}>
{before.system !== after.system && (
<PromptDiffSection
title="System Prompt"
before={before.system}
after={after.system}
/>
)}
{toolsBefore !== toolsAfter && (
<PromptDiffSection
title="Tools"
before={toolsBefore}
after={toolsAfter}
/>
)}
</div>
)
}
function ToolOutputBlocks({
blocks,
error,
preview,
}: {
blocks: readonly TrajectorySourceBlock[]
error: boolean
preview: boolean
}) {
return (
<div className={[
css.resultBlocks,
preview ? css.resultBlocksPreview : undefined,
error ? css.errorPayload : undefined,
].filter((value): value is string => value !== undefined).join(' ')}
>
{blocks.map((block, index) => (
block.imageSrc !== undefined
? <PanelImage block={block} preview={preview} key={index} />
: block.content !== ''
? <pre className={css.resultBlockText} key={index}>{block.content}</pre>
: null
))}
</div>
)
}
function MarkdownRecordContent({
record,
rendered,
preview = false,
thinkingExpanded,
onThinkingExpandedChange,
onOpenCall,
}: {
record: TableRecord
rendered: boolean
preview?: boolean
thinkingExpanded: boolean
onThinkingExpandedChange: (expanded: boolean) => void
onOpenCall: (callId: string) => void
}) {
if (!rendered && record.cell.sourceBlocks && record.cell.sourceBlocks.length > 0) {
return <SourceBlocks blocks={record.cell.sourceBlocks} onOpenCall={onOpenCall} />
}
if (record.cell.thinkingDetail) {
if (!rendered) {
const source = [
record.cell.thinkingDetail,
record.cell.outputDetail,
].filter((value): value is string => value !== undefined && value !== '').join('\n\n')
return <MarkdownFragment text={source} rendered={false} preview={preview} />
}
return (
<div className={`${css.assistantContent} ${css.assistantContentRendered}`}>
<div className={
preview && !record.cell.outputDetail
? `${css.thinkingQuote} ${css.thinkingQuoteOnlyPreview}`
: css.thinkingQuote
}
>
<button
type="button"
className={css.thinkingToggle}
aria-expanded={thinkingExpanded}
onClick={() => { onThinkingExpandedChange(!thinkingExpanded) }}
>
Thinking
<IconChevronRightOutline14 className={css.thinkingChevron} size={12} />
</button>
{thinkingExpanded && (
<MarkdownFragment
text={record.cell.thinkingDetail}
rendered={rendered}
preview={preview}
/>
)}
</div>
{record.cell.outputDetail && (
<div className={css.assistantOutput}>
<MarkdownFragment
text={record.cell.outputDetail}
rendered={rendered}
preview={preview}
/>
</div>
)}
<AssistantToolCalls
blocks={record.cell.sourceBlocks}
preview={preview}
onOpenCall={onOpenCall}
/>
<MessageImages
blocks={record.cell.sourceBlocks}
preview={preview}
/>
</div>
)
}
const source = markdownSource(record)
const hasImages = record.cell.sourceBlocks?.some(block => block.imageSrc !== undefined) === true
const hasToolCalls = record.cell.kind === 'message'
&& record.cell.sourceBlocks?.some(block => block.type === 'tool-call') === true
if (!source && !hasImages && !hasToolCalls) {
const emptyLabel = isToolCallOnly(record.cell)
? 'Tool call only'
: record.cell.text || 'No content'
return <p className={css.noPayload}>{emptyLabel}</p>
}
if (!rendered || (!hasImages && !hasToolCalls)) {
return <MarkdownFragment text={source ?? ''} rendered={rendered} preview={preview} />
}
return (
<div>
{source && <MarkdownFragment text={source} rendered preview={preview} />}
{record.cell.kind === 'message' && (
<AssistantToolCalls
blocks={record.cell.sourceBlocks}
preview={preview}
onOpenCall={onOpenCall}
/>
)}
<MessageImages blocks={record.cell.sourceBlocks} preview={preview} />
</div>
)
}
function RecordTiming({ record }: { record: TableRecord }) {
return record.cell.kind === 'message' && record.cell.assistantMetrics !== undefined
? <AssistantTimingPanel metrics={record.cell.assistantMetrics} />
: (
<dl className={css.overview}>
<div><dt>Started</dt><StartedAtValue timestamp={record.cell.startedAt ?? null} /></div>
<div><dt>Duration</dt><dd>{formatElapsedSeconds(record.cell.timeSeconds)}</dd></div>
<div><dt>Timing source</dt><dd>{record.cell.timeSeconds === null ? 'Not available' : 'Session timestamps'}</dd></div>
</dl>
)
}
function RequestTiming({
assistant,
anchor,
request,
}: {
assistant: TableRecord | undefined
anchor: TableRecord | undefined
request: TrajectoryRequestNumber | undefined
}) {
if (assistant !== undefined) return <RecordTiming record={assistant} />
if (request?.startedAt !== undefined) {
const duration = request.completedAt === null || request.completedAt === undefined
? null
: Math.max(0, (request.completedAt - request.startedAt) / 1000)
return (
<dl className={css.overview}>
<div><dt>Started</dt><StartedAtValue timestamp={request.startedAt} /></div>
<div><dt>Duration</dt><dd>{formatElapsedSeconds(duration)}</dd></div>
<div>
<dt>Timing source</dt>
<dd>{duration === null ? 'Session timestamps (running)' : 'Session timestamps'}</dd>
</div>
</dl>
)
}
return (
<dl className={css.overview}>
<div>
<dt>Started</dt>
<StartedAtValue timestamp={anchor?.cell.startedAt ?? null} />
</div>
<div><dt>Duration</dt><dd></dd></div>
</dl>
)
}
function RecordPayload({
record,
direction,
preview = false,
}: {
record: TableRecord
direction: 'input' | 'output'
preview?: boolean
}) {
const value = direction === 'input' ? record.cell.inputDetail : record.cell.outputDetail
const missing = direction === 'input'
? 'No payload captured'
: 'No result captured'
if (!value) return <p className={css.noPayload}>{missing}</p>
const error = direction === 'output' && record.cell.isError === true
const payloadClass = preview ? css.jsonPreview : css.jsonPayload
const payloadClassName = error ? `${payloadClass} ${css.errorPayload}` : payloadClass
const json = parseJsonContainer(value)
const singleTextResult = direction === 'output'
&& record.cell.outputBlocks?.length === 1
&& record.cell.outputBlocks[0]?.type === 'text'
if (singleTextResult && json !== undefined) {
return (
<JsonTree
data={json}
label="Result JSON"
className={payloadClassName}
/>
)
}
if (
direction === 'output'
&& record.cell.outputBlocks?.some(block =>
block.imageSrc !== undefined || block.content !== '') === true
) {
return (
<ToolOutputBlocks
blocks={record.cell.outputBlocks}
error={error}
preview={preview}
/>
)
}
const markdown = (
direction === 'input'
&& (record.cell.kind === 'user' || record.cell.kind === 'context')
) || (
direction === 'output' && record.cell.kind === 'message'
)
if (markdown) {
return (
<div className={[
preview ? css.markdownPreview : css.markdownPayload,
error ? css.errorPayload : undefined,
].filter((className): className is string => className !== undefined).join(' ')}
>
<MarkdownText text={value} />
</div>
)
}
if (json !== undefined) {
return (
<JsonTree
data={json}
label={`${direction === 'input' ? 'Payload' : 'Result'} JSON`}
className={payloadClassName}
/>
)
}
return (
<pre className={[
css.payload,
preview ? css.payloadPreview : undefined,
error ? css.errorPayload : undefined,
value === 'No output' ? css.noOutputText : undefined,
].filter((value): value is string => value !== undefined).join(' ')}
>
{value}
</pre>
)
}
function RecordSchema({
record,
preview = false,
}: {
record: TableRecord
preview?: boolean
}) {
if (!record.cell.schemaDetail) {
return <p className={css.noPayload}>Schema unavailable</p>
}
const schema = parseToolSchema(record.cell.schemaDetail)
if (schema !== undefined) {
return (
<div className={preview ? `${css.schema} ${css.schemaPreview}` : css.schema}>
<header className={css.schemaIntro}>
<h3 className={css.schemaName}>{schema.name}</h3>
<p className={css.schemaDescription}>{schema.description}</p>
</header>
<section className={css.schemaParameters}>
<h4 className={css.schemaParametersTitle}>Parameters</h4>
<JsonTree
data={schema.parameters}
label={`${schema.name} parameters JSON`}
className={css.schemaTree}
/>
</section>
</div>
)
}
return (
<pre className={`${css.payload} ${preview ? css.payloadPreview : ''}`}>
{record.cell.schemaDetail}
</pre>
)
}
interface ParsedToolSchema {
name: string
description: string
parameters: object
}
function parseToolSchema(value: string): ParsedToolSchema | undefined {
try {
const parsed: unknown = JSON.parse(value)
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) return undefined
const schema = parsed as Record<string, unknown>
if (
typeof schema.name !== 'string'
|| typeof schema.description !== 'string'
|| typeof schema.parameters !== 'object'
|| schema.parameters === null
|| Array.isArray(schema.parameters)
) return undefined
return {
name: schema.name,
description: schema.description,
parameters: schema.parameters,
}
} catch {
return undefined
}
}
function parseJsonContainer(value: string): object | undefined {
try {
const parsed: unknown = JSON.parse(value)
return typeof parsed === 'object' && parsed !== null ? parsed : undefined
} catch {
return undefined
}
}
function OverviewSection({
label,
onOpen,
children,
}: {
label: string
onOpen: () => void
children: ReactNode
}) {
return (
<section className={css.overviewSection}>
<h3 className={css.overviewHeading}>
<button
type="button"
className={css.overviewTitle}
onClick={onOpen}
>
<span>{label}</span>
<IconChevronRightOutline14 className={css.overviewTitleIcon} size={12} />
</button>
</h3>
<div className={css.overviewPreview}>{children}</div>
</section>
)
}
/**
* Render trajectory events as a dense ledger with turn and step separators.
* Clicking ledger whitespace clears the active record or request selection.
* @param props - Grouped trajectory data and whole-ledger fold state.
* @returns The ledger and an optional local record inspector.
*/
export function TrajectoryTable({
requestNumbers: sessionRequestNumbers,
turns,
timelineFocusIndexes = null,
searchMatchIndexes = null,
onSelectedIndexChange,
onRecordSelect,
recordSelection = null,
onClearSelection,
collapsedTurns,
onToggleTurn,
collapsedAssistants,
onToggleAssistant,
inspectCallId = null,
onInspectApplied,
}: TrajectoryTableProps) {
const [selectedIndex, setSelectedIndex] = useState<number | null>(null)
const [selectedRequest, setSelectedRequest] = useState<SelectedRequest | null>(null)
const [activeTab, setActiveTab] = useState<DetailTab>('overview')
const [thinkingExpanded, setThinkingExpanded] = useState(false)
const [detailsWidth, setDetailsWidth] = useState<number | null>(null)
const [toolRequestOffset, setToolRequestOffset] = useState<number | null>(null)
const detailsResizeDrag = useRef<DetailsResizeDrag | null>(null)
const appliedRecordSelection = useRef<TrajectoryTableProps['recordSelection']>(null)
const tabHistory = useRef<Set<DetailTab>>(new Set(['overview']))
useEffect(() => {
onSelectedIndexChange?.(selectedIndex)
}, [onSelectedIndexChange, selectedIndex])
const allRecords = useMemo(() => flattenRecords(turns), [turns])
const requestNumbers = indexRequestNumbers(allRecords, sessionRequestNumbers)
const records = searchMatchIndexes === null
? collapseAssistantRecords(
collapseTurnRecords(allRecords, collapsedTurns),
collapsedAssistants,
)
: filterRecords(allRecords, searchMatchIndexes)
const requestBoundaryRuns = indexRequestBoundaryRuns(records)
const selected = allRecords.find(record => record.cell.index === selectedIndex)
const selectedPrompt = selected?.cell.kind === 'system'
? selected.cell.promptDetail
: undefined
const selectedPreviousPrompt = selected?.cell.kind === 'system'
? selected.cell.previousPromptDetail
: undefined
const promptSelected = selectedPrompt !== undefined
const selectedState = selected === undefined ? undefined : stateOf(selected)
const selectedRequestRecords = selectedRequest === null
? []
: allRecords.filter(record =>
record.turn === selectedRequest.turn
&& record.section === selectedRequest.section
&& record.group === selectedRequest.group,
)
const selectedRequestAssistant = selectedRequestRecords.find(
record => record.cell.kind === 'message',
)
const selectedRequestAnchor = selectedRequestAssistant ?? selectedRequestRecords[0]
const selectedRequestInfo = selectedRequest === null
? undefined
: sessionRequestNumbers?.find(request => request.number === selectedRequest.number)
const selectedRequestState: RecordState | undefined = selectedRequest === null
? undefined
: selectedRequestInfo?.status
?? (selectedRequestAssistant?.cell.assistantMetrics?.completedTime === null
? 'running'
: selectedRequestAssistant === undefined
&& selectedRequestRecords.some(record => stateOf(record) === 'running')
? 'running'
: 'complete')
const selectedRequestToolCalls = selectedRequestRecords.filter(
record => record.cell.kind === 'tool',
).length
const selectedRequestSubtoolCalls = selectedRequestRecords.filter(
record => record.cell.kind === 'subtool',
).length
const selectedRequestResult = selectedRequestInfo?.resultSeq === undefined
? selectedRequestAssistant
: allRecords.find(record => record.cell.sourceSeq === selectedRequestInfo.resultSeq)
const selectedRequestUsage = selectedRequestInfo?.usage ?? (
selectedRequestAssistant === undefined
? undefined
: {
...(selectedRequestAssistant.cell.input === undefined
? {}
: { input: selectedRequestAssistant.cell.input }),
...(selectedRequestAssistant.cell.cacheRead === undefined
? {}
: { cacheRead: selectedRequestAssistant.cell.cacheRead }),
...(selectedRequestAssistant.cell.cacheWrite === undefined
? {}
: { cacheWrite: selectedRequestAssistant.cell.cacheWrite }),
...(selectedRequestAssistant.cell.output === undefined
? {}
: { output: selectedRequestAssistant.cell.output }),
...(selectedRequestAssistant.cell.think === undefined
? {}
: { reasoning: selectedRequestAssistant.cell.think }),
}
)
const selectedRequestCumulativeUsage =
selectedRequestInfo?.cumulativeUsage ?? selectedRequestUsage
const selectedRequestOptions = selectedRequestInfo?.requestConfig
const activeTurn = selectedRequest === null ? selected?.turn : selectedRequest.turn
const activeSection = selectedRequest === null ? selected?.section : selectedRequest.section
const selectedTabs = selectedRequest !== null
? REQUEST_TABS.filter(tab => tab.id !== 'options' || selectedRequestOptions !== undefined)
: selected === undefined ? [] : detailTabs(selected)
const selectedParents: ParentRecords = selected === undefined
? {}
: parentRecords(allRecords, selected)
const selectedParentMessage = selectedParents.message
const selectedParentTool = selectedParents.tool
const selectedAssistantRequest = selected?.cell.kind === 'message'
? requestNumbers.get(requestKey(selected.turn, selected.group))
: undefined
const selectedAssistantRequestTarget: SelectedRequest | undefined =
selected !== undefined && selectedAssistantRequest !== undefined
? {
turn: selected.turn,
section: selected.section,
number: selectedAssistantRequest,
group: selected.group,
}
: undefined
const hasSelectedHierarchy = selectedAssistantRequestTarget !== undefined
|| selectedParents.message !== undefined
|| selectedParents.tool !== undefined
const splitStyle: TrajectorySplitStyle | undefined = toolRequestOffset === null
? undefined
: {
'--trajectory-tool-request-width': `calc(58cqw - ${toolRequestOffset}px)`,
}
const activateTab = (tab: DetailTab) => {
tabHistory.current.delete(tab)
tabHistory.current.add(tab)
setActiveTab(tab)
}
const clearInspectorSelection = () => {
setSelectedIndex(null)
setSelectedRequest(null)
}
const clearAllSelections = () => {
clearInspectorSelection()
onClearSelection?.()
}
const selectRecord = useCallback((index: number) => {
const record = allRecords.find(candidate => candidate.cell.index === index)
onRecordSelect?.(index)
setSelectedRequest(null)
setSelectedIndex(index)
if (record === undefined) return
const tabs = detailTabs(record)
const available = new Set(tabs.map(tab => tab.id))
const recent = [...tabHistory.current].reverse().find(tab => available.has(tab))
setActiveTab(recent ?? tabs[0]?.id ?? 'overview')
}, [allRecords, onRecordSelect])
useEffect(() => {
if (
recordSelection === null
|| appliedRecordSelection.current === recordSelection
) return
appliedRecordSelection.current = recordSelection
selectRecord(recordSelection.index)
}, [recordSelection, selectRecord])
const selectRequest = (
request: SelectedRequest,
tab: 'overview' | 'timing' = 'overview',
) => {
setSelectedIndex(null)
setSelectedRequest(request)
activateTab(tab)
}
const openRecordSummary = (target: TableRecord) => {
const targetAt = allRecords.findIndex(record => record.cell.index === target.cell.index)
if (target.turn !== null && collapsedTurns.has(target.turn)) onToggleTurn(target.turn)
if (target.cell.kind === 'tool' || target.cell.kind === 'subtool') {
for (let i = targetAt - 1; i >= 0; i--) {
const candidate = allRecords[i]
if (candidate === undefined || candidate.turn !== target.turn) break
if (candidate.cell.kind !== 'message') continue
if (collapsedAssistants.has(candidate.cell.index)) onToggleAssistant(candidate.cell.index)
break
}
}
setSelectedRequest(null)
setSelectedIndex(target.cell.index)
activateTab('overview')
}
const openCallSummary = (callId: string) => {
const target = allRecords.find(record => record.cell.callId === callId)
if (target !== undefined) openRecordSummary(target)
}
// Cross-view inspect handoff: resolve the requested call to its record,
// open its summary, and remember the row to scroll once the un-collapsed
// ledger has rendered. Not-found leaves the request pending (`turns` in the
// deps retries as history pages in); the ack clears the store field.
const rootRef = useRef<HTMLDivElement>(null)
const tablePaneRef = useRef<HTMLDivElement>(null)
const followsTableTail = useRef(false)
const tableScrollInitialized = useRef(false)
const pendingScrollIndex = useRef<number | null>(null)
const openRecordSummaryRef = useRef(openRecordSummary)
openRecordSummaryRef.current = openRecordSummary
useEffect(() => {
if (inspectCallId === null) return
const target = flattenRecords(turns).find(record => record.cell.callId === inspectCallId)
if (target === undefined) return
openRecordSummaryRef.current(target)
pendingScrollIndex.current = target.cell.index
onInspectApplied?.()
}, [inspectCallId, turns, onInspectApplied])
useEffect(() => {
const index = pendingScrollIndex.current
if (index === null) return
const row = rootRef.current
?.querySelector<HTMLElement>(`tr[data-record-index="${index}"]`)
if (row === undefined || row === null) return
pendingScrollIndex.current = null
/* v8 ignore next -- jsdom lacks scrollIntoView; browsers always have it. */
if (typeof row.scrollIntoView === 'function') {
row.scrollIntoView({ behavior: 'smooth', block: 'center' })
}
})
useLayoutEffect(() => {
const pane = tablePaneRef.current
if (pane === null) return
if (!tableScrollInitialized.current) {
tableScrollInitialized.current = true
followsTableTail.current =
pane.scrollHeight - pane.clientHeight - pane.scrollTop
<= BOTTOM_FOLLOW_THRESHOLD_PX
return
}
if (followsTableTail.current) pane.scrollTop = pane.scrollHeight
}, [turns])
return (
<div ref={rootRef} className={css.split} style={splitStyle}>
<div
ref={tablePaneRef}
className={css.tablePane}
onScroll={(event) => {
const pane = event.currentTarget
followsTableTail.current =
pane.scrollHeight - pane.clientHeight - pane.scrollTop
<= BOTTOM_FOLLOW_THRESHOLD_PX
}}
onClick={(event) => {
if (event.target === event.currentTarget) clearAllSelections()
}}
>
<table className={css.table}>
<colgroup>
<col className={css.eventColumn} />
<col className={css.contentColumn} />
</colgroup>
<tbody>
{records.map((record) => {
const displayText = recordDisplayText(record.cell)
const toolCallText = toolCallTextParts(record.cell.kind, displayText)
const listDisplayText = toolCallText === undefined
? displayText
: [toolCallText.name, toolCallText.args].filter(Boolean).join(' ')
const isCollapsedSummary = record.collapsedSummary !== undefined
const isRequestOnly = record.cell.requestOnly === true
const isInitialSystem = record.cell.kind === 'system'
&& record.cell.index === allRecords[0]?.cell.index
const request = record.groupStart
&& !isCollapsedSummary
&& (record.turn === null || !collapsedTurns.has(record.turn))
? requestNumbers.get(requestKey(record.turn, record.group))
: undefined
const requestInfo = request === undefined
? undefined
: sessionRequestNumbers?.find(candidate => candidate.number === request)
const requestStatus = requestInfo?.status
?? (record.cell.isError === true ? 'error' : undefined)
const requestRunIndex = requestBoundaryRuns.get(record.cell.index) ?? 0
const requestBoundaryStyle: RequestBoundaryStyle = {
'--request-boundary-offset': `${requestRunIndex * 8}px`,
}
const requestLabel = request === undefined
? undefined
: `Request #${request}${requestInfo?.purpose === 'compaction' ? ' · Compaction' : ''}`
const requestSelected = request !== undefined
&& selectedRequest?.turn === record.turn
&& selectedRequest.section === record.section
&& selectedRequest.number === request
const sectionActive = record.turn === null
? activeSection === record.section
: activeTurn === record.turn
return (
<tr
key={`${record.cell.index}:${record.collapsedSummaryKind ?? 'record'}`}
tabIndex={isRequestOnly ? -1 : 0}
aria-label={isCollapsedSummary
? `Collapsed ${record.collapsedSummaryKind} summary, ${record.collapsedSummary}`
: isRequestOnly
? `Request ${request ?? ''}, compaction`
: `${request === undefined ? '' : `Request ${request}, `}${KIND_LABEL[record.cell.kind]}, ${listDisplayText || 'no content'}`}
aria-selected={!isCollapsedSummary && !isRequestOnly && selectedIndex === record.cell.index}
data-kind={record.cell.kind}
data-record-index={!isCollapsedSummary && !isRequestOnly
? record.cell.index
: undefined}
data-request-only={isRequestOnly || undefined}
data-group-start={record.groupStart || undefined}
data-turn-start={record.turnStart || undefined}
data-error={record.cell.isError || undefined}
data-running={stateOf(record) === 'running' || undefined}
data-turn-end={record.turnEnd || undefined}
data-collapsed-summary={record.collapsedSummaryKind}
data-selected={!isCollapsedSummary && selectedIndex === record.cell.index || undefined}
data-timeline-focus={isCollapsedSummary || timelineFocusIndexes === null
? undefined
: timelineFocusIndexes.has(record.cell.index) ? 'inside' : 'outside'}
onClick={isRequestOnly
? undefined
: isCollapsedSummary
? () => {
if (record.collapsedSummaryKind === 'turn' && record.turn !== null) {
onToggleTurn(record.turn)
} else onToggleAssistant(record.cell.index)
}
: () => { selectRecord(record.cell.index) }}
onDoubleClick={(event) => {
if (isCollapsedSummary || isRequestOnly) return
if (record.turn !== null && collapsedTurns.has(record.turn)) {
event.preventDefault()
onToggleTurn(record.turn)
return
}
if (
record.cell.kind === 'message'
&& assistantToolCalls(allRecords, record.cell.index).length > 0
) {
event.preventDefault()
onToggleAssistant(record.cell.index)
return
}
if (!record.turnStart) return
if (record.turn === null) return
if (allRecords.filter(candidate =>
candidate.turn === record.turn
&& candidate.cell.requestOnly !== true
&& candidate.cell.kind !== 'system').length <= 1) return
event.preventDefault()
onToggleTurn(record.turn)
}}
onKeyDown={(event) => {
if (isRequestOnly) return
if (event.key !== 'Enter' && event.key !== ' ') return
event.preventDefault()
if (isCollapsedSummary) {
if (record.collapsedSummaryKind === 'turn' && record.turn !== null) {
onToggleTurn(record.turn)
} else onToggleAssistant(record.cell.index)
return
}
selectRecord(record.cell.index)
}}
>
<td className={css.event}>
{request !== undefined && (
<button
type="button"
className={requestSelected
? `${css.requestBoundaryControl} ${css.requestBoundaryControlActive}`
: css.requestBoundaryControl}
aria-label={requestLabel}
aria-pressed={requestSelected}
data-label={requestLabel}
data-request-run-index={requestRunIndex}
data-request-status={requestStatus}
style={requestBoundaryStyle}
onClick={(event) => {
event.stopPropagation()
selectRequest({
turn: record.turn,
section: record.section,
number: request,
group: record.group,
})
}}
onDoubleClick={(event) => { event.stopPropagation() }}
/>
)}
{record.turn !== null
&& activeTurn === record.turn
&& !isInitialSystem && (
<span className={css.turnRail} aria-hidden="true" />
)}
{!isCollapsedSummary && selectedIndex === record.cell.index && (
<span className={css.selectionRail} aria-hidden="true" />
)}
{!isCollapsedSummary
&& !isRequestOnly
&& record.turnStart && (
<span
className={sectionActive
? `${css.turnLabel} ${css.turnLabelActive}`
: css.turnLabel}
aria-label={sectionLabel(record.turn)}
>
{record.turn === null
? sectionLabel(record.turn)
: (
<>
<span className={css.turnLabelFull} aria-hidden="true">
{sectionLabel(record.turn)}
</span>
<span className={css.turnLabelCompact} aria-hidden="true">
#{record.turn}
</span>
</>
)}
</span>
)}
<div className={css.eventInner}>
{!isCollapsedSummary && !isRequestOnly && (
<span
className={css.kindSlot}
>
<span
className={`${css.kindTag} ${
record.cell.kind === 'system'
? css.systemNeutral
: record.cell.kind === 'context'
? css.contextGreen
: record.cell.kind === 'compacted'
? css.compacted
: record.cell.kind === 'tool'
? css.toolAmber
: record.cell.kind === 'message'
? css.assistantVioletBright
: record.cell.kind === 'subtool'
? css.subtoolAmber
: css[record.cell.kind]
}`}
data-role-kind={record.cell.kind}
>
<Tooltip
label={KIND_LABEL[record.cell.kind]}
side="right"
>
<span className={css.kindTagIcon} aria-hidden="true">
{KIND_ICON[record.cell.kind]}
</span>
</Tooltip>
<span className={css.kindTagLabel}>
{KIND_LABEL[record.cell.kind]}
</span>
</span>
</span>
)}
</div>
</td>
<td className={css.content}>
{isRequestOnly
? null
: record.collapsedSummary !== undefined
? (
<span className={css.collapsedTurnContent} title={record.collapsedSummary}>
<span className={css.collapsedTurnEllipsis}></span>
<span className={css.collapsedTurnText}>{record.collapsedSummary}</span>
</span>
)
: (
<span
className={record.cell.result === undefined ? css.contentText : css.resultPreview}
title={record.cell.result === undefined
? listDisplayText
: `${listDisplayText}${record.cell.result}`}
>
<span className={record.cell.result === undefined ? undefined : css.resultRequest}>
{isToolCallOnly(record.cell)
? null
: toolCallText === undefined
? listDisplayText || '—'
: (
<>
<span className={css.toolCallNameTypeface}>
{toolCallText.name || '—'}
</span>
{toolCallText.args !== undefined && (
<span className={css.toolCallPayload}>
{toolCallText.args}
</span>
)}
</>
)}
</span>
{record.cell.result !== undefined && (
<span className={record.cell.isError ? `${css.inlineResult} ${css.error}` : css.inlineResult}>
<span className={css.arrow}></span>
<span className={record.cell.result === 'No output'
? `${css.inlineResultText} ${css.noOutputText}`
: css.inlineResultText}
>
{record.cell.result}
</span>
</span>
)}
</span>
)}
</td>
</tr>
)
})}
</tbody>
</table>
</div>
{(selectedRequest !== null
|| promptSelected
|| (selected !== undefined && selectedState !== undefined)) && (
<aside
className={css.details}
aria-label="Event details"
style={detailsWidth === null ? undefined : { width: detailsWidth }}
>
<div
className={css.detailsResizeHandle}
role="separator"
aria-label="Resize event details"
aria-controls="trajectory-detail-panel"
aria-orientation="vertical"
tabIndex={0}
title="Drag to resize. Double-click to reset."
onDoubleClick={() => {
setDetailsWidth(null)
setToolRequestOffset(null)
}}
onPointerDown={(event) => {
if (event.button !== 0) return
const details = event.currentTarget.parentElement
if (details === null) return
const split = details.parentElement
if (split === null) return
const splitWidth = split.getBoundingClientRect().width
detailsResizeDrag.current = {
pointerId: event.pointerId,
startX: event.clientX,
startWidth: details.getBoundingClientRect().width,
splitWidth,
startToolRequestOffset: toolRequestOffset ?? (
splitWidth * TOOL_REQUEST_SHARE - defaultToolRequestWidth(splitWidth)
),
}
event.currentTarget.setPointerCapture(event.pointerId)
event.preventDefault()
}}
onPointerMove={(event) => {
const drag = detailsResizeDrag.current
if (drag === null || drag.pointerId !== event.pointerId) return
const nextDetailsWidth = clampDetailsWidth(
drag.startWidth + drag.startX - event.clientX,
drag.splitWidth,
)
setDetailsWidth(nextDetailsWidth)
setToolRequestOffset(
drag.startToolRequestOffset
+ (nextDetailsWidth - drag.startWidth) * TOOL_REQUEST_SHARE,
)
}}
onPointerUp={(event) => {
if (detailsResizeDrag.current?.pointerId !== event.pointerId) return
detailsResizeDrag.current = null
event.currentTarget.releasePointerCapture(event.pointerId)
}}
onPointerCancel={() => {
detailsResizeDrag.current = null
}}
onKeyDown={(event) => {
if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return
const details = event.currentTarget.parentElement
if (details === null) return
const split = details.parentElement
if (split === null) return
const direction = event.key === 'ArrowLeft' ? 1 : -1
const currentDetailsWidth = details.getBoundingClientRect().width
const splitWidth = split.getBoundingClientRect().width
const nextDetailsWidth = clampDetailsWidth(
currentDetailsWidth + direction * DETAILS_RESIZE_STEP,
splitWidth,
)
const currentToolRequestOffset = toolRequestOffset ?? (
splitWidth * TOOL_REQUEST_SHARE - defaultToolRequestWidth(splitWidth)
)
setDetailsWidth(nextDetailsWidth)
setToolRequestOffset(
currentToolRequestOffset
+ (nextDetailsWidth - currentDetailsWidth) * TOOL_REQUEST_SHARE,
)
event.preventDefault()
}}
/>
<div className={css.detailsHeader}>
<div className={css.detailsTitle}>
{selectedRequest !== null
? (
<>
<span className={css.requestDetailsDot} aria-hidden="true" />
<span className={css.requestDetailsName}>
Request #{selectedRequest.number}
</span>
<span className={css.detailsLocation}>
{selectedRequestInfo?.purpose === 'compaction'
? `Compaction · ${sectionLabel(selectedRequest.turn)}`
: sectionLabel(selectedRequest.turn)}
</span>
</>
)
: promptSelected
? (
<>
<span className={`${css.kindTag} ${css.systemNeutral}`}>SYSTEM</span>
<span className={css.detailsLocation}>{selected?.cell.text}</span>
</>
)
: selected !== undefined && (
<>
<span className={`${css.kindTag} ${
selected.cell.kind === 'context'
? css.contextGreen
: selected.cell.kind === 'compacted'
? css.compacted
: selected.cell.kind === 'tool'
? css.toolAmber
: selected.cell.kind === 'message'
? css.assistantVioletBright
: selected.cell.kind === 'subtool'
? css.subtoolAmber
: css[selected.cell.kind]
}`}
>
{KIND_LABEL[selected.cell.kind]}
</span>
<span className={css.detailsLocation}>
{selected.cell.kind === 'compacted'
? sectionLabel(selected.turn)
: `${sectionLabel(selected.turn)} · ${selected.group}`}
</span>
</>
)}
</div>
<button
type="button"
className={css.close}
aria-label="Close details"
onClick={clearInspectorSelection}
>
<span aria-hidden="true">×</span>
</button>
</div>
<div className={css.detailTabs} role="tablist" aria-label="Event details">
{selectedTabs.map(tab => (
<button
key={tab.id}
id={`trajectory-detail-${tab.id}`}
type="button"
role="tab"
aria-controls="trajectory-detail-panel"
aria-selected={activeTab === tab.id}
className={activeTab === tab.id ? `${css.detailTab} ${css.detailTabActive}` : css.detailTab}
onClick={() => { activateTab(tab.id) }}
>
{tab.label}
</button>
))}
</div>
<div
id="trajectory-detail-panel"
className={activeTab === 'overview'
? `${css.detailBody} ${css.detailBodySummary}`
: css.detailBody}
role="tabpanel"
aria-labelledby={`trajectory-detail-${activeTab}`}
>
{selectedRequest !== null
&& selectedRequestState !== undefined
&& activeTab === 'overview' && (
<>
<dl className={css.overview}>
<div>
<dt>Status</dt>
<dd className={selectedRequestState === 'error' ? css.error : undefined}>
{statusLabel(selectedRequestState)}
</dd>
</div>
{selectedRequestInfo?.purpose === 'compaction' && (
<div>
<dt>Purpose</dt>
<dd>Compaction</dd>
</div>
)}
{(selectedRequestInfo?.provider
?? selectedRequestInfo?.requestConfig?.provider) !== undefined && (
<div>
<dt>Provider</dt>
<dd>
{selectedRequestInfo?.provider
?? selectedRequestInfo?.requestConfig?.provider}
</dd>
</div>
)}
{(selectedRequestInfo?.model
?? selectedRequestInfo?.requestConfig?.model) !== undefined && (
<div>
<dt>Model</dt>
<dd>
{selectedRequestInfo?.model
?? selectedRequestInfo?.requestConfig?.model}
</dd>
</div>
)}
<div>
<dt>Tool calls</dt>
<dd>{selectedRequestToolCalls}</dd>
</div>
{selectedRequestSubtoolCalls > 0 && (
<div>
<dt>Subtool calls</dt>
<dd>{selectedRequestSubtoolCalls}</dd>
</div>
)}
{selectedRequestInfo?.error !== undefined && (
<div>
<dt>Error</dt>
<dd className={css.error}>{selectedRequestInfo.error}</dd>
</div>
)}
{selectedRequestInfo?.retry !== undefined && (
<div>
<dt>Retry</dt>
<dd>
Scheduled {selectedRequestInfo.retry}
{selectedRequestInfo.maxRetries === undefined
? ''
: ` of ${selectedRequestInfo.maxRetries}`}
</dd>
</div>
)}
{selectedRequestInfo?.retryDelayMs !== undefined && (
<div>
<dt>Retry delay</dt>
<dd>{formatDurationMs(selectedRequestInfo.retryDelayMs)}</dd>
</div>
)}
{selectedRequestResult !== undefined && (
<div>
<dt>Result</dt>
<dd className={css.overviewParentLinks}>
<button
type="button"
className={css.overviewHierarchyNavLink}
onClick={() => {
openRecordSummary(selectedRequestResult)
}}
>
<span>
{selectedRequestInfo?.purpose === 'compaction'
? 'Compacted'
: 'Assistant Message'}
</span>
<IconChevronRightOutline14
className={css.overviewHierarchyJumpIconTight}
size={11}
/>
</button>
</dd>
</div>
)}
</dl>
<div className={css.overviewSections}>
{selectedRequestOptions !== undefined && (
<OverviewSection label="Options" onOpen={() => { activateTab('options') }}>
<RequestOptions options={selectedRequestOptions} preview />
</OverviewSection>
)}
<OverviewSection label="Usage" onOpen={() => { activateTab('usage') }}>
<UsageRows usage={selectedRequestUsage} />
</OverviewSection>
<OverviewSection label="Timing" onOpen={() => { activateTab('timing') }}>
<RequestTiming
assistant={selectedRequestAssistant}
anchor={selectedRequestAnchor}
request={selectedRequestInfo}
/>
</OverviewSection>
</div>
</>
)}
{selectedRequest !== null && activeTab === 'options' && (
<RequestOptions options={selectedRequestOptions} />
)}
{selectedRequest !== null && activeTab === 'usage' && (
<RequestUsagePanel
usage={selectedRequestUsage}
cumulative={selectedRequestCumulativeUsage}
/>
)}
{selectedRequest !== null && activeTab === 'timing' && (
<RequestTiming
assistant={selectedRequestAssistant}
anchor={selectedRequestAnchor}
request={selectedRequestInfo}
/>
)}
{promptSelected
&& selectedPreviousPrompt !== undefined
&& activeTab === 'diff' && (
<SystemPromptDiff
before={selectedPreviousPrompt}
after={selectedPrompt}
/>
)}
{promptSelected && activeTab === 'system-prompt' && (
selectedPrompt.system === ''
? <p className={css.noPayload}>No system prompt in this request</p>
: (
<div className={`${css.markdownPayload} ${css.systemPrompt}`}>
<MarkdownText text={selectedPrompt.system} />
</div>
)
)}
{promptSelected && activeTab === 'tools' && (
<ToolCatalog tools={selectedPrompt.tools} />
)}
{!promptSelected
&& selected?.cell.kind === 'compacted'
&& selectedState !== undefined
&& activeTab === 'overview' && (
<>
<dl className={css.overview}>
<div>
<dt>Status</dt>
<dd className={selectedState === 'error' ? css.error : undefined}>
{statusLabel(selectedState)}
</dd>
</div>
<div>
<dt>Duration</dt>
<dd>{formatElapsedSeconds(selected.cell.timeSeconds)}</dd>
</div>
<div>
<dt>Tokens</dt>
<dd></dd>
</div>
</dl>
{selected.cell.outputDetail !== undefined && (
<div className={css.compactedSummary}>
<MarkdownRecordContent
record={selected}
rendered
thinkingExpanded={thinkingExpanded}
onThinkingExpandedChange={setThinkingExpanded}
onOpenCall={openCallSummary}
/>
</div>
)}
</>
)}
{!promptSelected
&& selected !== undefined
&& selected.cell.kind !== 'compacted'
&& selectedState !== undefined
&& activeTab === 'overview' && (
<>
<dl className={css.overview}>
{selected.cell.messageSource !== undefined && (
<div>
<dt>Origin</dt>
<dd className={css.overviewParentLinks}>
<button
type="button"
className={css.overviewHierarchyNavLink}
onClick={() => { activateTab('origin') }}
>
<span>{messageOriginLabel(selected.cell.messageSource)}</span>
<IconChevronRightOutline14
className={css.overviewHierarchyJumpIconTight}
size={11}
/>
</button>
</dd>
</div>
)}
{hasSelectedHierarchy && (
<div>
<dt>
{selectedAssistantRequestTarget !== undefined
? 'Origin'
: 'Hierarchy'}
</dt>
<dd className={css.overviewParentLinks}>
{selectedAssistantRequestTarget !== undefined && (
<button
type="button"
className={css.overviewHierarchyNavLink}
onClick={() => {
selectRequest(selectedAssistantRequestTarget)
}}
>
<span>Request #{selectedAssistantRequestTarget.number}</span>
<IconChevronRightOutline14
className={css.overviewHierarchyJumpIconTight}
size={11}
/>
</button>
)}
{selectedParentMessage !== undefined && (
<button
type="button"
className={css.overviewHierarchyNavLink}
onClick={() => { openRecordSummary(selectedParentMessage) }}
>
<span>Assistant Message</span>
<IconChevronRightOutline14
className={css.overviewHierarchyJumpIconTight}
size={11}
/>
</button>
)}
{selectedParentTool !== undefined && (
<button
type="button"
className={css.overviewHierarchyNavLink}
onClick={() => { openRecordSummary(selectedParentTool) }}
>
<span>Tool Call</span>
<IconChevronRightOutline14
className={css.overviewHierarchyJumpIconTight}
size={11}
/>
</button>
)}
</dd>
</div>
)}
<div>
<dt>Status</dt>
<dd className={selectedState === 'error' ? css.error : undefined}>
{statusLabel(selectedState)}
</dd>
</div>
{selected.cell.kind === 'message' && (
<TokenRows cell={selected.cell} />
)}
{(selected.cell.kind === 'user' || selected.cell.kind === 'context') && (
<div>
<dt>Duration</dt>
<dd>{formatElapsedSeconds(selected.cell.timeSeconds)}</dd>
</div>
)}
</dl>
<div className={css.overviewSections}>
{isMarkdownRecord(selected)
? (
<>
<OverviewSection label="Preview" onOpen={() => { activateTab('rendered') }}>
<MarkdownRecordContent
record={selected}
rendered
preview
thinkingExpanded={thinkingExpanded}
onThinkingExpandedChange={setThinkingExpanded}
onOpenCall={openCallSummary}
/>
</OverviewSection>
</>
)
: (
<>
{selected.cell.inputDetail && (
<OverviewSection label="Payload" onOpen={() => { activateTab('input') }}>
<RecordPayload record={selected} direction="input" preview />
</OverviewSection>
)}
{selected.cell.outputDetail && (
<OverviewSection label="Result" onOpen={() => { activateTab('output') }}>
<RecordPayload record={selected} direction="output" preview />
</OverviewSection>
)}
<OverviewSection label="Schema" onOpen={() => { activateTab('schema') }}>
<RecordSchema record={selected} preview />
</OverviewSection>
</>
)}
{selectedAssistantRequestTarget !== undefined && (
<OverviewSection
label="Request Timing"
onOpen={() => {
selectRequest(selectedAssistantRequestTarget, 'timing')
}}
>
<RecordTiming record={selected} />
</OverviewSection>
)}
{(selected.cell.kind === 'tool' || selected.cell.kind === 'subtool') && (
<OverviewSection label="Timing" onOpen={() => { activateTab('timing') }}>
<RecordTiming record={selected} />
</OverviewSection>
)}
</div>
</>
)}
{!promptSelected && selected !== undefined && activeTab === 'rendered' && (
<MarkdownRecordContent
record={selected}
rendered
thinkingExpanded={thinkingExpanded}
onThinkingExpandedChange={setThinkingExpanded}
onOpenCall={openCallSummary}
/>
)}
{!promptSelected && selected !== undefined && activeTab === 'source' && (
<MarkdownRecordContent
record={selected}
rendered={false}
thinkingExpanded={thinkingExpanded}
onThinkingExpandedChange={setThinkingExpanded}
onOpenCall={openCallSummary}
/>
)}
{!promptSelected && selected !== undefined && activeTab === 'origin' && (
<MessageOrigin record={selected} />
)}
{!promptSelected && selected !== undefined && activeTab === 'input' && (
<RecordPayload record={selected} direction="input" />
)}
{!promptSelected && selected !== undefined && activeTab === 'output' && (
<RecordPayload record={selected} direction="output" />
)}
{!promptSelected && selected !== undefined && activeTab === 'schema' && (
<RecordSchema record={selected} />
)}
{!promptSelected && selected !== undefined && activeTab === 'timing' && (
<RecordTiming record={selected} />
)}
</div>
</aside>
)}
</div>
)
}