chatEditingService.ts ×7

Frontier kind: Code frontier

unlabeled · c_3c654277f9e2

261 tests · 22388 LOC · 119 files · introduces 0 tests · 413 LOC · 1 file

Introduces — evidence that enters the hierarchy at this concept

Code
7 ranges413 lines · 1 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
2462 ranges22388 lines · 119 files · Browse complete extent
All tests (intent)
261 testsBrowse complete intent

Neighbourhood graph

The orange circle is the focus. Violet and green circles are every ancestor and descendant, broader and narrower, at any distance; blue squares and pink diamonds are the introduced files and exact introduced tests of every visible concept, not only the focus's. Arrows point from broader to narrower concepts and bridge only concepts omitted from this view. Undirected links show source or test introduction. Concept and file size follows LOC; exact test nodes use test-count units.

Introduced files, introduced tests, and structurally relevant concept specialization

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the native relationship evidence on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the native relationship evidence on this page while the interactive map is unavailable.

Native relationship evidence

Every exact file and test below is linked only from the concept that introduces it.

Introduced tests

Every collected test enters the hierarchy at exactly one concept.

No tests are introduced at this concept. Its intent tests are introduced by other concepts.

Introduced code

Every collected source range enters the hierarchy at exactly one concept.

1 file ranked by introduced lines: 413 introduced LOC across 7 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/workbench/contrib/chat/common/editing/chatEditingService.ts 413 introduced LOC · 7 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- chatEditingService.ts
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 { decodeHex, encodeHex, VSBuffer } from '../../../../../base/common/buffer.js';
7 > import { CancellationToken } from '../../../../../base/common/cancellation.js';
8 > import { CancellationError } from '../../../../../base/common/errors.js';
9 > import { Event } from '../../../../../base/common/event.js';
10 > import { IDisposable } from '../../../../../base/common/lifecycle.js';
11 > import { autorunSelfDisposable, IObservable, IReader } from '../../../../../base/common/observable.js';
12 > import { hasKey } from '../../../../../base/common/types.js';
13 > import { URI } from '../../../../../base/common/uri.js';
14 > import { IDocumentDiff } from '../../../../../editor/common/diff/documentDiffProvider.js';
15 > import { TextEdit } from '../../../../../editor/common/languages.js';
16 > import { ITextModel } from '../../../../../editor/common/model.js';
17 > import { EditSuggestionId } from '../../../../../editor/common/textModelEditSource.js';
18 > import { localize } from '../../../../../nls.js';
19 > import { RawContextKey } from '../../../../../platform/contextkey/common/contextkey.js';
20 > import { createDecorator } from '../../../../../platform/instantiation/common/instantiation.js';
21 > import { IEditorPane } from '../../../../common/editor.js';
22 > import { ICellEditOperation } from '../../../notebook/common/notebookCommon.js';
23 > import { IChatMultiDiffData, IChatMultiDiffDataSerialized, IChatProgress, IChatWorkspaceEdit } from '../chatService/chatService.js';
24 > import { ChatModel, IChatRequestDisablement, IChatResponseModel } from '../model/chatModel.js';
25 > import { IChatAgentResult } from '../participants/chatAgents.js';
26 >
27 > export const IChatEditingService = createDecorator<IChatEditingService>('chatEditingService');
28 >
29 > export interface IChatEditingSessionProvider {
30 > createEditingSession(chatSessionResource: URI): IChatEditingSession;
31 > }
32 >
33 > export interface IChatEditingService {
34 >
35 > _serviceBrand: undefined;
36 >
37 > startOrContinueGlobalEditingSession(chatModel: ChatModel): IChatEditingSession;
38 >
39 > getEditingSession(chatSessionResource: URI): IChatEditingSession | undefined;
40 >
41 > /**
42 > * All editing sessions, sorted by recency, e.g the last created session comes first.
43 > */
44 > readonly editingSessionsObs: IObservable<readonly IChatEditingSession[]>;
45 >
46 > /**
47 > * Creates a new short lived editing session
48 > */
49 > createEditingSession(chatModel: ChatModel): IChatEditingSession;
50 >
51 > /**
52 > * Creates an editing session with state transferred from the provided session.
53 > */
54 > transferEditingSession(chatModel: ChatModel, session: IChatEditingSession): IChatEditingSession;
55 >
56 > /**
57 > * Registers a provider that creates editing sessions for chat sessions
58 > * with the given URI scheme. When {@link createEditingSession} is called
59 > * for a chat model whose sessionResource matches the scheme, the provider
60 > * is used instead of the default implementation.
61 > */
62 > registerEditingSessionProvider(scheme: string, provider: IChatEditingSessionProvider): IDisposable;
63 > }
64 >
65 > export interface WorkingSetDisplayMetadata {
66 > state: ModifiedFileEntryState;
67 > description?: string;
68 > }
69 >
70 > export interface IStreamingEdits {
71 > pushText(edits: TextEdit[], isLastEdits: boolean): void;
72 > pushNotebookCellText(cell: URI, edits: TextEdit[], isLastEdits: boolean): void;
73 > pushNotebook(edits: ICellEditOperation[], isLastEdits: boolean): void;
74 > /** Marks edits as done, idempotent */
75 > complete(): void;
76 > }
77 >
78 > export interface IModifiedEntryTelemetryInfo {
79 > readonly agentId: string | undefined;
80 > readonly command: string | undefined;
81 > readonly sessionResource: URI;
82 > readonly requestId: string;
83 > readonly result: IChatAgentResult | undefined;
84 > readonly modelId: string | undefined;
85 > readonly modeId: 'ask' | 'edit' | 'agent' | 'custom' | 'applyCodeBlock' | undefined;
86 > readonly applyCodeBlockSuggestionId: EditSuggestionId | undefined;
87 > readonly feature: 'sideBarChat' | 'inlineChat' | undefined;
88 > }
89 >
90 > export interface ISnapshotEntry {
91 > readonly resource: URI;
92 > readonly languageId: string;
93 > readonly snapshotUri: URI;
94 > readonly original: string;
95 > readonly current: string;
96 > readonly state: ModifiedFileEntryState;
97 > telemetryInfo: IModifiedEntryTelemetryInfo;
98 > /** True if this entry represents a deleted file */
99 > readonly isDeleted?: boolean;
100 > }
101 >
102 > export interface IChatEditingSession extends IDisposable {
103 > readonly isGlobalEditingSession: boolean;
104 > readonly supportsKeepUndo: boolean;
105 > readonly chatSessionResource: URI;
106 > readonly onDidDispose: Event<void>;
107 > readonly state: IObservable<ChatEditingSessionState>;
108 > readonly entries: IObservable<readonly IModifiedFileEntry[]>;
109 > /** Requests disabled by undo/redo in the session */
110 > readonly requestDisablement: IObservable<IChatRequestDisablement[]>;
111 >
112 > show(previousChanges?: boolean): Promise<void>;
113 > accept(...uris: URI[]): Promise<void>;
114 > reject(...uris: URI[]): Promise<void>;
115 > getEntry(uri: URI): IModifiedFileEntry | undefined;
116 > readEntry(uri: URI, reader: IReader): IModifiedFileEntry | undefined;
117 >
118 > restoreSnapshot(requestId: string, stopId: string | undefined): Promise<void>;
119 >
120 > /**
121 > * Marks all edits to the given resources as agent edits until
122 > * {@link stopExternalEdits} is called with the same ID. This is used for
123 > * agents that make changes on-disk rather than streaming edits through the
124 > * chat session.
125 > */
126 > startExternalEdits(responseModel: IChatResponseModel, operationId: number, resources: URI[], undoStopId: string, contentFor?: URI[]): Promise<IChatProgress[]>;
127 > stopExternalEdits(responseModel: IChatResponseModel, operationId: number, contentFor?: URI[]): Promise<IChatProgress[]>;
128 >
129 > /**
130 > * Gets the snapshot URI of a file at the request and _after_ changes made in the undo stop.
131 > * @param uri File in the workspace
132 > */
133 > getSnapshotUri(requestId: string, uri: URI, stopId: string | undefined): URI | undefined;
134 >
135 > getSnapshotContents(requestId: string, uri: URI, stopId: string | undefined): Promise<VSBuffer | undefined>;
136 > getSnapshotModel(requestId: string, undoStop: string | undefined, snapshotUri: URI): Promise<ITextModel | null>;
137 >
138 > /**
139 > * Will lead to this object getting disposed
140 > */
141 > stop(clearState?: boolean): Promise<void>;
142 >
143 > /**
144 > * Starts making edits to the resource.
145 > * @param resource URI that's being edited
146 > * @param responseModel The response model making the edits
147 > * @param inUndoStop The undo stop the edits will be grouped in
148 > */
149 > startStreamingEdits(resource: URI, responseModel: IChatResponseModel, inUndoStop: string | undefined): IStreamingEdits;
150 >
151 > /**
152 > * Applies a workspace edit (file deletions, creations, renames).
153 > * @param edit The workspace edit containing file operations
154 > * @param responseModel The response model making the edit
155 > * @param undoStopId The undo stop ID for this edit
156 > */
157 > applyWorkspaceEdit(edit: IChatWorkspaceEdit, responseModel: IChatResponseModel, undoStopId: string): void;
158 >
159 > /**
160 > * Gets the document diff of a change made to a URI between one undo stop and
161 > * the next one.
162 > * @returns The observable or undefined if there is no diff between the stops.
163 > */
164 > getEntryDiffBetweenStops(uri: URI, requestId: string | undefined, stopId: string | undefined): IObservable<IEditSessionEntryDiff | undefined> | undefined;
165 >
166 > /**
167 > * Gets the document diff of a change made to a URI between one request to another one.
168 > * @returns The observable or undefined if there is no diff between the requests.
169 > */
170 > getEntryDiffBetweenRequests(uri: URI, startRequestIs: string, stopRequestId: string): IObservable<IEditSessionEntryDiff | undefined>;
171 >
172 > /**
173 > * Gets the diff of each file modified in this session, comparing the initial
174 > * baseline to the current state.
175 > */
176 > getDiffsForFilesInSession(): IObservable<readonly IEditSessionEntryDiff[]>;
177 >
178 > /**
179 > * Gets the diff of each file modified in the request.
180 > */
181 > getDiffsForFilesInRequest(requestId: string): IObservable<readonly IEditSessionEntryDiff[]>;
182 >
183 > /**
184 > * Whether there are any edits made in the given request.
185 > */
186 > hasEditsInRequest(requestId: string, reader?: IReader): boolean;
187 >
188 > /**
189 > * Gets the aggregated diff stats for all files modified in this session.
190 > */
191 > getDiffForSession(): IObservable<IEditSessionDiffStats>;
192 >
193 > readonly canUndo: IObservable<boolean>;
194 > readonly canRedo: IObservable<boolean>;
195 > undoInteraction(): Promise<void>;
196 > redoInteraction(): Promise<void>;
197 >
198 > /**
199 > * Triggers generation of explanations for all modified files in the session.
200 > */
201 > triggerExplanationGeneration(): Promise<void>;
202 >
203 > /**
204 > * Clears any active explanation generation.
205 > */
206 > clearExplanations(): void;
207 >
208 > /**
209 > * Whether explanations are currently being generated or displayed.
210 > */
211 > hasExplanations(): boolean;
212 > }
213 >
214 > export function chatEditingSessionIsReady(session: IChatEditingSession): Promise<void> {
215 return new Promise<void>(resolve => {
216 autorunSelfDisposable(reader => {
223 });
224 }
226 > export function editEntriesToMultiDiffData(entriesObs: IObservable<readonly IEditSessionEntryDiff[]>): IChatMultiDiffData {
227 const multiDiffData = entriesObs.map(entries => ({
228 title: localize('chatMultidiff.autoGenerated', 'Changes to {0} files', entries.length),
249 };
250 }
252 > export function awaitCompleteChatEditingDiff(diff: IObservable<IEditSessionEntryDiff>, token?: CancellationToken): Promise<IEditSessionEntryDiff>;
253 > export function awaitCompleteChatEditingDiff(diff: IObservable<readonly IEditSessionEntryDiff[]>, token?: CancellationToken): Promise<readonly IEditSessionEntryDiff[]>;
254 > export function awaitCompleteChatEditingDiff(diff: IObservable<readonly IEditSessionEntryDiff[] | IEditSessionEntryDiff>, token?: CancellationToken): Promise<readonly IEditSessionEntryDiff[] | IEditSessionEntryDiff> {
255 return new Promise<readonly IEditSessionEntryDiff[] | IEditSessionEntryDiff>((resolve, reject) => {
256 autorunSelfDisposable(reader => {
279 });
280 }
282 > export interface IEditSessionDiffStats {
283 > /** Added data (e.g. line numbers) to show in the UI */
284 > added: number;
285 > /** Removed data (e.g. line numbers) to show in the UI */
286 > removed: number;
287 > }
288 >
289 > export interface IEditSessionEntryDiff extends IEditSessionDiffStats {
290 > /** LHS and RHS of a diff editor, if opened: */
291 > originalURI: URI;
292 > modifiedURI: URI;
293 >
294 > /**
295 > * Optional frozen "after" content for the RHS. When set, this is the exact
296 > * modified-side snapshot the diff represents (e.g. an agent-host per-turn
297 > * checkpoint), as opposed to {@link modifiedURI} which may be the live
298 > * working file and therefore include later changes. Consumers that want the
299 > * changeset's own diff should prefer this when present; {@link modifiedURI}
300 > * remains the file's identity for labels and go-to-file.
301 > *
302 > * Note: distinct from the agent-host checkpoint-ref readability fix (#323932).
303 > * That made the frozen snapshot blobs *readable*; this field carries *which*
304 > * snapshot to diff against so a per-turn review shows only that turn's changes.
305 > */
306 > modifiedSnapshotURI?: URI;
307 >
308 > /** Diff state information: */
309 > quitEarly: boolean;
310 > identical: boolean;
311 >
312 > /** True if nothing else will be added to this diff. */
313 > isFinal: boolean;
314 >
315 > /** True if the diff is currently being computed or updated. */
316 > isBusy: boolean;
317 > }
318 >
319 > export function emptySessionEntryDiff(originalURI: URI, modifiedURI: URI): IEditSessionEntryDiff {
320 return {
321 originalURI,
329 };
330 }
332 > export const enum ModifiedFileEntryState {
333 > Modified,
334 > Accepted,
335 > Rejected,
336 > }
337 >
338 > /**
339 > * Represents a part of a change
340 > */
341 > export interface IModifiedFileEntryChangeHunk {
342 > accept(): Promise<boolean>;
343 > reject(): Promise<boolean>;
344 > }
345 >
346 > export interface IModifiedFileEntryEditorIntegration extends IDisposable {
347 >
348 > /**
349 > * The index of a change
350 > */
351 > currentIndex: IObservable<number>;
352 >
353 > /**
354 > * Reveal the first (`true`) or last (`false`) change
355 > */
356 > reveal(firstOrLast: boolean, preserveFocus?: boolean): void;
357 >
358 > /**
359 > * Go to next change and increate `currentIndex`
360 > * @param wrap When at the last, start over again or not
361 > * @returns If it went next
362 > */
363 > next(wrap: boolean): boolean;
364 >
365 > /**
366 > * @see `next`
367 > */
368 > previous(wrap: boolean): boolean;
369 >
370 > /**
371 > * Enable the accessible diff viewer for this editor
372 > */
373 > enableAccessibleDiffView(): void;
374 >
375 > /**
376 > * Accept the change given or the nearest
377 > * @param change An opaque change object
378 > */
379 > acceptNearestChange(change?: IModifiedFileEntryChangeHunk): Promise<void>;
380 >
381 > /**
382 > * @see `acceptNearestChange`
383 > */
384 > rejectNearestChange(change?: IModifiedFileEntryChangeHunk): Promise<void>;
385 >
386 > /**
387 > * Toggle between diff-editor and normal editor
388 > * @param change An opaque change object
389 > * @param show Optional boolean to control if the diff should show
390 > */
391 > toggleDiff(change: IModifiedFileEntryChangeHunk | undefined, show?: boolean): Promise<void>;
392 > }
393 >
394 > export interface IModifiedFileEntry {
395 > readonly entryId: string;
396 > readonly originalURI: URI;
397 > readonly modifiedURI: URI;
398 > readonly isDeletion?: boolean;
399 >
400 > readonly lastModifyingRequestId: string;
401 >
402 > readonly state: IObservable<ModifiedFileEntryState>;
403 > readonly isCurrentlyBeingModifiedBy: IObservable<{ responseModel: IChatResponseModel; undoStopId: string | undefined } | undefined>;
404 > readonly lastModifyingResponse: IObservable<IChatResponseModel | undefined>;
405 > readonly rewriteRatio: IObservable<number>;
406 >
407 > readonly waitsForLastEdits: IObservable<boolean>;
408 >
409 > accept(): Promise<void>;
410 > reject(): Promise<void>;
411 >
412 > reviewMode: IObservable<boolean>;
413 > autoAcceptController: IObservable<{ total: number; remaining: number; cancel(): void } | undefined>;
414 > enableReviewModeUntilSettled(): void;
415 >
416 > /**
417 > * Number of changes for this file
418 > */
419 > readonly changesCount: IObservable<number>;
420 >
421 > /**
422 > * Diff information for this entry
423 > */
424 > readonly diffInfo?: IObservable<IDocumentDiff>;
425 >
426 > /**
427 > * Number of lines added in this entry.
428 > */
429 > readonly linesAdded?: IObservable<number>;
430 >
431 > /**
432 > * Number of lines removed in this entry
433 > */
434 > readonly linesRemoved?: IObservable<number>;
435 >
436 > getEditorIntegration(editor: IEditorPane): IModifiedFileEntryEditorIntegration;
437 > /**
438 > * Gets the document diff info, waiting for any ongoing promises to flush.
439 > */
440 > getDiffInfo?(): Promise<IDocumentDiff>;
441 > }
442 >
443 > export interface IChatEditingSessionStream {
444 > textEdits(resource: URI, textEdits: TextEdit[], isLastEdits: boolean, responseModel: IChatResponseModel): void;
445 > notebookEdits(resource: URI, edits: ICellEditOperation[], isLastEdits: boolean, responseModel: IChatResponseModel): void;
446 > }
447 >
448 > export const enum ChatEditingSessionState {
449 > Initial = 0,
450 > StreamingEdits = 1,
451 > Idle = 2,
452 > Disposed = 3
453 > }
454 >
455 > export const CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME = 'chat-editing-multi-diff-source';
456 >
457 > export const chatEditingWidgetFileStateContextKey = new RawContextKey<ModifiedFileEntryState>('chatEditingWidgetFileState', undefined, localize('chatEditingWidgetFileState', "The current state of the file in the chat editing widget"));
458 > export const chatEditingAgentSupportsReadonlyReferencesContextKey = new RawContextKey<boolean>('chatEditingAgentSupportsReadonlyReferences', undefined, localize('chatEditingAgentSupportsReadonlyReferences', "Whether the chat editing agent supports readonly references (temporary)"));
459 > export const decidedChatEditingResourceContextKey = new RawContextKey<string[]>('decidedChatEditingResource', []);
460 > export const chatEditingResourceContextKey = new RawContextKey<string | undefined>('chatEditingResource', undefined);
461 > export const inChatEditingSessionContextKey = new RawContextKey<boolean | undefined>('inChatEditingSession', undefined);
462 > export const hasUndecidedChatEditingResourceContextKey = new RawContextKey<boolean | undefined>('hasUndecidedChatEditingResource', false);
463 > export const hasAppliedChatEditsContextKey = new RawContextKey<boolean | undefined>('hasAppliedChatEdits', false);
464 > export const applyingChatEditsFailedContextKey = new RawContextKey<boolean | undefined>('applyingChatEditsFailed', false);
465 >
466 > export const chatEditingMaxFileAssignmentName = 'chatEditingSessionFileLimit';
467 > export const defaultChatEditingMaxFileLimit = 10;
468 >
469 > export const enum ChatEditKind {
470 > Created,
471 > Modified,
472 > Deleted,
473 > }
474 >
475 > export interface IChatEditingActionContext {
476 > // The chat session that this editing session is associated with
477 > sessionResource: URI;
478 > }
479 >
480 > export function isChatEditingActionContext(thing: unknown): thing is IChatEditingActionContext {
481 return typeof thing === 'object' && !!thing && hasKey(thing, { sessionResource: true });
482 }
484 > export function getMultiDiffSourceUri(session: IChatEditingSession, showPreviousChanges?: boolean): URI {
485 return URI.from({
486 scheme: CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME,
489 });
490 }
492 > export function parseChatMultiDiffUri(uri: URI): { chatSessionResource: URI; showPreviousChanges: boolean } {
493 const chatSessionResource = URI.parse(decodeHex(uri.authority).toString());
494 const showPreviousChanges = uri.query === 'previous';