From adf5d5f718a6fc20b11f6ff61f0a69fa5a0346ac Mon Sep 17 00:00:00 2001 From: Sergei Shmakov Date: Tue, 8 Jul 2025 21:08:21 +0200 Subject: [PATCH 1/3] Refactors AI feedback context handling for changelog docs Centralizes management of AI feedback context and changelog document URIs into a dedicated provider, improving modularity and cleanup. Updates relevant usages to access changelog feedback context through the new provider, removing redundant context management logic from command files. Simplifies resource cleanup on document close and ensures consistent context availability for AI-related features. (#4449, #4518) --- src/commands/aiFeedback.ts | 2 +- src/commands/generateChangelog.ts | 49 ++----------------- src/container.ts | 9 ++++ src/plus/ai/utils/-webview/ai.utils.ts | 5 +- src/telemetry/aiFeedbackProvider.ts | 67 ++++++++++++++++++++++++++ 5 files changed, 82 insertions(+), 50 deletions(-) create mode 100644 src/telemetry/aiFeedbackProvider.ts diff --git a/src/commands/aiFeedback.ts b/src/commands/aiFeedback.ts index 5e1677e45a3bf..52c1c91e256f7 100644 --- a/src/commands/aiFeedback.ts +++ b/src/commands/aiFeedback.ts @@ -75,7 +75,7 @@ const uriResponses = new UriMap(); let _updateContextDebounced: Deferrable<() => void> | undefined; async function sendFeedback(container: Container, uri: Uri, sentiment: AIFeedbackEvent['sentiment']): Promise { - const context = extractAIResultContext(uri); + const context = extractAIResultContext(container, uri); if (!context) return; try { diff --git a/src/commands/generateChangelog.ts b/src/commands/generateChangelog.ts index 5abcdb7ac88bb..e5aa58c20d870 100644 --- a/src/commands/generateChangelog.ts +++ b/src/commands/generateChangelog.ts @@ -1,4 +1,4 @@ -import type { CancellationToken, ProgressOptions, Uri } from 'vscode'; +import type { CancellationToken, ProgressOptions } from 'vscode'; import { ProgressLocation, window, workspace } from 'vscode'; import type { Source } from '../constants.telemetry'; import type { Container } from '../container'; @@ -6,13 +6,10 @@ import type { GitReference } from '../git/models/reference'; import { getChangesForChangelog } from '../git/utils/-webview/log.utils'; import { createRevisionRange, shortenRevision } from '../git/utils/revision.utils'; import { showGenericErrorMessage } from '../messages'; -import type { AIGenerateChangelogChanges, AIResultContext } from '../plus/ai/aiProviderService'; +import type { AIGenerateChangelogChanges } from '../plus/ai/aiProviderService'; import { getAIResultContext } from '../plus/ai/utils/-webview/ai.utils'; import { showComparisonPicker } from '../quickpicks/comparisonPicker'; import { command } from '../system/-webview/command'; -import { setContext } from '../system/-webview/context'; -import type { Deferrable } from '../system/function/debounce'; -import { debounce } from '../system/function/debounce'; import type { Lazy } from '../system/lazy'; import { lazy } from '../system/lazy'; import { Logger } from '../system/logger'; @@ -25,36 +22,6 @@ export interface GenerateChangelogCommandArgs { source?: Source; } -// Storage for AI feedback context associated with changelog documents -const changelogFeedbackContexts = new Map(); -export function getChangelogFeedbackContext(documentUri: string): AIResultContext | undefined { - return changelogFeedbackContexts.get(documentUri); -} -function setChangelogFeedbackContext(documentUri: string, context: AIResultContext): void { - changelogFeedbackContexts.set(documentUri, context); -} -function clearChangelogFeedbackContext(documentUri: string): void { - changelogFeedbackContexts.delete(documentUri); -} - -// Storage for changelog document URIs -const changelogUris = new Set(); -let _updateChangelogContextDebounced: Deferrable<() => void> | undefined; -function updateChangelogContext(): void { - _updateChangelogContextDebounced ??= debounce(() => { - void setContext('gitlens:tabs:ai:changelog', [...changelogUris]); - }, 100); - _updateChangelogContextDebounced(); -} -function addChangelogUri(uri: Uri): void { - changelogUris.add(uri); - updateChangelogContext(); -} -function removeChangelogUri(uri: Uri): void { - changelogUris.delete(uri); - updateChangelogContext(); -} - @command() export class GenerateChangelogCommand extends GlCommandBase { constructor(private readonly container: Container) { @@ -158,17 +125,7 @@ export async function generateChangelogAndOpenMarkdownDocument( const document = await workspace.openTextDocument({ language: 'markdown', content: content }); if (feedbackContext) { // Store feedback context for this document - setChangelogFeedbackContext(document.uri.toString(), feedbackContext); - // Add to changelog URIs context even for no-results documents - addChangelogUri(document.uri); - // Clean up context when document is closed - const disposable = workspace.onDidCloseTextDocument(closedDoc => { - if (closedDoc.uri.toString() === document.uri.toString()) { - clearChangelogFeedbackContext(document.uri.toString()); - removeChangelogUri(document.uri); - disposable.dispose(); - } - }); + container.aiFeedback.addChangelogDocument(document.uri, feedbackContext); } await window.showTextDocument(document); } diff --git a/src/container.ts b/src/container.ts index 58143fb312b0d..9f90a52eace28 100644 --- a/src/container.ts +++ b/src/container.ts @@ -57,6 +57,7 @@ import type { Storage } from './system/-webview/storage'; import { memoize } from './system/decorators/-webview/memoize'; import { log } from './system/decorators/log'; import { Logger } from './system/logger'; +import { AIFeedbackProvider } from './telemetry/aiFeedbackProvider'; import { TelemetryService } from './telemetry/telemetry'; import { UsageTracker } from './telemetry/usageTracker'; import { isWalkthroughSupported, WalkthroughStateProvider } from './telemetry/walkthroughStateProvider'; @@ -365,6 +366,14 @@ export class Container { return this._ai; } + private _aiFeedback: AIFeedbackProvider | undefined; + get aiFeedback(): AIFeedbackProvider { + if (this._aiFeedback == null) { + this._disposables.push((this._aiFeedback = new AIFeedbackProvider())); + } + return this._aiFeedback; + } + private _autolinks: AutolinksProvider | undefined; get autolinks(): AutolinksProvider { if (this._autolinks == null) { diff --git a/src/plus/ai/utils/-webview/ai.utils.ts b/src/plus/ai/utils/-webview/ai.utils.ts index 3f444bf9ec415..99a1c083c58e1 100644 --- a/src/plus/ai/utils/-webview/ai.utils.ts +++ b/src/plus/ai/utils/-webview/ai.utils.ts @@ -1,7 +1,6 @@ import type { Disposable, QuickInputButton } from 'vscode'; import { env, ThemeIcon, Uri, window } from 'vscode'; import { getMarkdownDocument } from '../../../../commands/aiFeedback'; -import { getChangelogFeedbackContext } from '../../../../commands/generateChangelog'; import { Schemes } from '../../../../constants'; import type { AIProviders } from '../../../../constants.ai'; import type { Container } from '../../../../container'; @@ -284,7 +283,7 @@ export function getAIResultContext(result: AIResult): AIResultContext { }; } -export function extractAIResultContext(uri: Uri | undefined): AIResultContext | undefined { +export function extractAIResultContext(container: Container, uri: Uri | undefined): AIResultContext | undefined { if (uri?.scheme === Schemes.GitLensAIMarkdown) { const { authority } = uri; if (!authority) return undefined; @@ -304,7 +303,7 @@ export function extractAIResultContext(uri: Uri | undefined): AIResultContext | // Check for untitled documents with stored changelog feedback context if (uri?.scheme === 'untitled') { try { - return getChangelogFeedbackContext(uri.toString()); + return container.aiFeedback.getChangelogFeedback(uri.toString()); } catch { return undefined; } diff --git a/src/telemetry/aiFeedbackProvider.ts b/src/telemetry/aiFeedbackProvider.ts new file mode 100644 index 0000000000000..67a690addb7fa --- /dev/null +++ b/src/telemetry/aiFeedbackProvider.ts @@ -0,0 +1,67 @@ +import type { Disposable, Uri } from 'vscode'; +import { workspace } from 'vscode'; +import type { AIResultContext } from '../plus/ai/aiProviderService'; +import { setContext } from '../system/-webview/context'; +import type { Deferrable } from '../system/function/debounce'; +import { debounce } from '../system/function/debounce'; + +export class AIFeedbackProvider implements Disposable { + constructor() { + // Listen for document close events to clean up contexts + this._disposables.push( + workspace.onDidCloseTextDocument(document => this.removeChangelogDocument(document.uri)), + ); + } + + public addChangelogDocument(uri: Uri, context: AIResultContext): void { + this.setChangelogFeedback(uri.toString(), context); + this.addChangelogUri(uri); + } + + private removeChangelogDocument(uri: Uri): void { + this.deleteChangelogFeedback(uri.toString()); + this.removeChangelogUri(uri); + } + + private readonly _disposables: Disposable[] = []; + dispose(): void { + this._disposables.forEach(d => void d.dispose()); + this._changelogFeedbacks.clear(); + this._changelogUris.clear(); + this._updateChangelogContextDebounced = undefined; + } + + // Storage for changelog document URIs + private readonly _changelogUris = new Set(); + private _updateChangelogContextDebounced: Deferrable<() => void> | undefined; + private updateChangelogContext(): void { + this._updateChangelogContextDebounced ??= debounce(() => { + void setContext('gitlens:tabs:ai:changelog', [...this._changelogUris]); + }, 100); + this._updateChangelogContextDebounced(); + } + private addChangelogUri(uri: Uri): void { + if (!this._changelogUris.has(uri)) { + this._changelogUris.add(uri); + this.updateChangelogContext(); + } + } + private removeChangelogUri(uri: Uri): void { + if (this._changelogUris.has(uri)) { + this._changelogUris.delete(uri); + this.updateChangelogContext(); + } + } + + // Storage for AI feedback context associated with changelog documents + private readonly _changelogFeedbacks = new Map(); + getChangelogFeedback(documentUri: string): AIResultContext | undefined { + return this._changelogFeedbacks.get(documentUri); + } + private setChangelogFeedback(documentUri: string, context: AIResultContext): void { + this._changelogFeedbacks.set(documentUri, context); + } + private deleteChangelogFeedback(documentUri: string): void { + this._changelogFeedbacks.delete(documentUri); + } +} From b3806767291bfe34ac450eb7244877e1a6974076 Mon Sep 17 00:00:00 2001 From: Sergei Shmakov Date: Tue, 8 Jul 2025 21:30:16 +0200 Subject: [PATCH 2/3] Moves AI feedback URI state handling to provider Consolidates storage and context updates for AI feedback responses from the command layer into the provider, improving separation of concerns and maintainability. Ensures feedback state is managed centrally, reducing duplication and potential for inconsistencies. (#4449, #4518) --- src/commands/aiFeedback.ts | 22 ++---------- src/plus/ai/utils/-webview/ai.utils.ts | 2 +- src/telemetry/aiFeedbackProvider.ts | 50 ++++++++++++++++++++------ 3 files changed, 44 insertions(+), 30 deletions(-) diff --git a/src/commands/aiFeedback.ts b/src/commands/aiFeedback.ts index 52c1c91e256f7..2a2af4f48c0d1 100644 --- a/src/commands/aiFeedback.ts +++ b/src/commands/aiFeedback.ts @@ -6,11 +6,7 @@ import type { AIResultContext } from '../plus/ai/aiProviderService'; import { extractAIResultContext } from '../plus/ai/utils/-webview/ai.utils'; import type { QuickPickItemOfT } from '../quickpicks/items/common'; import { command } from '../system/-webview/command'; -import { setContext } from '../system/-webview/context'; -import { UriMap } from '../system/-webview/uriMap'; -import type { Deferrable } from '../system/function/debounce'; -import { debounce } from '../system/function/debounce'; -import { filterMap, map } from '../system/iterable'; +import { map } from '../system/iterable'; import { Logger } from '../system/logger'; import { createDisposable } from '../system/unifiedDisposable'; import { ActiveEditorCommand } from './commandBase'; @@ -71,15 +67,12 @@ function deleteMarkdownDocument(documentUri: string): void { _markdownDocuments.delete(documentUri); } -const uriResponses = new UriMap(); -let _updateContextDebounced: Deferrable<() => void> | undefined; - async function sendFeedback(container: Container, uri: Uri, sentiment: AIFeedbackEvent['sentiment']): Promise { const context = extractAIResultContext(container, uri); if (!context) return; try { - const previous = uriResponses.get(uri); + const previous = container.aiFeedback.getFeedbackResponse(uri); if (sentiment === previous) return; let unhelpful: UnhelpfulResult | undefined; @@ -87,16 +80,7 @@ async function sendFeedback(container: Container, uri: Uri, sentiment: AIFeedbac unhelpful = await showUnhelpfulFeedbackPicker(); } - uriResponses.set(uri, sentiment); - _updateContextDebounced ??= debounce(() => { - void setContext('gitlens:tabs:ai:helpful', [ - ...filterMap(uriResponses, ([uri, sentiment]) => (sentiment === 'helpful' ? uri : undefined)), - ]); - void setContext('gitlens:tabs:ai:unhelpful', [ - ...filterMap(uriResponses, ([uri, sentiment]) => (sentiment === 'unhelpful' ? uri : undefined)), - ]); - }, 100); - _updateContextDebounced(); + container.aiFeedback.setFeedbackResponse(uri, sentiment); sendFeedbackEvent(container, { source: 'ai:markdown-preview' }, context, sentiment, unhelpful); } catch (ex) { diff --git a/src/plus/ai/utils/-webview/ai.utils.ts b/src/plus/ai/utils/-webview/ai.utils.ts index 99a1c083c58e1..8001d6c11f8a9 100644 --- a/src/plus/ai/utils/-webview/ai.utils.ts +++ b/src/plus/ai/utils/-webview/ai.utils.ts @@ -303,7 +303,7 @@ export function extractAIResultContext(container: Container, uri: Uri | undefine // Check for untitled documents with stored changelog feedback context if (uri?.scheme === 'untitled') { try { - return container.aiFeedback.getChangelogFeedback(uri.toString()); + return container.aiFeedback.getChangelogDocument(uri.toString()); } catch { return undefined; } diff --git a/src/telemetry/aiFeedbackProvider.ts b/src/telemetry/aiFeedbackProvider.ts index 67a690addb7fa..91a14456bd350 100644 --- a/src/telemetry/aiFeedbackProvider.ts +++ b/src/telemetry/aiFeedbackProvider.ts @@ -1,9 +1,12 @@ import type { Disposable, Uri } from 'vscode'; import { workspace } from 'vscode'; +import type { AIFeedbackEvent } from '../constants.telemetry'; import type { AIResultContext } from '../plus/ai/aiProviderService'; import { setContext } from '../system/-webview/context'; +import { UriMap } from '../system/-webview/uriMap'; import type { Deferrable } from '../system/function/debounce'; import { debounce } from '../system/function/debounce'; +import { filterMap } from '../system/iterable'; export class AIFeedbackProvider implements Disposable { constructor() { @@ -14,20 +17,22 @@ export class AIFeedbackProvider implements Disposable { } public addChangelogDocument(uri: Uri, context: AIResultContext): void { - this.setChangelogFeedback(uri.toString(), context); + this.setChangelogDocument(uri.toString(), context); this.addChangelogUri(uri); } private removeChangelogDocument(uri: Uri): void { - this.deleteChangelogFeedback(uri.toString()); + this.deleteChangelogDocument(uri.toString()); this.removeChangelogUri(uri); } private readonly _disposables: Disposable[] = []; dispose(): void { this._disposables.forEach(d => void d.dispose()); - this._changelogFeedbacks.clear(); + this._uriResponses.clear(); + this._changelogDocuments.clear(); this._changelogUris.clear(); + this._updateFeedbackContextDebounced = undefined; this._updateChangelogContextDebounced = undefined; } @@ -54,14 +59,39 @@ export class AIFeedbackProvider implements Disposable { } // Storage for AI feedback context associated with changelog documents - private readonly _changelogFeedbacks = new Map(); - getChangelogFeedback(documentUri: string): AIResultContext | undefined { - return this._changelogFeedbacks.get(documentUri); + private readonly _changelogDocuments = new Map(); + getChangelogDocument(documentUri: string): AIResultContext | undefined { + return this._changelogDocuments.get(documentUri); } - private setChangelogFeedback(documentUri: string, context: AIResultContext): void { - this._changelogFeedbacks.set(documentUri, context); + private setChangelogDocument(documentUri: string, context: AIResultContext): void { + this._changelogDocuments.set(documentUri, context); } - private deleteChangelogFeedback(documentUri: string): void { - this._changelogFeedbacks.delete(documentUri); + private deleteChangelogDocument(documentUri: string): void { + this._changelogDocuments.delete(documentUri); + } + + // Storage for AI feedback responses by URI + private readonly _uriResponses = new UriMap(); + private _updateFeedbackContextDebounced: Deferrable<() => void> | undefined; + private updateFeedbackContext(): void { + this._updateFeedbackContextDebounced ??= debounce(() => { + void setContext('gitlens:tabs:ai:helpful', [ + ...filterMap(this._uriResponses, ([uri, sentiment]) => (sentiment === 'helpful' ? uri : undefined)), + ]); + void setContext('gitlens:tabs:ai:unhelpful', [ + ...filterMap(this._uriResponses, ([uri, sentiment]) => (sentiment === 'unhelpful' ? uri : undefined)), + ]); + }, 100); + this._updateFeedbackContextDebounced(); + } + setFeedbackResponse(uri: Uri, sentiment: AIFeedbackEvent['sentiment']): void { + const previous = this._uriResponses.get(uri); + if (sentiment === previous) return; + + this._uriResponses.set(uri, sentiment); + this.updateFeedbackContext(); + } + getFeedbackResponse(uri: Uri): AIFeedbackEvent['sentiment'] | undefined { + return this._uriResponses.get(uri); } } From 55d5d1989ec7d3617efb55fe1eaa669be883ee5a Mon Sep 17 00:00:00 2001 From: Sergei Shmakov Date: Thu, 31 Jul 2025 15:27:06 +0200 Subject: [PATCH 3/3] Moves AI feedback context management into provider (#4449, #4518) --- src/commands/aiFeedback.ts | 30 ++------------------------ src/commands/explainBase.ts | 3 +-- src/plus/ai/utils/-webview/ai.utils.ts | 3 +-- src/telemetry/aiFeedbackProvider.ts | 23 +++++++++++++++++--- 4 files changed, 24 insertions(+), 35 deletions(-) diff --git a/src/commands/aiFeedback.ts b/src/commands/aiFeedback.ts index 2a2af4f48c0d1..40c8816130ed8 100644 --- a/src/commands/aiFeedback.ts +++ b/src/commands/aiFeedback.ts @@ -1,5 +1,5 @@ -import type { Disposable, TextEditor, Uri } from 'vscode'; -import { window, workspace } from 'vscode'; +import type { TextEditor, Uri } from 'vscode'; +import { window } from 'vscode'; import type { AIFeedbackEvent, AIFeedbackUnhelpfulReasons, Source } from '../constants.telemetry'; import type { Container } from '../container'; import type { AIResultContext } from '../plus/ai/aiProviderService'; @@ -8,7 +8,6 @@ import type { QuickPickItemOfT } from '../quickpicks/items/common'; import { command } from '../system/-webview/command'; import { map } from '../system/iterable'; import { Logger } from '../system/logger'; -import { createDisposable } from '../system/unifiedDisposable'; import { ActiveEditorCommand } from './commandBase'; import { getCommandUri } from './commandBase.utils'; @@ -42,31 +41,6 @@ export class AIFeedbackUnhelpfulCommand extends ActiveEditorCommand { type UnhelpfulResult = { reasons?: AIFeedbackUnhelpfulReasons[]; custom?: string }; -let _documentCloseTracker: Disposable | undefined; -const _markdownDocuments = new Map(); -export function getMarkdownDocument(documentUri: string): AIResultContext | undefined { - return _markdownDocuments.get(documentUri); -} -export function setMarkdownDocument(documentUri: string, context: AIResultContext, container: Container): void { - _markdownDocuments.set(documentUri, context); - - if (!_documentCloseTracker) { - _documentCloseTracker = workspace.onDidCloseTextDocument(document => { - deleteMarkdownDocument(document.uri.toString()); - }); - container.context.subscriptions.push( - createDisposable(() => { - _documentCloseTracker?.dispose(); - _documentCloseTracker = undefined; - _markdownDocuments.clear(); - }), - ); - } -} -function deleteMarkdownDocument(documentUri: string): void { - _markdownDocuments.delete(documentUri); -} - async function sendFeedback(container: Container, uri: Uri, sentiment: AIFeedbackEvent['sentiment']): Promise { const context = extractAIResultContext(container, uri); if (!context) return; diff --git a/src/commands/explainBase.ts b/src/commands/explainBase.ts index 927a877194d62..b9d76c221fb4f 100644 --- a/src/commands/explainBase.ts +++ b/src/commands/explainBase.ts @@ -11,7 +11,6 @@ import type { AIModel } from '../plus/ai/models/model'; import { getAIResultContext } from '../plus/ai/utils/-webview/ai.utils'; import { getBestRepositoryOrShowPicker } from '../quickpicks/repositoryPicker'; import { showMarkdownPreview } from '../system/-webview/markdown'; -import { setMarkdownDocument } from './aiFeedback'; import { GlCommandBase } from './commandBase'; import { getCommandUri } from './commandBase.utils'; @@ -142,7 +141,7 @@ export abstract class ExplainCommandBase extends GlCommandBase { const content = `${headerContent}\n\n${result.parsed.summary}\n\n${result.parsed.body}`; // Store the AI result context in the feedback provider for documents that cannot store it in their URI - setMarkdownDocument(documentUri.toString(), context, this.container); + this.container.aiFeedback.setMarkdownDocument(documentUri.toString(), context); this.container.markdown.updateDocument(documentUri, content); } diff --git a/src/plus/ai/utils/-webview/ai.utils.ts b/src/plus/ai/utils/-webview/ai.utils.ts index 8001d6c11f8a9..684dec8042a58 100644 --- a/src/plus/ai/utils/-webview/ai.utils.ts +++ b/src/plus/ai/utils/-webview/ai.utils.ts @@ -1,6 +1,5 @@ import type { Disposable, QuickInputButton } from 'vscode'; import { env, ThemeIcon, Uri, window } from 'vscode'; -import { getMarkdownDocument } from '../../../../commands/aiFeedback'; import { Schemes } from '../../../../constants'; import type { AIProviders } from '../../../../constants.ai'; import type { Container } from '../../../../container'; @@ -289,7 +288,7 @@ export function extractAIResultContext(container: Container, uri: Uri | undefine if (!authority) return undefined; try { - const context: AIResultContext | undefined = getMarkdownDocument(uri.toString()); + const context: AIResultContext | undefined = container.aiFeedback.getMarkdownDocument(uri.toString()); if (context) return context; const metadata = decodeGitLensRevisionUriAuthority(authority); diff --git a/src/telemetry/aiFeedbackProvider.ts b/src/telemetry/aiFeedbackProvider.ts index 91a14456bd350..ac6db2e0dcc93 100644 --- a/src/telemetry/aiFeedbackProvider.ts +++ b/src/telemetry/aiFeedbackProvider.ts @@ -12,7 +12,9 @@ export class AIFeedbackProvider implements Disposable { constructor() { // Listen for document close events to clean up contexts this._disposables.push( - workspace.onDidCloseTextDocument(document => this.removeChangelogDocument(document.uri)), + workspace.onDidCloseTextDocument(document => { + this.removeDocument(document.uri); + }), ); } @@ -21,9 +23,11 @@ export class AIFeedbackProvider implements Disposable { this.addChangelogUri(uri); } - private removeChangelogDocument(uri: Uri): void { - this.deleteChangelogDocument(uri.toString()); + private removeDocument(uri: Uri): void { + const uriString = uri.toString(); + this.deleteChangelogDocument(uriString); this.removeChangelogUri(uri); + this.deleteMarkdownDocument(uriString); } private readonly _disposables: Disposable[] = []; @@ -31,6 +35,7 @@ export class AIFeedbackProvider implements Disposable { this._disposables.forEach(d => void d.dispose()); this._uriResponses.clear(); this._changelogDocuments.clear(); + this._markdownDocuments.clear(); this._changelogUris.clear(); this._updateFeedbackContextDebounced = undefined; this._updateChangelogContextDebounced = undefined; @@ -70,6 +75,18 @@ export class AIFeedbackProvider implements Disposable { this._changelogDocuments.delete(documentUri); } + // Storage for AI feedback context associated with any document + private readonly _markdownDocuments = new Map(); + getMarkdownDocument(documentUri: string): AIResultContext | undefined { + return this._markdownDocuments.get(documentUri); + } + setMarkdownDocument(documentUri: string, context: AIResultContext): void { + this._markdownDocuments.set(documentUri, context); + } + private deleteMarkdownDocument(documentUri: string): void { + this._markdownDocuments.delete(documentUri); + } + // Storage for AI feedback responses by URI private readonly _uriResponses = new UriMap(); private _updateFeedbackContextDebounced: Deferrable<() => void> | undefined;