import { Disposable } from "@codingame/monaco-vscode-api/vscode/vs/base/common/lifecycle"; import { URI } from "@codingame/monaco-vscode-api/vscode/vs/base/common/uri"; import { IContextMenuService } from "@codingame/monaco-vscode-api/vscode/vs/platform/contextview/browser/contextView.service"; import { IChatDebugModelTurnEvent } from "@codingame/monaco-vscode-api/vscode/vs/workbench/contrib/chat/common/chatDebugService"; import { IChatDebugService } from "@codingame/monaco-vscode-api/vscode/vs/workbench/contrib/chat/common/chatDebugService.service"; import { IChatService } from "@codingame/monaco-vscode-api/vscode/vs/workbench/contrib/chat/common/chatService/chatService.service"; /** * Navigation events fired by the Cache Explorer breadcrumb. */ export declare enum CacheExplorerNavigation { Home = "home", Overview = "overview" } /** * Cache Explorer view — the third entry under "Explore Trace Data". Shows a * left rail of model turns with their cache hit %, plus a side-by-side prompt * signature diff that pinpoints where the prefix breaks. * * v1 reads {@link IChatDebugEventModelTurnContent} from the in-memory chat * debug service via {@link IChatDebugService.resolveEvent}. Content may be * truncated by the OTel attribute cap; the file-logger backed full-fidelity * provider is a follow-up. */ export declare class ChatDebugCacheExplorerView extends Disposable { private readonly chatService; private readonly chatDebugService; private readonly contextMenuService; private readonly _onNavigate; readonly onNavigate: import("@codingame/monaco-vscode-api/vscode/vs/base/common/event").Event; readonly container: HTMLElement; private readonly breadcrumbWidget; private readonly rail; private readonly railToolbar; private readonly railList; private readonly content; private readonly sash; private railWidth; /** Disposables for the left rail (toolbar + turn rows). Cleared on every full render. */ private readonly railDisposables; /** Disposables for the right content panel. Cleared whenever the content is re-rendered. */ private readonly contentDisposables; private readonly refreshScheduler; private currentSessionResource; /** All model turns for the session, before the agent filter is applied. */ private allModelTurns; /** Model turns after the agent filter — the list the rail and diff operate on. */ private modelTurns; /** Selected turn (B side). A is computed as `selectedIndex - 1`. -1 = no explicit selection yet. */ private selectedIndex; /** * Selected agent names (keyed by {@link agentKey}). `undefined` until the * first render applies the default selection. An empty set is never stored — * clearing the last agent falls back to "all". */ private selectedAgents; /** * Turn to re-select after the next render, used to keep the user's place * when the agent filter changes. Stored as the event object rather than its * id because {@link IChatDebugModelTurnEvent.id} is optional; matching falls * back to object reference and a composite identity for turns without an id. */ private pendingSelectTurn; /** Whether the per-chunk signature breakdown table is expanded. */ private sigBreakdownOpen; /** Rail turn-row elements by turn index, for in-place selection updates without rebuilding the rail. */ private readonly railRowsByIndex; /** * Component accordion entries by component name (`system`, `tools`, * `messages[i]`), so findings and signature segments can reveal the * matching entry. We track both the outer item (for the open/flash * classes and scroll target) and the inner header (the focus target). * Rebuilt on every content render. */ private readonly componentElements; /** Selection index the breaking component was last auto-expanded for. */ private autoOpenedForIndex; /** * Memoized cross-turn session report. Keyed on the session + filtered * turn list so background refreshes with new events recompute it while * plain selection changes reuse it. */ private sessionReportCache; /** * Monotonically-increasing render token. Each call to {@link render} * captures the current value, then re-checks it after each await; if a * newer render has started in the meantime, the older one bails out * before mutating the DOM. Avoids races where a slow model-turn * resolve from one session writes into another's panel. */ private renderToken; /** Cache of resolved model-turn content keyed by event id. */ private readonly resolvedCache; /** Components currently expanded (by component name). */ private readonly openComponents; /** Rail groups currently collapsed (by group key — the parent event id). */ private readonly collapsedGroups; constructor(parent: HTMLElement, chatService: IChatService, chatDebugService: IChatDebugService, contextMenuService: IContextMenuService); setSession(sessionResource: URI): void; show(): void; hide(): void; refresh(): void; updateBreadcrumb(): void; private render; /** * Render the right-hand content panel (summary, signature, options, * components) for the current selection. Split out of {@link render} so a * selection change can refresh just the content without rebuilding the * rail \u2014 which is what keeps keyboard focus and scroll position stable * while navigating turns. * * @param preserveScroll keep the content scroll position (used for zoom * and breakdown toggles where the selection is unchanged). */ private renderContentInner; /** * Build the findings list for an A→B pair. Shared between the per-turn * content panel and the cross-turn session report. */ private buildInsights; /** * Memoization key for the session report. The report is scoped to the * turns up to (and including) the selected one, so it is stable while * later requests stream in. Undefined when there is nothing to report * (no session, or fewer than two turns in scope). * * Every in-scope turn contributes its identity AND token counts to the * key — endpoints alone would miss a middle turn replaced in place, and * token counts live on the event (not the id-cached resolved content), * so a usage update arriving after the first render must invalidate the * memoized report or the overall hit rate stays stale. */ private sessionReportKey; /** * Run the insights engine over every consecutive turn pair up to the * selected turn and aggregate the outcome. Memoized per (session, * selection prefix, agent filter) — per-turn resolution is cached in * {@link resolvedCache}, so even a cold run is one pass over in-memory * events. */ private ensureSessionReport; /** Render the session-level cache health card from the cross-turn report. */ private renderSessionHealth; /** * Select a turn (the B side of the diff) and refresh only the content * panel. The rail is updated in place \u2014 just the selected classes move \u2014 * so clicking or arrowing through turns never rebuilds the list, keeping * focus and scroll position stable. */ private selectTurn; /** Move the selection to the previous/next visible turn row (arrow keys). */ private moveSelection; /** * Render the agent filter dropdown at the top of the rail. Hidden when a * session only used a single agent (nothing to filter). */ private renderRailToolbar; private showAgentFilterMenu; /** Toggle a single agent on/off. Never leaves the selection empty. */ private toggleAgent; private setAgentSelection; /** * Render a collapsible per-chunk breakdown table. Lists every signature * chunk (including identical ones the bar may hide) with its exact char * count on each side and its share of the current request \u2014 i.e. where the * bytes are allocated. */ private renderChunkBreakdown; private resolveSide; private renderRail; private renderTitleRow; private renderSummary; /** * Render one finding row: severity icon, title, evidence, and hint. * Findings that point at a Components entry render as a button that * reveals (scrolls to, expands, and flashes) that component. */ private renderFinding; /** * Scroll the named Components entry into view, expand it, and flash it so * the eye lands on the right place. No-op when the component isn't part * of the current drift list (e.g. an identical message). */ private revealComponent; private renderSideCard; /** * Render the summary cards alone when there is no prior turn to diff * against (e.g. the first request in a brand-new session). The OTel- * reported cache hit is still useful here — the system prompt and tool * definitions can already be cached from previous sessions. */ private renderSingleSummary; private appendKv; private renderSignature; /** * Render the per-key request-options table. Shows every cache-keying * option captured from the model provider request body, with a column * for the previous turn and one for the current turn. Rows whose * values differ are highlighted. */ private renderRequestOptions; private renderComponents; private renderComponentDiff; } /** A prompt-signature segment: a synthetic prefix (system/tools) or an input message. */ export interface ISignatureSegment { readonly role: string; readonly chars: number; readonly drift: boolean; readonly label: string; /** True for the synthetic system/tools prefix segments, false for input messages. */ readonly synthetic: boolean; } /** One aligned row of the chunk breakdown — a chunk present on either or both sides. */ export interface IChunkBreakdownRow { readonly role: string; readonly label: string; readonly aChars: number | undefined; readonly bChars: number | undefined; readonly drift: boolean; } /** * Align the previous (A) and current (B) signature segments into comparable * rows for the chunk breakdown table. * * Synthetic prefix segments (system, tools) are matched by identity so that a * tool catalog or system prompt present on only one side does not shift every * later message row. Input messages are matched positionally, consistent with * the positional prompt-signature diff used elsewhere in this view. */ export declare function alignSignatureChunks(aSegs: readonly ISignatureSegment[], bSegs: readonly ISignatureSegment[]): IChunkBreakdownRow[]; /** * The agent a model turn belongs to. `requestName` carries the debug/agent * name the producer tagged the request with (e.g. `panel/editAgent`, * `backgroundTodoAgent`, or a utility name such as `title`). */ export declare function agentKey(turn: IChatDebugModelTurnEvent): string; /** Count model turns per agent, preserving first-seen order. */ export declare function computeAgentCounts(turns: readonly IChatDebugModelTurnEvent[]): Map; /** * Default agent selection: focus on the main panel edit agent when present so * background and utility calls don't clutter the rail. Falls back to all agents * when the edit agent isn't part of the session. */ export declare function defaultAgentSelection(agentCounts: Map): Set; /** * Whether two model-turn events refer to the *exact same* turn. This is the * precise identity test: the same object reference, or the same stable span * `id` when both events carry one. It never reports two distinct turns as equal, * so it is safe to scan a list with it even when several turns look alike. */ export declare function isSameModelTurn(a: IChatDebugModelTurnEvent, b: IChatDebugModelTurnEvent): boolean; /** * Resolve which turn index to select after the agent filter changes. Prefers * the previously-selected turn; when that turn no longer survives the filter — * or there was no prior selection — falls back to the most recent turn so the * selection never lands on an unrelated turn that happens to occupy the old * ordinal position. Returns -1 when there are no turns to select. * * Matching runs in two passes so the exact turn always wins: first the precise * id/reference identity ({@link isSameModelTurn}), then a best-effort composite * match for id-less turns ({@link isSimilarNoIdModelTurn}). Without the split, * an earlier look-alike turn could be picked by `findIndex` before the real * object is reached. */ export declare function resolveFilteredSelectionIndex(turns: readonly IChatDebugModelTurnEvent[], previous: IChatDebugModelTurnEvent | undefined): number;