Path: blob/main/src/vs/workbench/contrib/chat/common/chatEditingService.ts
3296 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 { CancellationToken } from '../../../../base/common/cancellation.js';6import { Event } from '../../../../base/common/event.js';7import { IDisposable } from '../../../../base/common/lifecycle.js';8import { IObservable, IReader } from '../../../../base/common/observable.js';9import { URI } from '../../../../base/common/uri.js';10import { TextEdit } from '../../../../editor/common/languages.js';11import { ITextModel } from '../../../../editor/common/model.js';12import { localize } from '../../../../nls.js';13import { RawContextKey } from '../../../../platform/contextkey/common/contextkey.js';14import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';15import { IEditorPane } from '../../../common/editor.js';16import { EditSuggestionId } from '../../../../editor/common/textModelEditSource.js';17import { ICellEditOperation } from '../../notebook/common/notebookCommon.js';18import { IChatAgentResult } from './chatAgents.js';19import { ChatModel, IChatResponseModel } from './chatModel.js';2021export const IChatEditingService = createDecorator<IChatEditingService>('chatEditingService');2223export interface IChatEditingService {2425_serviceBrand: undefined;2627startOrContinueGlobalEditingSession(chatModel: ChatModel): Promise<IChatEditingSession>;2829getEditingSession(chatSessionId: string): IChatEditingSession | undefined;3031/**32* All editing sessions, sorted by recency, e.g the last created session comes first.33*/34readonly editingSessionsObs: IObservable<readonly IChatEditingSession[]>;3536/**37* Creates a new short lived editing session38*/39createEditingSession(chatModel: ChatModel): Promise<IChatEditingSession>;4041//#region related files4243hasRelatedFilesProviders(): boolean;44registerRelatedFilesProvider(handle: number, provider: IChatRelatedFilesProvider): IDisposable;45getRelatedFiles(chatSessionId: string, prompt: string, files: URI[], token: CancellationToken): Promise<{ group: string; files: IChatRelatedFile[] }[] | undefined>;4647//#endregion48}4950export interface IChatRequestDraft {51readonly prompt: string;52readonly files: readonly URI[];53}5455export interface IChatRelatedFileProviderMetadata {56readonly description: string;57}5859export interface IChatRelatedFile {60readonly uri: URI;61readonly description: string;62}6364export interface IChatRelatedFilesProvider {65readonly description: string;66provideRelatedFiles(chatRequest: IChatRequestDraft, token: CancellationToken): Promise<IChatRelatedFile[] | undefined>;67}6869export interface WorkingSetDisplayMetadata {70state: ModifiedFileEntryState;71description?: string;72}7374export interface IStreamingEdits {75pushText(edits: TextEdit[], isLastEdits: boolean): void;76pushNotebookCellText(cell: URI, edits: TextEdit[], isLastEdits: boolean): void;77pushNotebook(edits: ICellEditOperation[], isLastEdits: boolean): void;78/** Marks edits as done, idempotent */79complete(): void;80}8182export interface IModifiedEntryTelemetryInfo {83readonly agentId: string | undefined;84readonly command: string | undefined;85readonly sessionId: string;86readonly requestId: string;87readonly result: IChatAgentResult | undefined;88readonly modelId: string | undefined;89readonly modeId: 'ask' | 'edit' | 'agent' | 'custom' | 'applyCodeBlock' | undefined;90readonly applyCodeBlockSuggestionId: EditSuggestionId | undefined;91readonly feature: 'sideBarChat' | 'inlineChat' | string | undefined;92}9394export interface ISnapshotEntry {95readonly resource: URI;96readonly languageId: string;97readonly snapshotUri: URI;98readonly original: string;99readonly current: string;100readonly state: ModifiedFileEntryState;101telemetryInfo: IModifiedEntryTelemetryInfo;102}103104export interface IChatEditingSession extends IDisposable {105readonly isGlobalEditingSession: boolean;106readonly chatSessionId: string;107readonly onDidDispose: Event<void>;108readonly state: IObservable<ChatEditingSessionState>;109readonly entries: IObservable<readonly IModifiedFileEntry[]>;110show(previousChanges?: boolean): Promise<void>;111accept(...uris: URI[]): Promise<void>;112reject(...uris: URI[]): Promise<void>;113getEntry(uri: URI): IModifiedFileEntry | undefined;114readEntry(uri: URI, reader?: IReader): IModifiedFileEntry | undefined;115116restoreSnapshot(requestId: string, stopId: string | undefined): Promise<void>;117118/**119* Gets the snapshot URI of a file at the request and _after_ changes made in the undo stop.120* @param uri File in the workspace121*/122getSnapshotUri(requestId: string, uri: URI, stopId: string | undefined): URI | undefined;123124getSnapshotModel(requestId: string, undoStop: string | undefined, snapshotUri: URI): Promise<ITextModel | null>;125126getSnapshot(requestId: string, undoStop: string | undefined, snapshotUri: URI): ISnapshotEntry | undefined;127128/**129* Will lead to this object getting disposed130*/131stop(clearState?: boolean): Promise<void>;132133/**134* Starts making edits to the resource.135* @param resource URI that's being edited136* @param responseModel The response model making the edits137* @param inUndoStop The undo stop the edits will be grouped in138*/139startStreamingEdits(resource: URI, responseModel: IChatResponseModel, inUndoStop: string | undefined): IStreamingEdits;140141/**142* Gets the document diff of a change made to a URI between one undo stop and143* the next one.144* @returns The observable or undefined if there is no diff between the stops.145*/146getEntryDiffBetweenStops(uri: URI, requestId: string | undefined, stopId: string | undefined): IObservable<IEditSessionEntryDiff | undefined> | undefined;147148/**149* Gets the document diff of a change made to a URI between one request to another one.150* @returns The observable or undefined if there is no diff between the requests.151*/152getEntryDiffBetweenRequests(uri: URI, startRequestIs: string, stopRequestId: string): IObservable<IEditSessionEntryDiff | undefined>;153154readonly canUndo: IObservable<boolean>;155readonly canRedo: IObservable<boolean>;156undoInteraction(): Promise<void>;157redoInteraction(): Promise<void>;158}159160export interface IEditSessionEntryDiff {161/** LHS and RHS of a diff editor, if opened: */162originalURI: URI;163modifiedURI: URI;164165/** Diff state information: */166quitEarly: boolean;167identical: boolean;168169/** Added data (e.g. line numbers) to show in the UI */170added: number;171/** Removed data (e.g. line numbers) to show in the UI */172removed: number;173}174175export const enum ModifiedFileEntryState {176Modified,177Accepted,178Rejected,179}180181/**182* Represents a part of a change183*/184export interface IModifiedFileEntryChangeHunk {185accept(): Promise<boolean>;186reject(): Promise<boolean>;187}188189export interface IModifiedFileEntryEditorIntegration extends IDisposable {190191/**192* The index of a change193*/194currentIndex: IObservable<number>;195196/**197* Reveal the first (`true`) or last (`false`) change198*/199reveal(firstOrLast: boolean, preserveFocus?: boolean): void;200201/**202* Go to next change and increate `currentIndex`203* @param wrap When at the last, start over again or not204* @returns If it went next205*/206next(wrap: boolean): boolean;207208/**209* @see `next`210*/211previous(wrap: boolean): boolean;212213/**214* Enable the accessible diff viewer for this editor215*/216enableAccessibleDiffView(): void;217218/**219* Accept the change given or the nearest220* @param change An opaque change object221*/222acceptNearestChange(change?: IModifiedFileEntryChangeHunk): Promise<void>;223224/**225* @see `acceptNearestChange`226*/227rejectNearestChange(change?: IModifiedFileEntryChangeHunk): Promise<void>;228229/**230* Toggle between diff-editor and normal editor231* @param change An opaque change object232* @param show Optional boolean to control if the diff should show233*/234toggleDiff(change: IModifiedFileEntryChangeHunk | undefined, show?: boolean): Promise<void>;235}236237export interface IModifiedFileEntry {238readonly entryId: string;239readonly originalURI: URI;240readonly modifiedURI: URI;241242readonly lastModifyingRequestId: string;243244readonly state: IObservable<ModifiedFileEntryState>;245readonly isCurrentlyBeingModifiedBy: IObservable<IChatResponseModel | undefined>;246readonly lastModifyingResponse: IObservable<IChatResponseModel | undefined>;247readonly rewriteRatio: IObservable<number>;248249readonly waitsForLastEdits: IObservable<boolean>;250251accept(): Promise<void>;252reject(): Promise<void>;253254reviewMode: IObservable<boolean>;255autoAcceptController: IObservable<{ total: number; remaining: number; cancel(): void } | undefined>;256enableReviewModeUntilSettled(): void;257258/**259* Number of changes for this file260*/261readonly changesCount: IObservable<number>;262263/**264* Number of lines added in this entry.265*/266readonly linesAdded?: IObservable<number>;267268/**269* Number of lines removed in this entry270*/271readonly linesRemoved?: IObservable<number>;272273getEditorIntegration(editor: IEditorPane): IModifiedFileEntryEditorIntegration;274}275276export interface IChatEditingSessionStream {277textEdits(resource: URI, textEdits: TextEdit[], isLastEdits: boolean, responseModel: IChatResponseModel): void;278notebookEdits(resource: URI, edits: ICellEditOperation[], isLastEdits: boolean, responseModel: IChatResponseModel): void;279}280281export const enum ChatEditingSessionState {282Initial = 0,283StreamingEdits = 1,284Idle = 2,285Disposed = 3286}287288export const CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME = 'chat-editing-multi-diff-source';289290export const chatEditingWidgetFileStateContextKey = new RawContextKey<ModifiedFileEntryState>('chatEditingWidgetFileState', undefined, localize('chatEditingWidgetFileState', "The current state of the file in the chat editing widget"));291export const chatEditingAgentSupportsReadonlyReferencesContextKey = new RawContextKey<boolean>('chatEditingAgentSupportsReadonlyReferences', undefined, localize('chatEditingAgentSupportsReadonlyReferences', "Whether the chat editing agent supports readonly references (temporary)"));292export const decidedChatEditingResourceContextKey = new RawContextKey<string[]>('decidedChatEditingResource', []);293export const chatEditingResourceContextKey = new RawContextKey<string | undefined>('chatEditingResource', undefined);294export const inChatEditingSessionContextKey = new RawContextKey<boolean | undefined>('inChatEditingSession', undefined);295export const hasUndecidedChatEditingResourceContextKey = new RawContextKey<boolean | undefined>('hasUndecidedChatEditingResource', false);296export const hasAppliedChatEditsContextKey = new RawContextKey<boolean | undefined>('hasAppliedChatEdits', false);297export const applyingChatEditsFailedContextKey = new RawContextKey<boolean | undefined>('applyingChatEditsFailed', false);298299export const chatEditingMaxFileAssignmentName = 'chatEditingSessionFileLimit';300export const defaultChatEditingMaxFileLimit = 10;301302export const enum ChatEditKind {303Created,304Modified,305}306307export interface IChatEditingActionContext {308// The chat session ID that this editing session is associated with309sessionId: string;310}311312export function isChatEditingActionContext(thing: unknown): thing is IChatEditingActionContext {313return typeof thing === 'object' && !!thing && 'sessionId' in thing;314}315316export function getMultiDiffSourceUri(session: IChatEditingSession, showPreviousChanges?: boolean): URI {317return URI.from({318scheme: CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME,319authority: session.chatSessionId,320query: showPreviousChanges ? 'previous' : undefined,321});322}323324export function parseChatMultiDiffUri(uri: URI): { chatSessionId: string; showPreviousChanges: boolean } {325const chatSessionId = uri.authority;326const showPreviousChanges = uri.query === 'previous';327328return { chatSessionId, showPreviousChanges };329}330331332