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

Pass followup objects as part of the request into interactive session providers #177137

Merged
merged 2 commits into from
Mar 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/vs/workbench/api/common/extHost.protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1100,7 +1100,7 @@ export interface IInteractiveSessionDto {
}

export interface IInteractiveRequestDto {
message: string;
message: string | IInteractiveSessionReplyFollowup;
}

export interface IInteractiveResponseDto {
Expand Down
27 changes: 5 additions & 22 deletions src/vs/workbench/api/common/extHostInteractiveSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { IRelaxedExtensionDescription } from 'vs/platform/extensions/common/exte
import { ILogService } from 'vs/platform/log/common/log';
import { ExtHostInteractiveSessionShape, IInteractiveRequestDto, IInteractiveResponseDto, IInteractiveSessionDto, IMainContext, MainContext, MainThreadInteractiveSessionShape } from 'vs/workbench/api/common/extHost.protocol';
import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters';
import { IInteractiveSessionFollowup, IInteractiveSessionReplyFollowup, IInteractiveSessionResponseCommandFollowup, IInteractiveSessionUserActionEvent, IInteractiveSlashCommand } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionService';
import { IInteractiveSessionFollowup, IInteractiveSessionReplyFollowup, IInteractiveSessionUserActionEvent, IInteractiveSlashCommand } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionService';
import type * as vscode from 'vscode';

class InteractiveSessionProviderWrapper {
Expand Down Expand Up @@ -103,7 +103,7 @@ export class ExtHostInteractiveSession implements ExtHostInteractiveSessionShape
const request = await entry.provider.resolveRequest(realSession, context, token);
if (request) {
return {
message: request.message,
message: typeof request.message === 'string' ? request.message : typeConvert.InteractiveSessionReplyFollowup.from(request.message),
};
}

Expand Down Expand Up @@ -141,7 +141,7 @@ export class ExtHostInteractiveSession implements ExtHostInteractiveSessionShape
if (typeof item === 'string') {
return item;
} else {
return item.map(f => (<IInteractiveSessionReplyFollowup>{ title: f.title, message: f.message, kind: 'reply' }));
return item.map(f => typeConvert.InteractiveSessionReplyFollowup.from(f));
}
});
}
Expand All @@ -162,24 +162,7 @@ export class ExtHostInteractiveSession implements ExtHostInteractiveSessionShape
}

const rawFollowups = await entry.provider.provideFollowups(realSession, token);
return rawFollowups?.map(f => {
if (typeof f === 'string') {
return <IInteractiveSessionReplyFollowup>{ title: f, message: f, kind: 'reply' };
} else if ('commandId' in f) {
return <IInteractiveSessionResponseCommandFollowup>{
kind: 'command',
title: f.title,
commandId: f.commandId,
args: f.args
};
} else {
return <IInteractiveSessionReplyFollowup>{
kind: 'reply',
title: f.title,
message: f.message
};
}
});
return rawFollowups?.map(f => typeConvert.InteractiveSessionFollowup.from(f));
}

