chatDebugServiceImpl.ts ×16

Frontier kind: Code frontier

unlabeled · c_937236ebfc11

117 tests · 10771 LOC · 49 files · introduces 0 tests · 135 LOC · 1 file

Introduces — evidence that enters the hierarchy at this concept

Code
16 ranges135 lines · 1 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
1318 ranges10771 lines · 49 files · Browse complete extent
All tests (intent)
117 testsBrowse complete intent

Neighbourhood graph

The orange circle is the focus. Violet and green circles are every ancestor and descendant, broader and narrower, at any distance; blue squares and pink diamonds are the introduced files and exact introduced tests of every visible concept, not only the focus's. Arrows point from broader to narrower concepts and bridge only concepts omitted from this view. Undirected links show source or test introduction. Concept and file size follows LOC; exact test nodes use test-count units.

Introduced files, introduced tests, and structurally relevant concept specialization

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the native relationship evidence on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the native relationship evidence on this page while the interactive map is unavailable.

Native relationship evidence

Every exact file and test below is linked only from the concept that introduces it.

Introduced tests

Every collected test enters the hierarchy at exactly one concept.

No tests are introduced at this concept. Its intent tests are introduced by other concepts.

Introduced code

Every collected source range enters the hierarchy at exactly one concept.

1 file ranked by introduced lines: 135 introduced LOC across 16 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/workbench/contrib/chat/common/chatDebugServiceImpl.ts 135 introduced LOC · 16 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- chatDebugServiceImpl.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { timeout } from '../../../../base/common/async.js';
7 > import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js';
8 > import { Emitter, Event } from '../../../../base/common/event.js';
9 > import { onUnexpectedError } from '../../../../base/common/errors.js';
10 > import { Disposable, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
11 > import { ResourceMap } from '../../../../base/common/map.js';
12 > import { extUri } from '../../../../base/common/resources.js';
13 > import { URI } from '../../../../base/common/uri.js';
14 > import { ChatDebugLogLevel, IChatDebugEvent, IChatDebugLogProvider, IChatDebugResolvedEventContent, IChatDebugService } from './chatDebugService.js';
15 > import { isAgentHostTarget, localChatSessionType } from './chatSessionsService.js';
16 > import { getChatSessionType } from './model/chatUri.js';
17 > import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
18 > import { AgentHostAgentDebugLogMaxEventsSettingId } from './promptSyntax/promptTypes.js';
19 >
20 > /**
21 > * Per-session circular buffer for debug events.
22 > * Stores up to `capacity` events using a ring buffer.
23 > */
24 > class SessionEventBuffer {
25 > private readonly _buffer: (IChatDebugEvent | undefined)[];
26 > private _head = 0;
27 > private _size = 0;
28 >
29 > constructor(readonly capacity: number) {
30 this._buffer = new Array(capacity);
31 }
33 > get size(): number {
34 return this._size;
35 }
37 > push(event: IChatDebugEvent): void {
38 const idx = (this._head + this._size) % this.capacity;
39 this._buffer[idx] = event;
44 }
45 }
47 > /** Return events in insertion order. */
48 > toArray(): IChatDebugEvent[] {
49 const result: IChatDebugEvent[] = [];
50 for (let i = 0; i < this._size; i++) {
56 return result;
57 }
59 > /** Remove events matching the predicate and compact in-place. */
60 > removeWhere(predicate: (event: IChatDebugEvent) => boolean): void {
61 let write = 0;
62 for (let i = 0; i < this._size; i++) {
77 this._size = write;
78 }
80 > clear(): void {
81 this._buffer.fill(undefined);
82 this._head = 0;
83 this._size = 0;
84 }
86 >
87 > export class ChatDebugServiceImpl extends Disposable implements IChatDebugService {
88 > declare readonly _serviceBrand: undefined;
89 >
90 > static readonly MAX_EVENTS_PER_SESSION = 10_000;
91 > static readonly MAX_SESSIONS = 5;
92 >
93 > /** Per-session event buffers. Ordered from oldest to newest session (LRU). */
94 > private readonly _sessionBuffers = new ResourceMap<SessionEventBuffer>();
95 > /** Ordered list of session URIs for LRU eviction. */
96 > private readonly _sessionOrder: URI[] = [];
97 > /** Per-session tracking of seen event IDs to deduplicate events
98 > * that share the same ID (e.g. subagentInvocation + userMessage
99 > * emitted from the same span). Stores id → event kind so we can
100 > * keep the richer event kind on collision. */
101 > private readonly _seenEventIds = new ResourceMap<Map<string, IChatDebugEvent['kind']>>();
102 >
103 > private readonly _onDidAddEvent = this._register(new Emitter<IChatDebugEvent>());
104 > readonly onDidAddEvent: Event<IChatDebugEvent> = this._onDidAddEvent.event;
105 >
106 > private readonly _onDidClearProviderEvents = this._register(new Emitter<URI>());
107 > readonly onDidClearProviderEvents: Event<URI> = this._onDidClearProviderEvents.event;
108 >
109 > private readonly _onDidEndSession = this._register(new Emitter<URI>());
110 > readonly onDidEndSession: Event<URI> = this._onDidEndSession.event;
111 >
112 > private readonly _onDidChangeAvailableSessionResources = this._register(new Emitter<void>());
113 > readonly onDidChangeAvailableSessionResources: Event<void> = this._onDidChangeAvailableSessionResources.event;
114 >
115 > private readonly _providers = new Set<IChatDebugLogProvider>();
116 > private readonly _invocationCts = new ResourceMap<CancellationTokenSource>();
117 >
118 > /**
119 > * Sessions whose provider events should be cleared before the next batch of
120 > * provider events is applied. The clear is deferred until the first new
121 > * provider event actually arrives so that a provider which transiently
122 > * returns nothing (e.g. an Agent Host `events.jsonl` mid-rewrite) does not
123 > * wipe the events currently shown.
124 > */
125 > private readonly _pendingProviderClear = new ResourceMap<boolean>();
126 >
127 > /** Events that were returned by providers (not internally logged). */
128 > private readonly _providerEvents = new WeakSet<IChatDebugEvent>();
129 >
130 > /** Session URIs created via import. */
131 > private readonly _importedSessions = new ResourceMap<boolean>();
132 >
133 > /** Session URIs reported by providers as available on disk (historical sessions). */
134 > private readonly _availableSessionResources: URI[] = [];
135 > private readonly _availableSessionResourceSet = new Set<string>();
136 >
137 > /** Titles for historical sessions discovered from disk. */
138 > private readonly _historicalSessionTitles = new ResourceMap<string>();
139 >
140 > /** Human-readable titles for imported sessions. */
141 > private readonly _importedSessionTitles = new ResourceMap<string>();
142 >
143 > activeSessionResource: URI | undefined;
144 >
145 > constructor(
146 @IConfigurationService private readonly _configurationService: IConfigurationService,
147 ) {
148 super();
149 }
151 > /** Priority for deduplicating events with the same ID: lower = richer. */
152 > private static readonly _eventKindPriority: Record<string, number> = {
153 > subagentInvocation: 0,
154 > modelTurn: 1,
155 > toolCall: 2,
156 > agentResponse: 3,
157 > userMessage: 4,
158 > generic: 5,
159 > };
160 >
161 > /** Session types eligible for debug logging and provider invocation. */
162 > private static readonly _debugEligibleSessionTypes = new Set([
163 > localChatSessionType, // local sessions
164 > 'copilotcli', // Copilot CLI background sessions
165 > 'agent-host-copilotcli', // local Agent Host Copilot CLI sessions
166 > 'claude-code', // Claude Code CLI sessions
167 > ]);
168
169 private _isDebugEligibleSession(sessionResource: URI): boolean {
537 private readonly _availableSessionsFetchers = new Set<{ readonly fetcher: (token: CancellationToken) => Promise<{ uri: URI; title?: string }[]>; started: boolean }>();
538 private _availableSessionsRequested = false;
540 > getAvailableSessionResources(): readonly URI[] {
541 // Trigger lazy fetch when both a fetcher is registered and this getter is called.
542 this._availableSessionsRequested = true;
553 return result;
554 }
556 > registerAvailableSessionsFetcher(fetcher: (token: CancellationToken) => Promise<{ uri: URI; title?: string }[]>): IDisposable {
557 const entry = { fetcher, started: false };
558 this._availableSessionsFetchers.add(entry);
561 return toDisposable(() => this._availableSessionsFetchers.delete(entry));
562 }
564 > private _tryFetchAvailableSessions(): void {
565 if (!this._availableSessionsRequested) {
566 return;
579 }
580 }
582 > getHistoricalSessionTitle(sessionResource: URI): string | undefined {
583 return this._historicalSessionTitles.get(sessionResource);
584 }
586 > async exportLog(sessionResource: URI): Promise<Uint8Array | undefined> {
587 for (const provider of this._providers) {
588 if (provider.provideChatDebugLogExport) {
599 return undefined;
600 }
602 > async importLog(data: Uint8Array): Promise<URI | undefined> {
603 for (const provider of this._providers) {
604 if (provider.resolveChatDebugLogImport) {
616 return undefined;
617 }
619 > override dispose(): void {
620 for (const cts of this._invocationCts.values()) {
621 cts.cancel();