mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-02 22:41:31 +01:00
feat: instrument accepting and rejecting chat edits (#230748)
* feat: instrument accepting and rejecting chat edits * eslint * Fix compile
This commit is contained in:
@@ -1742,6 +1742,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
EditSessionIdentityMatch: EditSessionIdentityMatch,
|
||||
InteractiveSessionVoteDirection: extHostTypes.InteractiveSessionVoteDirection,
|
||||
ChatCopyKind: extHostTypes.ChatCopyKind,
|
||||
ChatEditingSessionActionOutcome: extHostTypes.ChatEditingSessionActionOutcome,
|
||||
InteractiveEditorResponseFeedbackKind: extHostTypes.InteractiveEditorResponseFeedbackKind,
|
||||
DebugStackFrame: extHostTypes.DebugStackFrame,
|
||||
DebugThread: extHostTypes.DebugThread,
|
||||
|
||||
@@ -2868,6 +2868,8 @@ export namespace ChatAgentUserActionEvent {
|
||||
return { action: followupAction, result: ehResult };
|
||||
} else if (event.action.kind === 'inlineChat') {
|
||||
return { action: { kind: 'editor', accepted: event.action.action === 'accepted' }, result: ehResult };
|
||||
} else if (event.action.kind === 'chatEditingSessionAction') {
|
||||
return { action: { kind: 'chatEditingSessionAction', outcome: event.action.outcome === 'accepted' ? types.ChatEditingSessionActionOutcome.Accepted : types.ChatEditingSessionActionOutcome.Rejected, uri: URI.revive(event.action.uri), hasRemainingEdits: event.action.hasRemainingEdits }, result: ehResult };
|
||||
} else {
|
||||
return { action: event.action, result: ehResult };
|
||||
}
|
||||
|
||||
@@ -4342,6 +4342,11 @@ export class ChatCompletionItem implements vscode.ChatCompletionItem {
|
||||
}
|
||||
}
|
||||
|
||||
export enum ChatEditingSessionActionOutcome {
|
||||
Accepted = 1,
|
||||
Rejected = 2
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Interactive Editor
|
||||
|
||||
@@ -170,7 +170,7 @@ export class ChatEditingService extends Disposable implements IChatEditingServic
|
||||
public triggerEditComputation(responseModel: IChatResponseModel): Promise<void> {
|
||||
return this._continueEditingSession(async (builder, token) => {
|
||||
const codeMapperResponse: ICodeMapperResponse = {
|
||||
textEdit: (resource, edits) => builder.textEdits(resource, edits),
|
||||
textEdit: (resource, edits) => builder.textEdits(resource, edits, responseModel),
|
||||
};
|
||||
await this._codeMapperService.mapCodeFromResponse(responseModel, codeMapperResponse, token);
|
||||
}, { silent: true });
|
||||
@@ -246,8 +246,8 @@ export class ChatEditingService extends Disposable implements IChatEditingServic
|
||||
}
|
||||
|
||||
const stream: IChatEditingSessionStream = {
|
||||
textEdits: (resource: URI, textEdits: TextEdit[]) => {
|
||||
session.acceptTextEdits(resource, textEdits);
|
||||
textEdits: (resource: URI, textEdits: TextEdit[], responseModel: IChatResponseModel) => {
|
||||
session.acceptTextEdits(resource, textEdits, responseModel);
|
||||
}
|
||||
};
|
||||
session.acceptStreamingEditsStart();
|
||||
@@ -573,14 +573,14 @@ class ChatEditingSession extends Disposable implements IChatEditingSession {
|
||||
this._sequencer.queue(() => this._acceptStreamingEditsStart());
|
||||
}
|
||||
|
||||
acceptTextEdits(resource: URI, textEdits: TextEdit[]): void {
|
||||
acceptTextEdits(resource: URI, textEdits: TextEdit[], responseModel: IChatResponseModel): void {
|
||||
if (this._state.get() === ChatEditingSessionState.Disposed) {
|
||||
// we don't throw in this case because there could be a builder still connected to a disposed session
|
||||
return;
|
||||
}
|
||||
|
||||
// ensure that the edits are processed sequentially
|
||||
this._sequencer.queue(() => this._acceptTextEdits(resource, textEdits));
|
||||
this._sequencer.queue(() => this._acceptTextEdits(resource, textEdits, responseModel));
|
||||
}
|
||||
|
||||
resolve(): void {
|
||||
@@ -606,8 +606,8 @@ class ChatEditingSession extends Disposable implements IChatEditingSession {
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
|
||||
private async _acceptTextEdits(resource: URI, textEdits: TextEdit[]): Promise<void> {
|
||||
const entry = await this._getOrCreateModifiedFileEntry(resource);
|
||||
private async _acceptTextEdits(resource: URI, textEdits: TextEdit[], responseModel: IChatResponseModel): Promise<void> {
|
||||
const entry = await this._getOrCreateModifiedFileEntry(resource, responseModel);
|
||||
entry.applyEdits(textEdits);
|
||||
await this._editorService.openEditor({ resource: entry.modifiedURI, options: { inactive: true } });
|
||||
}
|
||||
@@ -617,13 +617,13 @@ class ChatEditingSession extends Disposable implements IChatEditingSession {
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
|
||||
private async _getOrCreateModifiedFileEntry(resource: URI): Promise<ModifiedFileEntry> {
|
||||
private async _getOrCreateModifiedFileEntry(resource: URI, responseModel: IChatResponseModel): Promise<ModifiedFileEntry> {
|
||||
const existingEntry = this._entries.find(e => e.resource.toString() === resource.toString());
|
||||
if (existingEntry) {
|
||||
return existingEntry;
|
||||
}
|
||||
|
||||
const entry = await this._createModifiedFileEntry(resource);
|
||||
const entry = await this._createModifiedFileEntry(resource, responseModel);
|
||||
this._register(entry);
|
||||
this._entries = [...this._entries, entry];
|
||||
this._entriesObs.set(this._entries, undefined);
|
||||
@@ -632,17 +632,17 @@ class ChatEditingSession extends Disposable implements IChatEditingSession {
|
||||
return entry;
|
||||
}
|
||||
|
||||
private async _createModifiedFileEntry(resource: URI, mustExist = false): Promise<ModifiedFileEntry> {
|
||||
private async _createModifiedFileEntry(resource: URI, responseModel: IChatResponseModel, mustExist = false): Promise<ModifiedFileEntry> {
|
||||
try {
|
||||
const ref = await this._textModelService.createModelReference(resource);
|
||||
return this._instantiationService.createInstance(ModifiedFileEntry, resource, ref, { collapse: (transaction: ITransaction | undefined) => this._collapse(resource, transaction) });
|
||||
return this._instantiationService.createInstance(ModifiedFileEntry, resource, ref, { collapse: (transaction: ITransaction | undefined) => this._collapse(resource, transaction) }, responseModel);
|
||||
} catch (err) {
|
||||
if (mustExist) {
|
||||
throw err;
|
||||
}
|
||||
// this file does not exist yet, create it and try again
|
||||
await this._bulkEditService.apply({ edits: [{ newResource: resource }] });
|
||||
return this._createModifiedFileEntry(resource, true);
|
||||
return this._createModifiedFileEntry(resource, responseModel, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -684,10 +684,12 @@ class ModifiedFileEntry extends Disposable implements IModifiedFileEntry {
|
||||
public readonly resource: URI,
|
||||
resourceRef: IReference<IResolvedTextEditorModel>,
|
||||
private readonly _multiDiffEntryDelegate: { collapse: (transaction: ITransaction | undefined) => void },
|
||||
private readonly _responseModel: IChatResponseModel,
|
||||
@IModelService modelService: IModelService,
|
||||
@ITextModelService textModelService: ITextModelService,
|
||||
@ILanguageService languageService: ILanguageService,
|
||||
@IBulkEditService public readonly _bulkEditService: IBulkEditService,
|
||||
@IBulkEditService public readonly bulkEditService: IBulkEditService,
|
||||
@IChatService private readonly _chatService: IChatService,
|
||||
) {
|
||||
super();
|
||||
this.doc = resourceRef.object.textEditorModel;
|
||||
@@ -722,6 +724,7 @@ class ModifiedFileEntry extends Disposable implements IModifiedFileEntry {
|
||||
this.docSnapshot.setValue(this.doc.createSnapshot());
|
||||
this._stateObs.set(WorkingSetEntryState.Accepted, transaction);
|
||||
await this.collapse(transaction);
|
||||
this._notifyAction('accepted');
|
||||
}
|
||||
|
||||
async reject(transaction: ITransaction | undefined): Promise<void> {
|
||||
@@ -732,9 +735,21 @@ class ModifiedFileEntry extends Disposable implements IModifiedFileEntry {
|
||||
this.doc.setValue(this.docSnapshot.createSnapshot());
|
||||
this._stateObs.set(WorkingSetEntryState.Rejected, transaction);
|
||||
await this.collapse(transaction);
|
||||
this._notifyAction('rejected');
|
||||
}
|
||||
|
||||
async collapse(transaction: ITransaction | undefined): Promise<void> {
|
||||
this._multiDiffEntryDelegate.collapse(transaction);
|
||||
}
|
||||
|
||||
private _notifyAction(outcome: 'accepted' | 'rejected') {
|
||||
this._chatService.notifyUserAction({
|
||||
action: { kind: 'chatEditingSessionAction', uri: this.resource, hasRemainingEdits: false, outcome },
|
||||
agentId: this._responseModel.agent?.id,
|
||||
command: this._responseModel.slashCommand?.name,
|
||||
sessionId: this._responseModel.session.sessionId,
|
||||
requestId: this._responseModel.requestId,
|
||||
result: this._responseModel.result
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ export interface IModifiedFileEntry {
|
||||
}
|
||||
|
||||
export interface IChatEditingSessionStream {
|
||||
textEdits(resource: URI, textEdits: TextEdit[]): void;
|
||||
textEdits(resource: URI, textEdits: TextEdit[], responseModel: IChatResponseModel): void;
|
||||
}
|
||||
|
||||
export const enum ChatEditingSessionState {
|
||||
|
||||
@@ -326,7 +326,15 @@ export interface IChatInlineChatCodeAction {
|
||||
action: 'accepted' | 'discarded';
|
||||
}
|
||||
|
||||
export type ChatUserAction = IChatVoteAction | IChatCopyAction | IChatInsertAction | IChatApplyAction | IChatTerminalAction | IChatCommandAction | IChatFollowupAction | IChatBugReportAction | IChatInlineChatCodeAction;
|
||||
|
||||
export interface IChatEditingSessionAction {
|
||||
kind: 'chatEditingSessionAction';
|
||||
uri: URI;
|
||||
hasRemainingEdits: boolean;
|
||||
outcome: 'accepted' | 'rejected';
|
||||
}
|
||||
|
||||
export type ChatUserAction = IChatVoteAction | IChatCopyAction | IChatInsertAction | IChatApplyAction | IChatTerminalAction | IChatCommandAction | IChatFollowupAction | IChatBugReportAction | IChatInlineChatCodeAction | IChatEditingSessionAction;
|
||||
|
||||
export interface IChatUserActionEvent {
|
||||
action: ChatUserAction;
|
||||
|
||||
@@ -358,9 +358,22 @@ declare module 'vscode' {
|
||||
accepted: boolean;
|
||||
}
|
||||
|
||||
export interface ChatEditingSessionAction {
|
||||
// eslint-disable-next-line local/vscode-dts-string-type-literals
|
||||
kind: 'chatEditingSessionAction';
|
||||
uri: Uri;
|
||||
hasRemainingEdits: boolean;
|
||||
outcome: ChatEditingSessionActionOutcome;
|
||||
}
|
||||
|
||||
export enum ChatEditingSessionActionOutcome {
|
||||
Accepted = 1,
|
||||
Rejected = 2
|
||||
}
|
||||
|
||||
export interface ChatUserActionEvent {
|
||||
readonly result: ChatResult;
|
||||
readonly action: ChatCopyAction | ChatInsertAction | ChatApplyAction | ChatTerminalAction | ChatCommandAction | ChatFollowupAction | ChatBugReportAction | ChatEditorAction;
|
||||
readonly action: ChatCopyAction | ChatInsertAction | ChatApplyAction | ChatTerminalAction | ChatCommandAction | ChatFollowupAction | ChatBugReportAction | ChatEditorAction | ChatEditingSessionAction;
|
||||
}
|
||||
|
||||
export interface ChatPromptReference {
|
||||
|
||||
Reference in New Issue
Block a user