Path: blob/main/src/vs/workbench/contrib/editTelemetry/browser/aiContributionFeature.ts
13401 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js';6import { ResourceMap } from '../../../../base/common/map.js';7import { autorun } from '../../../../base/common/observable.js';8import { URI, UriComponents } from '../../../../base/common/uri.js';9import { CommandsRegistry } from '../../../../platform/commands/common/commands.js';10import { AnnotatedDocument, IAnnotatedDocuments } from './helpers/annotatedDocuments.js';11import { createDocWithJustReason } from './helpers/documentWithAnnotatedEdits.js';12import { DocumentEditSourceTracker } from './telemetry/editTracker.js';1314export type AiContributionLevel = 'chatAndAgent' | 'all';1516interface TrackerEntry {17readonly trackerStore: DisposableStore;18readonly tracker: DocumentEditSourceTracker;19}2021/**22* Tracks AI-generated edits across open documents using the edit telemetry pipeline.23*/24export class AiContributionFeature extends Disposable {2526private readonly _trackers = new ResourceMap<TrackerEntry>();27private readonly _documentsByUri = new ResourceMap<AnnotatedDocument>();2829constructor(30annotatedDocuments: IAnnotatedDocuments,31) {32super();3334this._register(autorun(reader => {35const docs = annotatedDocuments.documents.read(reader);36const activeUris = new ResourceMap<boolean>();3738for (const doc of docs) {39const uri = doc.document.uri;40activeUris.set(uri, true);41this._documentsByUri.set(uri, doc);4243if (!this._trackers.has(uri)) {44this._trackers.set(uri, this._createTrackerEntry(doc));45}46}4748for (const [uri, entry] of this._trackers) {49if (!activeUris.has(uri)) {50entry.trackerStore.dispose();51this._trackers.delete(uri);52this._documentsByUri.delete(uri);53}54}55}));5657this._register(CommandsRegistry.registerCommand('_aiEdits.hasAiContributions', (_accessor, resources: UriComponents[], level: AiContributionLevel) => {58return this._hasAiContributions(resources, level);59}));6061this._register(CommandsRegistry.registerCommand('_aiEdits.clearAiContributions', (_accessor, resources: UriComponents[]) => {62this._clearAiContributions(resources);63}));6465this._register(CommandsRegistry.registerCommand('_aiEdits.clearAllAiContributions', () => {66this._clearAiContributions();67}));68}6970override dispose(): void {71for (const [, entry] of this._trackers) {72entry.trackerStore.dispose();73}74super.dispose();75}7677private _createTrackerEntry(doc: AnnotatedDocument): TrackerEntry {78const trackerStore = new DisposableStore();79const docWithJustReason = createDocWithJustReason(doc.documentWithAnnotations, trackerStore);80const tracker = trackerStore.add(new DocumentEditSourceTracker(docWithJustReason, undefined));81return { trackerStore, tracker };82}8384private _hasAiContributions(resources: UriComponents[], level: AiContributionLevel): boolean {85for (const resource of resources) {86const entry = this._trackers.get(URI.revive(resource));87if (entry) {88for (const edit of entry.tracker.getTrackedRanges()) {89if (edit.source.category === 'ai' && (level === 'all' || edit.source.feature === 'chat')) {90return true;91}92}93}94}95return false;96}9798private _clearAiContributions(resources?: UriComponents[]): void {99const uris = resources ? resources.map(r => URI.revive(r)) : [...this._trackers.keys()];100for (const uri of uris) {101const entry = this._trackers.get(uri);102if (entry) {103entry.trackerStore.dispose();104const doc = this._documentsByUri.get(uri);105if (doc) {106this._trackers.set(uri, this._createTrackerEntry(doc));107} else {108this._trackers.delete(uri);109this._documentsByUri.delete(uri);110}111}112}113}114}115116117