async $provideInteractiveReply(handle: number, sessionId: number, request: IInteractiveRequestDto, token: CancellationToken): Promise<IInteractiveResponseDto | undefined> {
Expand All @@ -195,7 +178,7 @@ export class ExtHostInteractiveSession implements ExtHostInteractiveSessionShape

const requestObj: vscode.InteractiveRequest = {
session: realSession,
message: request.message,
message: typeof request.message === 'string' ? request.message : typeConvert.InteractiveSessionReplyFollowup.to(request.message),
};

const stopWatch = StopWatch.create(false);
Expand Down
39 changes: 39 additions & 0 deletions src/vs/workbench/api/common/extHostTypeConverters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import { EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGro
import { ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import type * as vscode from 'vscode';
import * as types from './extHostTypes';
import { IInteractiveSessionFollowup, IInteractiveSessionReplyFollowup, IInteractiveSessionResponseCommandFollowup } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionService';

export namespace Command {

Expand Down Expand Up @@ -2089,3 +2090,41 @@ export namespace DataTransfer {
return newDTO;
}
}

export namespace InteractiveSessionReplyFollowup {
export function to(followup: IInteractiveSessionReplyFollowup): vscode.InteractiveSessionReplyFollowup {
return {
message: followup.message,
metadata: followup.metadata,
title: followup.title,
tooltip: followup.tooltip,
};
}

export function from(followup: vscode.InteractiveSessionReplyFollowup): IInteractiveSessionReplyFollowup {
return {
kind: 'reply',
message: followup.message,
metadata: followup.metadata,
title: followup.title,
tooltip: followup.tooltip,
};
}
}

export namespace InteractiveSessionFollowup {
export function from(followup: string | vscode.InteractiveSessionFollowup): IInteractiveSessionFollowup {
if (typeof followup === 'string') {
return <IInteractiveSessionReplyFollowup>{ title: followup, message: followup, kind: 'reply' };
} else if ('commandId' in followup) {
return <IInteractiveSessionResponseCommandFollowup>{
kind: 'command',
title: followup.title,
commandId: followup.commandId,
args: followup.args
};
} else {
return InteractiveSessionReplyFollowup.from(followup);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ export class InteractiveSessionFollowups<T extends IInteractiveSessionFollowup>
}

private renderFollowup(container: HTMLElement, followup: T): void {
const button = this._register(new Button(container, { ...this.options, supportIcons: true }));
const tooltip = 'tooltip' in followup ? followup.tooltip : undefined;
const button = this._register(new Button(container, { ...this.options, supportIcons: true, title: tooltip }));
if (followup.kind === 'reply') {
button.element.classList.add('interactive-followup-reply');
} else if (followup.kind === 'command') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import { IInteractiveSessionCodeBlockActionContext } from 'vs/workbench/contrib/
import { InteractiveSessionFollowups } from 'vs/workbench/contrib/interactiveSession/browser/interactiveSessionFollowups';
import { InteractiveSessionEditorOptions } from 'vs/workbench/contrib/interactiveSession/browser/interactiveSessionOptions';
import { interactiveSessionResponseHasProviderId } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionContextKeys';
import { IInteractiveSessionReplyFollowup, IInteractiveSlashCommand } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionService';
import { IInteractiveSessionReplyFollowup, IInteractiveSessionService, IInteractiveSlashCommand } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionService';
import { IInteractiveRequestViewModel, IInteractiveResponseViewModel, IInteractiveWelcomeMessageViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionViewModel';
import { getNWords } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionWordCounter';

Expand Down Expand Up @@ -100,6 +100,7 @@ export class InteractiveListItemRenderer extends Disposable implements ITreeRend
@ILogService private readonly logService: ILogService,
@ICommandService private readonly commandService: ICommandService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IInteractiveSessionService private readonly interactiveSessionService: IInteractiveSessionService,
) {
super();
this.renderer = this.instantiationService.createInstance(MarkdownRenderer, {});
Expand Down Expand Up @@ -218,7 +219,7 @@ export class InteractiveListItemRenderer extends Disposable implements ITreeRend
} else if (isResponseVM(element)) {
this.basicRenderElement(element.response.value, element, index, templateData);
} else if (isRequestVM(element)) {
this.basicRenderElement(element.message, element, index, templateData);
this.basicRenderElement(element.messageText, element, index, templateData);
} else {
this.renderWelcomeMessage(element, templateData);
}
Expand All @@ -241,7 +242,16 @@ export class InteractiveListItemRenderer extends Disposable implements ITreeRend
followupsContainer,
element.commandFollowups,
defaultButtonStyles,
followup => this.commandService.executeCommand(followup.commandId, ...(followup.args ?? []))));
followup => {
this.interactiveSessionService.notifyUserAction({
providerId: element.providerId,
action: {
kind: 'command',
command: followup
}
});
return this.commandService.executeCommand(followup.commandId, ...(followup.args ?? []));
}));
}
}

Expand Down Expand Up @@ -425,7 +435,7 @@ export class InteractiveSessionAccessibilityProvider implements IListAccessibili

