import type { Message, MessagePart } from '../../types/api'; import { type PreloadedContextSummary } from './assistantTurnModel'; import { type CompactActivityEntry } from './compactActivity'; /** * The thread renders one list row per *persisted message part*, plus separate * rows for the chrome around a turn (headers, live status, approvals, errors, * footers), so a streaming part invalidates and re-lays out only its own row. * * The single exception is compact-thread exploratory activity. There a * contiguous run of reads/searches/reasoning parts is presented as *one* * bordered, height-constrained activity box that collapses to a one-line * summary when the run is finished — see {@link CompactActivityGroup}. That * presentation is a property of the run, not of any single part, so the run * owns one row (`assistant-compact-group`) keyed on its first part. Inside the * row every part still keeps its own keyed entry, and the row's identity only * changes when one of its own parts changes. */ export type ThreadRow = { kind: 'user'; key: string; messageId: string; endsTurn: boolean; message: Message; /** * Deliberately no positional field here: a user row must not depend on * its index in the thread, otherwise prepending an older page would * invalidate the previously first row and cost a re-measure. */ nextAssistantMessageId?: string; } | { kind: 'assistant-header'; key: string; messageId: string; endsTurn: boolean; message: Message; } | { kind: 'assistant-context'; key: string; messageId: string; endsTurn: boolean; context: PreloadedContextSummary; showLine: boolean; } | { kind: 'assistant-item'; key: string; messageId: string; endsTurn: boolean; /** The single persisted (or ephemeral) part this row renders. */ part: MessagePart; /** * Renderer selected up-front so the row component stays thin. * `suppressed` parts have no timeline content (their payload belongs to * the status row or the todo panel, or they are empty) but still own a * measurable row, so the part → row mapping stays 1:1. */ variant: 'compaction' | 'action' | 'part' | 'suppressed'; showLine: boolean; isFirstPart: boolean; /** * True while this tool call has no persisted result yet, i.e. it still * renders its live box. Derived from the call's own result rather than * from its position, so later parts never resize this row. */ isLiveToolCall: boolean; isLastMessage: boolean; canRetry: boolean; } | { /** * One contiguous run of compact exploratory activity, rendered as the * single bordered/scrolling activity box the compact thread has always * used. Keyed on the run's first part so the row survives every append * to the run. */ kind: 'assistant-compact-group'; key: string; messageId: string; endsTurn: boolean; /** The persisted parts this box covers, in thread order. */ parts: MessagePart[]; /** One keyed entry per rendered part; identity is stable per part. */ entries: CompactActivityEntry[]; /** Latest `progress_update` text, used as the collapsed title. */ titleOverride?: string; /** Collapsed runs show the one-line summary instead of the log. */ collapsed: boolean; showLine: boolean; } | { kind: 'assistant-approvals'; key: string; messageId: string; endsTurn: boolean; } | { kind: 'assistant-status'; key: string; messageId: string; endsTurn: boolean; message: Message; variant: 'tool' | 'progress' | 'loading'; part: MessagePart | null; showLine: boolean; isFirstPart: boolean; } | { kind: 'assistant-error'; key: string; messageId: string; endsTurn: boolean; error: string; } | { kind: 'assistant-footer'; key: string; messageId: string; endsTurn: boolean; message: Message; } | { /** * Divider that hides an older turn's tool work. The latest turn never * emits this row — its tool calls stay visible. */ kind: 'assistant-show-work'; key: string; messageId: string; endsTurn: boolean; expanded: boolean; }; export interface ThreadRowsResult { rows: ThreadRow[]; /** Row index of the first row of each visible message, for the navigator. */ rowIndexByMessageIndex: number[]; } /** * Presentation class of a row, used as LegendList's `getItemType`. * * The list keeps a running size average *per type*, so the types have to track * what actually drives height rather than just the row's kind: a one-line * suppressed placeholder, a paragraph of markdown, a tool card and a collapsed * activity summary differ by an order of magnitude. Mixing them into one * average is what makes predicted offsets wrong during a fast flick, which is * what surfaces as blank rows. */ export declare function getThreadRowType(row: ThreadRow): string; interface BuildThreadRowsOptions { messages: Message[]; sessionId?: string; compact: boolean; currentMessageId: string | null; queueLength: number; queuedMessageIds: ReadonlySet; /** * Per-thread row identity cache. Threads can be mounted side by side (the * subagent viewer over a session, canvas blocks, desktop panes), and they * must not share one cache: each build evicts the keys it did not see, so a * shared cache would recreate every row of the *other* thread on every * rebuild and force LegendList to re-measure its whole viewport. */ cache?: ThreadRowCache; /** * Older turns whose tool work the reader has expanded. The latest turn is * always expanded and is not looked up here. */ expandedWorkMessageIds?: ReadonlySet; } /** * Rows are recreated on every rebuild, but LegendList keeps measurements only * while item identity is stable. Reusing the previous row object whenever every * field is unchanged is what stops a prepend (or an unrelated stream delta) * from invalidating the whole viewport. */ export interface ThreadRowCache { /** Previous row object per key, reused when every rendered field matches. */ rows: Map; /** * Ids of assistant turns this thread has rendered while they were still * pending. Such a turn keeps its expanded per-part rows after completion: * flipping it to the auto-compacted representation at the * pending→complete instant would replace every in-view row key in one * data change and yank the viewport. Turns first seen complete (initial * load, prepends, session switches) still auto-compact. */ liveTurnIds: Set; } /** Creates a row identity cache scoped to a single thread instance. */ export declare function createThreadRowCache(): ThreadRowCache; /** True when two row lists are element-wise identical (identity comparison). */ export declare function sameThreadRows(left: readonly ThreadRow[], right: readonly ThreadRow[]): boolean; /** * Flattens the visible thread into stable list rows. Row keys are derived from * message/part ids so LegendList keeps measurements across rebuilds, and every * persisted part maps to exactly one row. */ export declare function buildThreadRows({ messages, sessionId, compact, currentMessageId, queueLength, queuedMessageIds, cache, expandedWorkMessageIds, }: BuildThreadRowsOptions): ThreadRowsResult; /** Test seam: clears the shared fallback row identity cache. */ export declare function resetThreadRowCache(): void; export {}; //# sourceMappingURL=threadRowModel.d.ts.map