Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/chat/common/chatEditingService.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
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 { CancellationToken } from '../../../../base/common/cancellation.js';
7
import { Event } from '../../../../base/common/event.js';
8
import { IDisposable } from '../../../../base/common/lifecycle.js';
9
import { IObservable, IReader } from '../../../../base/common/observable.js';
10
import { URI } from '../../../../base/common/uri.js';
11
import { TextEdit } from '../../../../editor/common/languages.js';
12
import { ITextModel } from '../../../../editor/common/model.js';
13
import { localize } from '../../../../nls.js';
14
import { RawContextKey } from '../../../../platform/contextkey/common/contextkey.js';
15
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
16
import { IEditorPane } from '../../../common/editor.js';
17
import { EditSuggestionId } from '../../../../editor/common/textModelEditSource.js';
18
import { ICellEditOperation } from '../../notebook/common/notebookCommon.js';
19
import { IChatAgentResult } from './chatAgents.js';
20
import { ChatModel, IChatResponseModel } from './chatModel.js';
21
22
export const IChatEditingService = createDecorator<IChatEditingService>('chatEditingService');
23
24
export interface IChatEditingService {
25
26
_serviceBrand: undefined;
27
28
startOrContinueGlobalEditingSession(chatModel: ChatModel): Promise<IChatEditingSession>;
29
30
getEditingSession(chatSessionId: string): IChatEditingSession | undefined;
31
32
/**
33
* All editing sessions, sorted by recency, e.g the last created session comes first.
34
*/
35
readonly editingSessionsObs: IObservable<readonly IChatEditingSession[]>;
36
37
/**
38
* Creates a new short lived editing session
39
*/
40
createEditingSession(chatModel: ChatModel): Promise<IChatEditingSession>;
41
42
//#region related files
43
44
hasRelatedFilesProviders(): boolean;
45
registerRelatedFilesProvider(handle: number, provider: IChatRelatedFilesProvider): IDisposable;
46
getRelatedFiles(chatSessionId: string, prompt: string, files: URI[], token: CancellationToken): Promise<{ group: string; files: IChatRelatedFile[] }[] | undefined>;
47
48
//#endregion
49
}
50
51
export interface IChatRequestDraft {
52
readonly prompt: string;
53
readonly files: readonly URI[];
54
}
55
56
export interface IChatRelatedFileProviderMetadata {
57
readonly description: string;
58
}
59
60
export interface IChatRelatedFile {
61
readonly uri: URI;
62
readonly description: string;
63
}
64
65
export interface IChatRelatedFilesProvider {
66
readonly description: string;
67
provideRelatedFiles(chatRequest: IChatRequestDraft, token: CancellationToken): Promise<IChatRelatedFile[] | undefined>;
68
}
69
70
export interface WorkingSetDisplayMetadata {
71
state: ModifiedFileEntryState;
72
description?: string;
73
}
74
75
export interface IStreamingEdits {
76
pushText(edits: TextEdit[], isLastEdits: boolean): void;
77
pushNotebookCellText(cell: URI, edits: TextEdit[], isLastEdits: boolean): void;
78
pushNotebook(edits: ICellEditOperation[], isLastEdits: boolean): void;
79
/** Marks edits as done, idempotent */
80
complete(): void;
81
}
82
83
export interface IModifiedEntryTelemetryInfo {
84
readonly agentId: string | undefined;
85
readonly command: string | undefined;
86
readonly sessionId: string;
87
readonly requestId: string;
88
readonly result: IChatAgentResult | undefined;
89
readonly modelId: string | undefined;
90
readonly modeId: 'ask' | 'edit' | 'agent' | 'custom' | 'applyCodeBlock' | undefined;
91
readonly applyCodeBlockSuggestionId: EditSuggestionId | undefined;
92
readonly feature: 'sideBarChat' | 'inlineChat' | string | undefined;
93
}
94
95
export interface ISnapshotEntry {
96
readonly resource: URI;
97
readonly languageId: string;
98
readonly snapshotUri: URI;
99
readonly original: string;
100
readonly current: string;
101
readonly state: ModifiedFileEntryState;
102
telemetryInfo: IModifiedEntryTelemetryInfo;
103
}
104
105
export interface IChatEditingSession extends IDisposable {
106
readonly isGlobalEditingSession: boolean;
107
readonly chatSessionId: string;
108
readonly onDidDispose: Event<void>;
109
readonly state: IObservable<ChatEditingSessionState>;
110
readonly entries: IObservable<readonly IModifiedFileEntry[]>;
111
show(previousChanges?: boolean): Promise<void>;
112
accept(...uris: URI[]): Promise<void>;
113
reject(...uris: URI[]): Promise<void>;
114
getEntry(uri: URI): IModifiedFileEntry | undefined;
115
readEntry(uri: URI, reader?: IReader): IModifiedFileEntry | undefined;
116
117
restoreSnapshot(requestId: string, stopId: string | undefined): Promise<void>;
118
119
/**
120
* Gets the snapshot URI of a file at the request and _after_ changes made in the undo stop.
121
* @param uri File in the workspace
122
*/
123
getSnapshotUri(requestId: string, uri: URI, stopId: string | undefined): URI | undefined;
124
125
getSnapshotModel(requestId: string, undoStop: string | undefined, snapshotUri: URI): Promise<ITextModel | null>;
126
127
getSnapshot(requestId: string, undoStop: string | undefined, snapshotUri: URI): ISnapshotEntry | undefined;
128
129
/**
130
* Will lead to this object getting disposed
131
*/
132
stop(clearState?: boolean): Promise<void>;
133
134
/**
135
* Starts making edits to the resource.
136
* @param resource URI that's being edited
137
* @param responseModel The response model making the edits
138
* @param inUndoStop The undo stop the edits will be grouped in
139
*/
140
startStreamingEdits(resource: URI, responseModel: IChatResponseModel, inUndoStop: string | undefined): IStreamingEdits;
141
142
/**
143
* Gets the document diff of a change made to a URI between one undo stop and
144
* the next one.
145
* @returns The observable or undefined if there is no diff between the stops.
146
*/
147
getEntryDiffBetweenStops(uri: URI, requestId: string | undefined, stopId: string | undefined): IObservable<IEditSessionEntryDiff | undefined> | undefined;
148
149
/**
150
* Gets the document diff of a change made to a URI between one request to another one.
151
* @returns The observable or undefined if there is no diff between the requests.
152
*/
153
getEntryDiffBetweenRequests(uri: URI, startRequestIs: string, stopRequestId: string): IObservable<IEditSessionEntryDiff | undefined>;
154
155
readonly canUndo: IObservable<boolean>;
156
readonly canRedo: IObservable<boolean>;
157
undoInteraction(): Promise<void>;
158
redoInteraction(): Promise<void>;
159
}
160
161
export interface IEditSessionEntryDiff {
162
/** LHS and RHS of a diff editor, if opened: */
163
originalURI: URI;
164
modifiedURI: URI;
165
166
/** Diff state information: */
167
quitEarly: boolean;
168
identical: boolean;
169
170
/** Added data (e.g. line numbers) to show in the UI */
171
added: number;
172
/** Removed data (e.g. line numbers) to show in the UI */
173
removed: number;
174
}
175
176
export const enum ModifiedFileEntryState {
177
Modified,
178
Accepted,
179
Rejected,
180
}
181
182
/**
183
* Represents a part of a change
184
*/
185
export interface IModifiedFileEntryChangeHunk {
186
accept(): Promise<boolean>;
187
reject(): Promise<boolean>;
188
}
189
190
export interface IModifiedFileEntryEditorIntegration extends IDisposable {
191
192
/**
193
* The index of a change
194
*/
195
currentIndex: IObservable<number>;
196
197
/**
198
* Reveal the first (`true`) or last (`false`) change
199
*/
200
reveal(firstOrLast: boolean, preserveFocus?: boolean): void;
201
202
/**
203
* Go to next change and increate `currentIndex`
204
* @param wrap When at the last, start over again or not
205
* @returns If it went next
206
*/
207
next(wrap: boolean): boolean;
208
209
/**
210
* @see `next`
211
*/
212
previous(wrap: boolean): boolean;
213
214
/**
215
* Enable the accessible diff viewer for this editor
216
*/
217
enableAccessibleDiffView(): void;
218
219
/**
220
* Accept the change given or the nearest
221
* @param change An opaque change object
222
*/
223
acceptNearestChange(change?: IModifiedFileEntryChangeHunk): Promise<void>;
224
225
/**
226
* @see `acceptNearestChange`
227
*/
228
rejectNearestChange(change?: IModifiedFileEntryChangeHunk): Promise<void>;
229
230
/**
231
* Toggle between diff-editor and normal editor
232
* @param change An opaque change object
233
* @param show Optional boolean to control if the diff should show
234
*/
235
toggleDiff(change: IModifiedFileEntryChangeHunk | undefined, show?: boolean): Promise<void>;
236
}
237
238
export interface IModifiedFileEntry {
239
readonly entryId: string;
240
readonly originalURI: URI;
241
readonly modifiedURI: URI;
242
243
readonly lastModifyingRequestId: string;
244
245
readonly state: IObservable<ModifiedFileEntryState>;
246
readonly isCurrentlyBeingModifiedBy: IObservable<IChatResponseModel | undefined>;
247
readonly lastModifyingResponse: IObservable<IChatResponseModel | undefined>;
248
readonly rewriteRatio: IObservable<number>;
249
250
readonly waitsForLastEdits: IObservable<boolean>;
251
252
accept(): Promise<void>;
253
reject(): Promise<void>;
254
255
reviewMode: IObservable<boolean>;
256
autoAcceptController: IObservable<{ total: number; remaining: number; cancel(): void } | undefined>;
257
enableReviewModeUntilSettled(): void;
258
259
/**
260
* Number of changes for this file
261
*/
262
readonly changesCount: IObservable<number>;
263
264
/**
265
* Number of lines added in this entry.
266
*/
267
readonly linesAdded?: IObservable<number>;
268
269
/**
270
* Number of lines removed in this entry
271
*/
272
readonly linesRemoved?: IObservable<number>;
273
274
getEditorIntegration(editor: IEditorPane): IModifiedFileEntryEditorIntegration;
275
}
276
277
export interface IChatEditingSessionStream {
278
textEdits(resource: URI, textEdits: TextEdit[], isLastEdits: boolean, responseModel: IChatResponseModel): void;
279
notebookEdits(resource: URI, edits: ICellEditOperation[], isLastEdits: boolean, responseModel: IChatResponseModel): void;
280
}
281
282
export const enum ChatEditingSessionState {
283
Initial = 0,
284
StreamingEdits = 1,
285
Idle = 2,
286
Disposed = 3
287
}
288
289
export const CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME = 'chat-editing-multi-diff-source';
290
291
export const chatEditingWidgetFileStateContextKey = new RawContextKey<ModifiedFileEntryState>('chatEditingWidgetFileState', undefined, localize('chatEditingWidgetFileState', "The current state of the file in the chat editing widget"));
292
export const chatEditingAgentSupportsReadonlyReferencesContextKey = new RawContextKey<boolean>('chatEditingAgentSupportsReadonlyReferences', undefined, localize('chatEditingAgentSupportsReadonlyReferences', "Whether the chat editing agent supports readonly references (temporary)"));
293
export const decidedChatEditingResourceContextKey = new RawContextKey<string[]>('decidedChatEditingResource', []);
294
export const chatEditingResourceContextKey = new RawContextKey<string | undefined>('chatEditingResource', undefined);
295
export const inChatEditingSessionContextKey = new RawContextKey<boolean | undefined>('inChatEditingSession', undefined);
296
export const hasUndecidedChatEditingResourceContextKey = new RawContextKey<boolean | undefined>('hasUndecidedChatEditingResource', false);
297
export const hasAppliedChatEditsContextKey = new RawContextKey<boolean | undefined>('hasAppliedChatEdits', false);
298
export const applyingChatEditsFailedContextKey = new RawContextKey<boolean | undefined>('applyingChatEditsFailed', false);
299
300
export const chatEditingMaxFileAssignmentName = 'chatEditingSessionFileLimit';
301
export const defaultChatEditingMaxFileLimit = 10;
302
303
export const enum ChatEditKind {
304
Created,
305
Modified,
306
}
307
308
export interface IChatEditingActionContext {
309
// The chat session ID that this editing session is associated with
310
sessionId: string;
311
}
312
313
export function isChatEditingActionContext(thing: unknown): thing is IChatEditingActionContext {
314
return typeof thing === 'object' && !!thing && 'sessionId' in thing;
315
}
316
317
export function getMultiDiffSourceUri(session: IChatEditingSession, showPreviousChanges?: boolean): URI {
318
return URI.from({
319
scheme: CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME,
320
authority: session.chatSessionId,
321
query: showPreviousChanges ? 'previous' : undefined,
322
});
323
}
324
325
export function parseChatMultiDiffUri(uri: URI): { chatSessionId: string; showPreviousChanges: boolean } {
326
const chatSessionId = uri.authority;
327
const showPreviousChanges = uri.query === 'previous';
328
329
return { chatSessionId, showPreviousChanges };
330
}
331
332