Skip to content

Commit

Permalink
fix: don't validate URIs for edits in response stream (microsoft#237430)
Browse files Browse the repository at this point in the history
  • Loading branch information
joyceerhl authored Jan 7, 2025
1 parent cbb7f99 commit 07f8b09
Showing 1 changed file with 0 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import { ITextModel } from '../../../../../editor/common/model.js';
import { IModelService } from '../../../../../editor/common/services/model.js';
import { ITextModelService } from '../../../../../editor/common/services/resolverService.js';
import { localize } from '../../../../../nls.js';
import { IFileDialogService } from '../../../../../platform/dialogs/common/dialogs.js';
import { EditorActivation } from '../../../../../platform/editor/common/editor.js';
import { IFileService } from '../../../../../platform/files/common/files.js';
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
Expand All @@ -29,12 +28,10 @@ import { IEditorGroupsService } from '../../../../services/editor/common/editorG
import { IEditorService } from '../../../../services/editor/common/editorService.js';
import { MultiDiffEditor } from '../../../multiDiffEditor/browser/multiDiffEditor.js';
import { MultiDiffEditorInput } from '../../../multiDiffEditor/browser/multiDiffEditorInput.js';
import { ChatAgentLocation, IChatAgentService } from '../../common/chatAgents.js';
import { ChatEditingSessionChangeType, ChatEditingSessionState, ChatEditKind, getMultiDiffSourceUri, IChatEditingSession, IModifiedFileEntry, WorkingSetDisplayMetadata, WorkingSetEntryRemovalReason, WorkingSetEntryState } from '../../common/chatEditingService.js';
import { IChatResponseModel } from '../../common/chatModel.js';
import { ChatEditingModifiedFileEntry, IModifiedEntryTelemetryInfo, ISnapshotEntry } from './chatEditingModifiedFileEntry.js';
import { ChatEditingTextModelContentProvider } from './chatEditingTextModelContentProviders.js';
import { Schemas } from '../../../../../base/common/network.js';
import { isEqual, joinPath } from '../../../../../base/common/resources.js';
import { StringSHA1 } from '../../../../../base/common/hash.js';
import { IEnvironmentService } from '../../../../../platform/environment/common/environment.js';
Expand Down Expand Up @@ -94,7 +91,6 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
* Contains the contents of a file when the AI first began doing edits to it.
*/
private readonly _initialFileContents = new ResourceMap<string>();
private readonly _filesToSkipCreating = new ResourceSet();

private readonly _entriesObs = observableValue<readonly ChatEditingModifiedFileEntry[]>(this, []);
public get entries(): IObservable<readonly ChatEditingModifiedFileEntry[]> {
Expand Down Expand Up @@ -174,10 +170,6 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
@IBulkEditService public readonly _bulkEditService: IBulkEditService,
@IEditorGroupsService private readonly _editorGroupsService: IEditorGroupsService,
@IEditorService private readonly _editorService: IEditorService,
@IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService,
@IFileService private readonly _fileService: IFileService,
@IFileDialogService private readonly _dialogService: IFileDialogService,
@IChatAgentService private readonly _chatAgentService: IChatAgentService,
@IChatService private readonly _chatService: IChatService,
@INotebookService private readonly _notebookService: INotebookService,
) {
Expand All @@ -187,9 +179,6 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
public async init(): Promise<void> {
const restoredSessionState = await this._instantiationService.createInstance(ChatEditingSessionStorage, this.chatSessionId).restoreState();
if (restoredSessionState) {
for (const uri of restoredSessionState.filesToSkipCreating) {
this._filesToSkipCreating.add(uri);
}
for (const [uri, content] of restoredSessionState.initialFileContents) {
this._initialFileContents.set(uri, content);
}
Expand Down Expand Up @@ -225,7 +214,6 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
public storeState(): Promise<void> {
const storage = this._instantiationService.createInstance(ChatEditingSessionStorage, this.chatSessionId);
const state: StoredSessionState = {
filesToSkipCreating: [...this._filesToSkipCreating],
initialFileContents: this._initialFileContents,
pendingSnapshot: this._pendingSnapshot,
recentSnapshot: this._createSnapshot(undefined),
Expand Down Expand Up @@ -625,26 +613,11 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
}

private async _acceptTextEdits(resource: URI, textEdits: TextEdit[], isLastEdits: boolean, responseModel: IChatResponseModel): Promise<void> {
if (this._filesToSkipCreating.has(resource)) {
return;
}

if (!this._entriesObs.get().find(e => isEqual(e.modifiedURI, resource)) && this._entriesObs.get().length >= (await this.editingSessionFileLimitPromise)) {
// Do not create files in a single editing session that would be in excess of our limit
return;
}

if (resource.scheme !== Schemas.untitled && !this._workspaceContextService.getWorkspaceFolder(resource) && !(await this._fileService.exists(resource))) {
// if the file doesn't exist yet and is outside the workspace, prompt the user for a location to save it to
const saveLocation = await this._dialogService.showSaveDialog({ title: localize('chatEditing.fileSave', '{0} wants to create a file. Choose where it should be saved.', this._chatAgentService.getDefaultAgent(ChatAgentLocation.EditingSession)?.fullName ?? 'Chat') });
if (!saveLocation) {
// don't ask the user to create the file again when the next text edit for this same resource streams in
this._filesToSkipCreating.add(resource);
return;
}
resource = saveLocation;
}

// Make these getters because the response result is not available when the file first starts to be edited
const telemetryInfo = new class {
get agentId() { return responseModel.agent?.id; }
Expand Down Expand Up @@ -730,7 +703,6 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
}

interface StoredSessionState {
readonly filesToSkipCreating: URI[];
readonly initialFileContents: ResourceMap<string>;
readonly pendingSnapshot?: IChatEditingSessionSnapshot;
readonly recentSnapshot: IChatEditingSessionSnapshot;
Expand Down Expand Up @@ -801,7 +773,6 @@ class ChatEditingSessionStorage {
}

const linearHistory = await Promise.all(data.linearHistory.map(deserializeChatEditingSessionSnapshot));
const filesToSkipCreating = data.filesToSkipCreating.map((uriStr: string) => URI.parse(uriStr));

const initialFileContents = new ResourceMap<string>();
for (const fileContentDTO of data.initialFileContents) {
Expand All @@ -811,7 +782,6 @@ class ChatEditingSessionStorage {
const recentSnapshot = await deserializeChatEditingSessionSnapshot(data.recentSnapshot);

return {
filesToSkipCreating,
initialFileContents,
pendingSnapshot,
recentSnapshot,
Expand Down Expand Up @@ -889,7 +859,6 @@ class ChatEditingSessionStorage {
initialFileContents: serializeResourceMap(state.initialFileContents, value => addFileContent(value)),
pendingSnapshot: state.pendingSnapshot ? serializeChatEditingSessionSnapshot(state.pendingSnapshot) : undefined,
recentSnapshot: serializeChatEditingSessionSnapshot(state.recentSnapshot),
filesToSkipCreating: state.filesToSkipCreating.map(uri => uri.toString()),
} satisfies IChatEditingSessionDTO;

this._logService.debug(`chatEditingSession: Storing editing session at ${storageFolder.toString()}: ${fileContents.size} files`);
Expand Down Expand Up @@ -959,5 +928,4 @@ interface IChatEditingSessionDTO {
readonly linearHistoryIndex: number;
readonly pendingSnapshot: IChatEditingSessionSnapshotDTO | undefined;
readonly initialFileContents: ResourceMapDTO<string>;
readonly filesToSkipCreating: string[];
}

0 comments on commit 07f8b09

Please sign in to comment.