diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index ff67a82364b..a50330e1019 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -963,10 +963,18 @@ export class ProviderId { /** @internal */ export class VersionedExtensionId { + public static tryCreate(extensionId: string | undefined, version: string | undefined): VersionedExtensionId | undefined { + if (!extensionId || !version) { + return undefined; + } + return new VersionedExtensionId(extensionId, version); + } + constructor( public readonly extensionId: string, public readonly version: string, ) { } + toString(): string { return `${this.extensionId}@${this.version}`; } diff --git a/src/vs/editor/common/textModelEditSource.ts b/src/vs/editor/common/textModelEditSource.ts index 9995fe03737..2cd2533ca1b 100644 --- a/src/vs/editor/common/textModelEditSource.ts +++ b/src/vs/editor/common/textModelEditSource.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ProviderId } from './languages.js'; +import { ProviderId, VersionedExtensionId } from './languages.js'; const privateSymbol = Symbol('TextModelEditSource'); @@ -126,10 +126,14 @@ export const EditSources = { } as const); }, - inlineChatApplyEdit(data: { modelId: string | undefined }) { + inlineChatApplyEdit(data: { modelId: string | undefined; requestId: string | undefined; languageId: string; extensionId: VersionedExtensionId | undefined }) { return createEditSource({ source: 'inlineChat.applyEdits', $modelId: avoidPathRedaction(data.modelId), + $extensionId: data.extensionId?.extensionId, + $extensionVersion: data.extensionId?.version, + $$requestId: data.requestId, + $$languageId: data.languageId, } as const); }, diff --git a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts index caebf58820a..2a95d75c278 100644 --- a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts +++ b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts @@ -201,6 +201,7 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA name: dynamicProps.name, description: dynamicProps.description, extensionId: extension, + extensionVersion: extensionDescription?.version, extensionDisplayName: extensionDescription?.displayName ?? extension.value, extensionPublisherId: extensionDescription?.publisher ?? '', publisherDisplayName: dynamicProps.publisherName, diff --git a/src/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts index 15917a1f871..6daa78a64d3 100644 --- a/src/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatParticipant.contribution.ts @@ -275,6 +275,7 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { providerDescriptor.id, { extensionId: extension.description.identifier, + extensionVersion: extension.description.version, publisherDisplayName: extension.description.publisherDisplayName ?? extension.description.publisher, // May not be present in OSS extensionPublisherId: extension.description.publisher, extensionDisplayName: extension.description.displayName ?? extension.description.name, diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts index 07af679de36..1fca283261c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts @@ -274,6 +274,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ isSticky: false, }, extensionId, + extensionVersion: extensionDescription.version, extensionDisplayName: extensionDisplayName || extensionName, extensionPublisherId, }; diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.ts index 63252fd89b1..bc968c1ba0a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.ts @@ -174,6 +174,7 @@ class SetupAgent extends Disposable implements IChatAgentImplementation { metadata: { helpTextPrefix: SetupAgent.SETUP_NEEDED_MESSAGE }, description, extensionId: nullExtensionDescription.identifier, + extensionVersion: undefined, extensionDisplayName: nullExtensionDescription.name, extensionPublisherId: nullExtensionDescription.publisher })); diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index f32aab6cb09..d493f503fa6 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -46,6 +46,7 @@ export interface IChatAgentData { /** This is string, not ContextKeyExpression, because dealing with serializing/deserializing is hard and need a better pattern for this */ when?: string; extensionId: ExtensionIdentifier; + extensionVersion: string | undefined; extensionPublisherId: string; /** This is the extension publisher id, or, in the case of a dynamically registered participant (remote agent), whatever publisher name we have for it */ publisherDisplayName?: string; @@ -582,6 +583,7 @@ export class MergedChatAgent implements IChatAgent { get fullName(): string { return this.data.fullName ?? ''; } get description(): string { return this.data.description ?? ''; } get extensionId(): ExtensionIdentifier { return this.data.extensionId; } + get extensionVersion(): string | undefined { return this.data.extensionVersion; } get extensionPublisherId(): string { return this.data.extensionPublisherId; } get extensionPublisherDisplayName() { return this.data.publisherDisplayName; } get extensionDisplayName(): string { return this.data.extensionDisplayName; } diff --git a/src/vs/workbench/contrib/chat/test/browser/chatEditingService.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatEditingService.test.ts index 8eea9346962..d08381a05d9 100644 --- a/src/vs/workbench/contrib/chat/test/browser/chatEditingService.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/chatEditingService.test.ts @@ -52,6 +52,7 @@ function getAgentData(id: string): IChatAgentData { name: id, id: id, extensionId: nullExtensionDescription.identifier, + extensionVersion: undefined, extensionPublisherId: '', publisherDisplayName: '', extensionDisplayName: '', diff --git a/src/vs/workbench/contrib/chat/test/common/chatAgents.test.ts b/src/vs/workbench/contrib/chat/test/common/chatAgents.test.ts index 5a2fa987cd7..3c43f4e22de 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatAgents.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatAgents.test.ts @@ -17,6 +17,7 @@ const testAgentData: IChatAgentData = { name: 'Test Agent', extensionDisplayName: '', extensionId: new ExtensionIdentifier(''), + extensionVersion: undefined, extensionPublisherId: '', locations: [], modes: [], diff --git a/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts b/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts index cf69d202fd3..7e1e932fef3 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts @@ -233,7 +233,7 @@ suite('ChatRequestParser', () => { // }); const getAgentWithSlashCommands = (slashCommands: IChatAgentCommand[]) => { - return { id: 'agent', name: 'agent', extensionId: nullExtensionDescription.identifier, publisherDisplayName: '', extensionDisplayName: '', extensionPublisherId: '', locations: [ChatAgentLocation.Panel], modes: [ChatModeKind.Ask], metadata: {}, slashCommands, disambiguation: [] } satisfies IChatAgentData; + return { id: 'agent', name: 'agent', extensionId: nullExtensionDescription.identifier, extensionVersion: undefined, publisherDisplayName: '', extensionDisplayName: '', extensionPublisherId: '', locations: [ChatAgentLocation.Panel], modes: [ChatModeKind.Ask], metadata: {}, slashCommands, disambiguation: [] } satisfies IChatAgentData; }; test('agent with subcommand after text', async () => { diff --git a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts index 1d47a226acf..4ff54b3d87f 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts @@ -48,6 +48,7 @@ const chatAgentWithUsedContext: IChatAgent = { id: chatAgentWithUsedContextId, name: chatAgentWithUsedContextId, extensionId: nullExtensionDescription.identifier, + extensionVersion: undefined, publisherDisplayName: '', extensionPublisherId: '', extensionDisplayName: '', @@ -82,6 +83,7 @@ const chatAgentWithMarkdown: IChatAgent = { id: chatAgentWithMarkdownId, name: chatAgentWithMarkdownId, extensionId: nullExtensionDescription.identifier, + extensionVersion: undefined, publisherDisplayName: '', extensionPublisherId: '', extensionDisplayName: '', @@ -104,6 +106,7 @@ function getAgentData(id: string): IChatAgentData { name: id, id: id, extensionId: nullExtensionDescription.identifier, + extensionVersion: undefined, extensionPublisherId: '', publisherDisplayName: '', extensionDisplayName: '', diff --git a/src/vs/workbench/contrib/chat/test/common/voiceChatService.test.ts b/src/vs/workbench/contrib/chat/test/common/voiceChatService.test.ts index 59892a5c8fa..3e567b3c794 100644 --- a/src/vs/workbench/contrib/chat/test/common/voiceChatService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/voiceChatService.test.ts @@ -27,6 +27,7 @@ suite('VoiceChat', () => { class TestChatAgent implements IChatAgent { extensionId: ExtensionIdentifier = nullExtensionDescription.identifier; + extensionVersion: string | undefined = undefined; extensionPublisher = ''; extensionDisplayName = ''; extensionPublisherId = ''; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 57b824f2a86..24bf5c2dfb2 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -27,7 +27,7 @@ import { IPosition, Position } from '../../../../editor/common/core/position.js' import { IRange, Range } from '../../../../editor/common/core/range.js'; import { ISelection, Selection, SelectionDirection } from '../../../../editor/common/core/selection.js'; import { IEditorContribution } from '../../../../editor/common/editorCommon.js'; -import { TextEdit } from '../../../../editor/common/languages.js'; +import { TextEdit, VersionedExtensionId } from '../../../../editor/common/languages.js'; import { IValidEditOperation } from '../../../../editor/common/model.js'; import { IEditorWorkerService } from '../../../../editor/common/services/editorWorker.js'; import { DefaultModelSHA1Computer } from '../../../../editor/common/services/modelService.js'; @@ -51,7 +51,7 @@ import { CTX_INLINE_CHAT_EDITING, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, CTX_INLIN import { HunkInformation, Session, StashedSession } from './inlineChatSession.js'; import { IInlineChatSession2, IInlineChatSessionService } from './inlineChatSessionService.js'; import { InlineChatError } from './inlineChatSessionServiceImpl.js'; -import { HunkAction, IEditObserver, LiveStrategy, ProgressingEditsOptions } from './inlineChatStrategies.js'; +import { HunkAction, IEditObserver, IInlineChatMetadata, LiveStrategy, ProgressingEditsOptions } from './inlineChatStrategies.js'; import { EditorBasedInlineChatWidget } from './inlineChatWidget.js'; import { InlineChatZoneWidget } from './inlineChatZoneWidget.js'; import { ChatAgentLocation } from '../../chat/common/constants.js'; @@ -1036,13 +1036,23 @@ export class InlineChatController1 implements IEditorContribution { stop: () => this._session!.hunkData.ignoreTextModelNChanges = false, }; + const metadata = this._getMetadata(); if (opts) { - await this._strategy.makeProgressiveChanges(editOperations, editsObserver, opts, undoStopBefore); + await this._strategy.makeProgressiveChanges(editOperations, editsObserver, opts, undoStopBefore, metadata); } else { - await this._strategy.makeChanges(editOperations, editsObserver, undoStopBefore); + await this._strategy.makeChanges(editOperations, editsObserver, undoStopBefore, metadata); } } + private _getMetadata(): IInlineChatMetadata { + const lastRequest = this._session?.chatModel.lastRequest; + return { + extensionId: VersionedExtensionId.tryCreate(this._session?.agent.extensionId.value, this._session?.agent.extensionVersion), + modelId: lastRequest?.modelId, + requestId: lastRequest?.id, + }; + } + private _updatePlaceholder(): void { this._ui.value.widget.placeholder = this._session?.agent.description ?? localize('askOrEditInContext', 'Ask or edit in context'); } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index 551435f3040..3cf153e78f9 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -41,6 +41,8 @@ import { ConflictActionsFactory, IContentWidgetAction } from '../../mergeEditor/ import { observableValue } from '../../../../base/common/observable.js'; import { IMenuService, MenuItemAction } from '../../../../platform/actions/common/actions.js'; import { InlineDecoration, InlineDecorationType } from '../../../../editor/common/viewModel/inlineDecorations.js'; +import { EditSources } from '../../../../editor/common/textModelEditSource.js'; +import { VersionedExtensionId } from '../../../../editor/common/languages.js'; export interface IEditObserver { start(): void; @@ -141,11 +143,11 @@ export class LiveStrategy { return this._session.hunkData.discardAll(); } - async makeChanges(edits: ISingleEditOperation[], obs: IEditObserver, undoStopBefore: boolean): Promise { - return this._makeChanges(edits, obs, undefined, undefined, undoStopBefore); + async makeChanges(edits: ISingleEditOperation[], obs: IEditObserver, undoStopBefore: boolean, metadata: IInlineChatMetadata): Promise { + return this._makeChanges(edits, obs, undefined, undefined, undoStopBefore, metadata); } - async makeProgressiveChanges(edits: ISingleEditOperation[], obs: IEditObserver, opts: ProgressingEditsOptions, undoStopBefore: boolean): Promise { + async makeProgressiveChanges(edits: ISingleEditOperation[], obs: IEditObserver, opts: ProgressingEditsOptions, undoStopBefore: boolean, metadata: IInlineChatMetadata): Promise { // add decorations once per line that got edited const progress = new Progress(edits => { @@ -165,10 +167,10 @@ export class LiveStrategy { this._progressiveEditingDecorations.append(newDecorations); }); - return this._makeChanges(edits, obs, opts, progress, undoStopBefore); + return this._makeChanges(edits, obs, opts, progress, undoStopBefore, metadata); } - private async _makeChanges(edits: ISingleEditOperation[], obs: IEditObserver, opts: ProgressingEditsOptions | undefined, progress: Progress | undefined, undoStopBefore: boolean): Promise { + private async _makeChanges(edits: ISingleEditOperation[], obs: IEditObserver, opts: ProgressingEditsOptions | undefined, progress: Progress | undefined, undoStopBefore: boolean, metadata: IInlineChatMetadata): Promise { // push undo stop before first edit if (undoStopBefore) { @@ -176,6 +178,12 @@ export class LiveStrategy { } this._editCount++; + const editSource = EditSources.inlineChatApplyEdit({ + modelId: metadata.modelId, + extensionId: metadata.extensionId, + requestId: metadata.requestId, + languageId: this._session.textModelN.getLanguageId(), + }); if (opts) { // ASYNC @@ -185,7 +193,7 @@ export class LiveStrategy { const speed = wordCount / durationInSec; // console.log({ durationInSec, wordCount, speed: wordCount / durationInSec }); const asyncEdit = asProgressiveEdit(new WindowIntervalTimer(this._zone.domNode), edit, speed, opts.token); - await performAsyncTextEdit(this._session.textModelN, asyncEdit, progress, obs); + await performAsyncTextEdit(this._session.textModelN, asyncEdit, progress, obs, editSource); } } else { @@ -194,7 +202,7 @@ export class LiveStrategy { this._session.textModelN.pushEditOperations(null, edits, (undoEdits) => { progress?.report(undoEdits); return null; - }); + }, undefined, editSource); obs.stop(); } } @@ -576,3 +584,9 @@ function changeDecorationsAndViewZones(editor: ICodeEditor, callback: (accessor: }); }); } + +export interface IInlineChatMetadata { + modelId: string | undefined; + extensionId: VersionedExtensionId | undefined; + requestId: string | undefined; +} diff --git a/src/vs/workbench/contrib/inlineChat/browser/utils.ts b/src/vs/workbench/contrib/inlineChat/browser/utils.ts index 737d4863352..df93be8528c 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/utils.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/utils.ts @@ -11,7 +11,7 @@ import { IProgress } from '../../../../platform/progress/common/progress.js'; import { IntervalTimer, AsyncIterableSource } from '../../../../base/common/async.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; import { getNWords } from '../../chat/common/chatWordCounter.js'; -import { EditSources } from '../../../../editor/common/textModelEditSource.js'; +import { TextModelEditSource } from '../../../../editor/common/textModelEditSource.js'; @@ -22,7 +22,7 @@ export interface AsyncTextEdit { readonly newText: AsyncIterable; } -export async function performAsyncTextEdit(model: ITextModel, edit: AsyncTextEdit, progress?: IProgress, obs?: IEditObserver) { +export async function performAsyncTextEdit(model: ITextModel, edit: AsyncTextEdit, progress?: IProgress, obs?: IEditObserver, editSource?: TextModelEditSource) { const [id] = model.deltaDecorations([], [{ range: edit.range, @@ -52,7 +52,7 @@ export async function performAsyncTextEdit(model: ITextModel, edit: AsyncTextEdi model.pushEditOperations(null, [edit], (undoEdits) => { progress?.report(undoEdits); return null; - }, undefined, EditSources.inlineChatApplyEdit({ modelId: undefined })); + }, undefined, editSource); obs?.stop(); first = false; diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index 3f060769ca9..5fa87e15225 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -85,6 +85,7 @@ suite('InlineChatController', function () { const agentData = { extensionId: nullExtensionDescription.identifier, + extensionVersion: undefined, publisherDisplayName: '', extensionDisplayName: '', extensionPublisherId: '', diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts index c4129a03941..cb810cd5d7f 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts @@ -145,6 +145,7 @@ suite('InlineChatSession', function () { instaService.get(IChatAgentService).registerDynamicAgent({ extensionId: nullExtensionDescription.identifier, + extensionVersion: undefined, publisherDisplayName: '', extensionDisplayName: '', extensionPublisherId: '', diff --git a/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookCellDiagnostics.test.ts b/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookCellDiagnostics.test.ts index c3983cdac50..f99fceca3b8 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookCellDiagnostics.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookCellDiagnostics.test.ts @@ -68,6 +68,7 @@ suite('notebookCellDiagnostics', () => { const agentData = { extensionId: nullExtensionDescription.identifier, + extensionVersion: undefined, extensionDisplayName: '', extensionPublisherId: '', name: 'testEditorAgent', diff --git a/src/vs/workbench/contrib/terminalContrib/chat/test/browser/terminalInitialHint.test.ts b/src/vs/workbench/contrib/terminalContrib/chat/test/browser/terminalInitialHint.test.ts index 1616d3317c2..5e1da3dae92 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/test/browser/terminalInitialHint.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/test/browser/terminalInitialHint.test.ts @@ -28,6 +28,7 @@ suite('Terminal Initial Hint Addon', () => { id: 'termminal', name: 'terminal', extensionId: new ExtensionIdentifier('test'), + extensionVersion: undefined, extensionPublisherId: 'test', extensionDisplayName: 'test', metadata: {}, @@ -41,6 +42,7 @@ suite('Terminal Initial Hint Addon', () => { id: 'editor', name: 'editor', extensionId: new ExtensionIdentifier('test-editor'), + extensionVersion: undefined, extensionPublisherId: 'test-editor', extensionDisplayName: 'test-editor', metadata: {},