Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: don't validate URIs for edits in response stream #237430

Merged
merged 1 commit into from
Jan 7, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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[];
}
Loading