getAriaLabel(element: InteractiveTreeItem): string {
if (isRequestVM(element)) {
return localize('interactiveRequest', "Request: {0}", element.message);
return localize('interactiveRequest', "Request: {0}", element.messageText);
}

if (isResponseVM(element)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ export class InteractiveSessionWidget extends Disposable implements IInteractive
dom.clearNode(this.followupsContainer);

if (items && items.length > 0) {
this.followupsDisposables.add(new InteractiveSessionFollowups(this.followupsContainer, items, undefined, followup => this.acceptInput(followup.message)));
this.followupsDisposables.add(new InteractiveSessionFollowups(this.followupsContainer, items, undefined, followup => this.acceptInput(followup)));
}

if (this.bodyDimension) {
Expand Down Expand Up @@ -291,7 +291,7 @@ export class InteractiveSessionWidget extends Disposable implements IInteractive
};
this.renderer = scopedInstantiationService.createInstance(InteractiveListItemRenderer, this.inputOptions, rendererDelegate);
this._register(this.renderer.onDidClickFollowup(item => {
this.acceptInput(item.message);
this.acceptInput(item);
}));

this.tree = <WorkbenchObjectTree<InteractiveTreeItem>>scopedInstantiationService.createInstance(
Expand Down Expand Up @@ -435,7 +435,7 @@ export class InteractiveSessionWidget extends Disposable implements IInteractive
}
}

async acceptInput(query?: string): Promise<void> {
async acceptInput(query?: string | IInteractiveSessionReplyFollowup): Promise<void> {
if (!this.viewModel) {
// This currently shouldn't happen anymore, but leaving this here to make sure we don't get stuck without a viewmodel
await this.initializeSessionModel();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export interface IInteractiveRequestModel {
readonly id: string;
readonly username: string;
readonly avatarIconUri?: URI;
readonly message: string;
readonly message: string | IInteractiveSessionReplyFollowup;
readonly response: IInteractiveResponseModel | undefined;
}

Expand Down Expand Up @@ -54,7 +54,7 @@ export class InteractiveRequestModel implements IInteractiveRequestModel {
return this._id;
}

constructor(public readonly message: string, public readonly username: string, public readonly avatarIconUri?: URI) {
constructor(public readonly message: string | IInteractiveSessionReplyFollowup, public readonly username: string, public readonly avatarIconUri?: URI) {
this._id = 'request_' + InteractiveRequestModel.nextId++;
}
}
Expand Down Expand Up @@ -225,7 +225,7 @@ export class InteractiveSessionModel extends Disposable implements IInteractiveS
return this._requests;
}

addRequest(message: string): InteractiveRequestModel {
addRequest(message: string | IInteractiveSessionReplyFollowup): InteractiveRequestModel {
const request = new InteractiveRequestModel(message, this.session.requesterUsername, this.session.requesterAvatarIconUri);

// TODO this is suspicious, maybe the request should know that it is "in progress" instead of having a fake response model.
Expand Down Expand Up @@ -276,7 +276,7 @@ export class InteractiveSessionModel extends Disposable implements IInteractiveS
requests: this._requests.map(r => {
return {
providerResponseId: r.response?.providerResponseId,
message: r.message,
message: typeof r.message === 'string' ? r.message : r.message.message,
response: r.response ? r.response.response.value : undefined,
responseErrorDetails: r.response?.errorDetails,
followups: r.response?.followups
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export interface IInteractiveSession {

export interface IInteractiveRequest {
session: IInteractiveSession;
message: string;
message: string | IInteractiveSessionReplyFollowup;
}

export interface IInteractiveResponse {
Expand Down Expand Up @@ -59,8 +59,10 @@ export interface IInteractiveSlashCommand {

export interface IInteractiveSessionReplyFollowup {
kind: 'reply';
title?: string;
message: string;
title?: string;
tooltip?: string;
metadata?: any;
}

export interface IInteractiveSessionResponseCommandFollowup {
Expand All @@ -77,25 +79,30 @@ export enum InteractiveSessionVoteDirection {
Down = 2
}

export interface InteractiveSessionVoteAction {
export interface IInteractiveSessionVoteAction {
kind: 'vote';
responseId: string;
direction: InteractiveSessionVoteDirection;
}

export interface InteractiveSessionCopyAction {
export interface IInteractiveSessionCopyAction {
kind: 'copy';
responseId: string;
codeBlockIndex: number;
}

export interface InteractiveSessionInsertAction {
export interface IInteractiveSessionInsertAction {
kind: 'insert';
responseId: string;
codeBlockIndex: number;
}

export type InteractiveSessionUserAction = InteractiveSessionVoteAction | InteractiveSessionCopyAction | InteractiveSessionInsertAction;
export interface IInteractiveSessionCommandAction {
kind: 'command';
command: IInteractiveSessionResponseCommandFollowup;
}

export type InteractiveSessionUserAction = IInteractiveSessionVoteAction | IInteractiveSessionCopyAction | IInteractiveSessionInsertAction | IInteractiveSessionCommandAction;

export interface IInteractiveSessionUserActionEvent {
action: InteractiveSessionUserAction;
Expand All @@ -113,7 +120,7 @@ export interface IInteractiveSessionService {
/**
* Returns whether the request was accepted.
*/
sendRequest(sessionId: number, message: string, token: CancellationToken): boolean;
sendRequest(sessionId: number, message: string | IInteractiveSessionReplyFollowup, token: CancellationToken): boolean;
getSlashCommands(sessionId: number, token: CancellationToken): Promise<IInteractiveSlashCommand[] | undefined>;
clearSession(sessionId: number): void;
acceptNewSessionState(sessionId: number, state: any): void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,10 @@ export class InteractiveSessionService extends Disposable implements IInteractiv
return model;
}

sendRequest(sessionId: number, message: string, token: CancellationToken): boolean {
this.trace('sendRequest', `sessionId: ${sessionId}, message: ${message.substring(0, 20)}${message.length > 20 ? '[...]' : ''}}`);
if (!message.trim()) {
sendRequest(sessionId: number, request: string | IInteractiveSessionReplyFollowup, token: CancellationToken): boolean {
const messageText = typeof request === 'string' ? request : request.message;
this.trace('sendRequest', `sessionId: ${sessionId}, message: ${messageText.substring(0, 20)}${messageText.length > 20 ? '[...]' : ''}}`);
if (!messageText.trim()) {
this.trace('sendRequest', 'Rejected empty message');
return false;
}
Expand All @@ -161,11 +162,11 @@ export class InteractiveSessionService extends Disposable implements IInteractiv
}

// Return immediately that the request was accepted, don't wait
this._sendRequestAsync(model, provider, message, token);
this._sendRequestAsync(model, provider, request, token);
return true;
}

private async _sendRequestAsync(model: InteractiveSessionModel, provider: IInteractiveProvider, message: string, token: CancellationToken): Promise<void> {
private async _sendRequestAsync(model: InteractiveSessionModel, provider: IInteractiveProvider, message: string | IInteractiveSessionReplyFollowup, token: CancellationToken): Promise<void> {
try {
this._pendingRequestSessions.add(model.sessionId);
const request = model.addRequest(message);
Expand All @@ -180,7 +181,7 @@ export class InteractiveSessionService extends Disposable implements IInteractiv

model.acceptResponseProgress(request, progress);
};
let rawResponse = await provider.provideReply({ session: model.session, message }, progressCallback, token);
let rawResponse = await provider.provideReply({ session: model.session, message: request.message }, progressCallback, token);
if (!rawResponse) {
this.trace('sendRequest', `Provider returned no response for session ${model.sessionId}`);
rawResponse = { session: model.session, errorDetails: { message: localize('emptyResponse', "Provider returned null response") } };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ export interface IInteractiveRequestViewModel {
readonly id: string;
readonly username: string;
readonly avatarIconUri?: URI;
readonly message: string;
readonly message: string | IInteractiveSessionReplyFollowup;
readonly messageText: string;
currentRenderedHeight: number | undefined;
}

Expand Down Expand Up @@ -164,6 +165,10 @@ export class InteractiveRequestViewModel implements IInteractiveRequestViewModel
return this._model.message;
}

get messageText() {
return typeof this.message === 'string' ? this.message : this.message.message;
}

currentRenderedHeight: number | undefined;

constructor(readonly _model: IInteractiveRequestModel) { }
Expand Down