Skip to content

Commit

Permalink
Continue chat session seamlessly when EH crashes. (#186807)
Browse files Browse the repository at this point in the history
Need to sync the existing ChatModel to the new EH and reinitialize it for the new chat provider instance.
Fix microsoft/vscode-copilot#155
  • Loading branch information
roblourens authored Jun 30, 2023
1 parent 134e152 commit 01d4949
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 14 deletions.
1 change: 0 additions & 1 deletion src/vs/workbench/contrib/chat/browser/chatEditorInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ export class ChatEditorInput extends EditorInput {

this.sessionId = this.model.sessionId;
this.providerId = this.model.providerId;
await this.model.waitForInitialization();
this._register(this.model.onDidChange(() => this._onDidChangeLabel.fire()));

return this._register(new ChatEditorModel(this.model));
Expand Down
6 changes: 5 additions & 1 deletion src/vs/workbench/contrib/chat/common/chatModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,6 @@ export interface IChatModel {
readonly requestInProgress: boolean;
readonly inputPlaceholder?: string;
getRequests(): IChatRequestModel[];
waitForInitialization(): Promise<void>;
toExport(): IExportableChatData;
toJSON(): ISerializableChatData;
}
Expand Down Expand Up @@ -385,6 +384,11 @@ export class ChatModel extends Disposable implements IChatModel {
});
}

startReinitialize(): void {
this._session = undefined;
this._isInitializedDeferred = new DeferredPromise<void>();
}

initialize(session: IChat, welcomeMessage: ChatWelcomeMessageModel | undefined): void {
if (this._session || this._isInitializedDeferred.isSettled) {
throw new Error('ChatModel is already initialized');
Expand Down
36 changes: 24 additions & 12 deletions src/vs/workbench/contrib/chat/common/chatServiceImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,13 +287,8 @@ export class ChatService extends Disposable implements IChatService {
private _startSession(providerId: string, someSessionHistory: ISerializableChatData | undefined, token: CancellationToken): ChatModel {
const model = this.instantiationService.createInstance(ChatModel, providerId, someSessionHistory);
this._sessionModels.set(model.sessionId, model);
const modelInitPromise = this.initializeSession(model, someSessionHistory, token);
modelInitPromise.then(resolvedModel => {
if (!resolvedModel) {
model.dispose();
this._sessionModels.delete(model.sessionId);
}
}).catch(err => {
const modelInitPromise = this.initializeSession(model, token);
modelInitPromise.catch(err => {
this.trace('startSession', `initializeSession failed: ${err}`);
model.setInitializationError(err);
model.dispose();
Expand All @@ -303,7 +298,22 @@ export class ChatService extends Disposable implements IChatService {
return model;
}

private async initializeSession(model: ChatModel, sessionHistory: ISerializableChatData | undefined, token: CancellationToken): Promise<ChatModel | undefined> {
private reinitializeModel(model: ChatModel): void {
model.startReinitialize();
this.startSessionInit(model, CancellationToken.None);
}

private startSessionInit(model: ChatModel, token: CancellationToken): void {
const modelInitPromise = this.initializeSession(model, token);
modelInitPromise.catch(err => {
this.trace('startSession', `initializeSession failed: ${err}`);
model.setInitializationError(err);
model.dispose();
this._sessionModels.delete(model.sessionId);
});
}

private async initializeSession(model: ChatModel, token: CancellationToken): Promise<void> {
await this.extensionService.activateByEvent(`onInteractiveSession:${model.providerId}`);

const provider = this._providers.get(model.providerId);
Expand All @@ -319,18 +329,16 @@ export class ChatService extends Disposable implements IChatService {
}

if (!session) {
this.trace('startSession', 'Provider returned no session');
return undefined;
throw new Error('Provider returned no session');
}

this.trace('startSession', `Provider returned session`);

const welcomeMessage = sessionHistory ? undefined : withNullAsUndefined(await provider.provideWelcomeMessage?.(token));
const welcomeMessage = model.welcomeMessage ? undefined : withNullAsUndefined(await provider.provideWelcomeMessage?.(token));
const welcomeModel = welcomeMessage && new ChatWelcomeMessageModel(
welcomeMessage.map(item => typeof item === 'string' ? new MarkdownString(item) : item as IChatReplyFollowup[]), session.responderUsername, session.responderAvatarIconUri);

model.initialize(session, welcomeModel);
return model;
}

getSession(sessionId: string): IChatModel | undefined {
Expand Down Expand Up @@ -618,6 +626,10 @@ export class ChatService extends Disposable implements IChatService {
this._providers.set(provider.id, provider);
this._hasProvider.set(true);

Array.from(this._sessionModels.values())
.filter(model => model.providerId === provider.id)
.forEach(model => this.reinitializeModel(model));

return toDisposable(() => {
this.trace('registerProvider', `Disposing chat provider`);
this._providers.delete(provider.id);
Expand Down

0 comments on commit 01d4949

Please sign in to comment.