Add errorDetails to interactive session responses. (#176457)

* Add errorDetails to interactive session responses.
Also avoid calling updateElementHeight during a renderElement

* Fix hygiene
This commit is contained in:
Rob Lourens
2023-03-07 22:30:46 -05:00
committed by GitHub
parent 40ad2f338c
commit 7114851c4a
9 changed files with 118 additions and 56 deletions

View File

@@ -51,7 +51,7 @@ import { SaveReason } from 'vs/workbench/common/editor';
import { IRevealOptions, ITreeItem, IViewBadge } from 'vs/workbench/common/views'; import { IRevealOptions, ITreeItem, IViewBadge } from 'vs/workbench/common/views';
import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
import { DebugConfigurationProviderTriggerKind, IAdapterDescriptor, IConfig, IDebugSessionReplMode } from 'vs/workbench/contrib/debug/common/debug'; import { DebugConfigurationProviderTriggerKind, IAdapterDescriptor, IConfig, IDebugSessionReplMode } from 'vs/workbench/contrib/debug/common/debug';
import { IInteractiveSessionResponseCommandFollowup } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionModel'; import { IInteractiveResponseErrorDetails, IInteractiveSessionResponseCommandFollowup } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionModel';
import * as notebookCommon from 'vs/workbench/contrib/notebook/common/notebookCommon'; import * as notebookCommon from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellExecutionUpdateType } from 'vs/workbench/contrib/notebook/common/notebookExecutionService'; import { CellExecutionUpdateType } from 'vs/workbench/contrib/notebook/common/notebookExecutionService';
import { ICellExecutionComplete, ICellExecutionStateUpdate } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { ICellExecutionComplete, ICellExecutionStateUpdate } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
@@ -1102,6 +1102,7 @@ export interface IInteractiveRequestDto {
export interface IInteractiveResponseDto { export interface IInteractiveResponseDto {
followups?: string[]; followups?: string[];
commandFollowups?: IInteractiveSessionResponseCommandFollowup[]; commandFollowups?: IInteractiveSessionResponseCommandFollowup[];
errorDetails?: IInteractiveResponseErrorDetails;
} }
export interface IInteractiveResponseProgressDto { export interface IInteractiveResponseProgressDto {

View File

@@ -6,6 +6,7 @@
import { CancellationToken } from 'vs/base/common/cancellation'; import { CancellationToken } from 'vs/base/common/cancellation';
import { toDisposable } from 'vs/base/common/lifecycle'; import { toDisposable } from 'vs/base/common/lifecycle';
import { withNullAsUndefined } from 'vs/base/common/types'; import { withNullAsUndefined } from 'vs/base/common/types';
import { localize } from 'vs/nls';
import { IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { ILogService } from 'vs/platform/log/common/log'; import { ILogService } from 'vs/platform/log/common/log';
import { ExtHostInteractiveSessionShape, IInteractiveRequestDto, IInteractiveResponseDto, IInteractiveSessionDto, IMainContext, MainContext, MainThreadInteractiveSessionShape } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostInteractiveSessionShape, IInteractiveRequestDto, IInteractiveResponseDto, IInteractiveSessionDto, IMainContext, MainContext, MainThreadInteractiveSessionShape } from 'vs/workbench/api/common/extHost.protocol';
@@ -33,7 +34,7 @@ export class ExtHostInteractiveSession implements ExtHostInteractiveSessionShape
constructor( constructor(
mainContext: IMainContext, mainContext: IMainContext,
_logService: ILogService private readonly logService: ILogService
) { ) {
this._proxy = mainContext.getProxy(MainContext.MainThreadInteractiveSession); this._proxy = mainContext.getProxy(MainContext.MainThreadInteractiveSession);
} }
@@ -147,20 +148,30 @@ export class ExtHostInteractiveSession implements ExtHostInteractiveSessionShape
const progressObj: vscode.Progress<vscode.InteractiveProgress> = { const progressObj: vscode.Progress<vscode.InteractiveProgress> = {
report: (progress: vscode.InteractiveProgress) => this._proxy.$acceptInteractiveResponseProgress(handle, sessionId, { responsePart: progress.content }) report: (progress: vscode.InteractiveProgress) => this._proxy.$acceptInteractiveResponseProgress(handle, sessionId, { responsePart: progress.content })
}; };
const res = await entry.provider.provideResponseWithProgress(requestObj, progressObj, token); let result: vscode.InteractiveResponseForProgress | undefined | null;
if (realSession.saveState) { try {
const newState = realSession.saveState(); result = await entry.provider.provideResponseWithProgress(requestObj, progressObj, token);
this._proxy.$acceptInteractiveSessionState(sessionId, newState); if (!result) {
result = { errorDetails: { message: localize('emptyResponse', "Provider returned null response") } };
}
} catch (err) {
result = { errorDetails: { message: localize('errorResponse', "Error from provider: {0}", err.message) } };
this.logService.error(err);
} }
if (!res) { try {
return; if (realSession.saveState) {
const newState = realSession.saveState();
this._proxy.$acceptInteractiveSessionState(sessionId, newState);
}
} catch (err) {
this.logService.warn(err);
} }
return { followups: res.followups, commandFollowups: res.commands }; return { followups: result.followups, commandFollowups: result.commands, errorDetails: result.errorDetails };
} }
throw new Error('provider must implement either provideResponse or provideResponseWithProgress'); throw new Error('Provider must implement either provideResponse or provideResponseWithProgress');
} }
$releaseSession(sessionId: number) { $releaseSession(sessionId: number) {

View File

@@ -5,6 +5,7 @@
import * as dom from 'vs/base/browser/dom'; import * as dom from 'vs/base/browser/dom';
import { Button } from 'vs/base/browser/ui/button/button'; import { Button } from 'vs/base/browser/ui/button/button';
import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels';
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
import { ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; import { ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree';
@@ -60,7 +61,7 @@ interface IItemHeightChangeParams {
height: number; height: number;
} }
const forceVerboseLayoutTracing = true; const forceVerboseLayoutTracing = false;
export class InteractiveListItemRenderer extends Disposable implements ITreeRenderer<InteractiveTreeItem, FuzzyScore, IInteractiveListItemTemplate> { export class InteractiveListItemRenderer extends Disposable implements ITreeRenderer<InteractiveTreeItem, FuzzyScore, IInteractiveListItemTemplate> {
static readonly cursorCharacter = '\u258c'; static readonly cursorCharacter = '\u258c';
@@ -176,12 +177,12 @@ export class InteractiveListItemRenderer extends Disposable implements ITreeRend
this.traceLayout('renderElement', `start progressive render ${kind}, index=${index}`); this.traceLayout('renderElement', `start progressive render ${kind}, index=${index}`);
const progressiveRenderingDisposables = templateData.elementDisposables.add(new DisposableStore()); const progressiveRenderingDisposables = templateData.elementDisposables.add(new DisposableStore());
const timer = templateData.elementDisposables.add(new IntervalTimer()); const timer = templateData.elementDisposables.add(new IntervalTimer());
const runProgressiveRender = () => { const runProgressiveRender = (initial?: boolean) => {
if (this.doNextProgressiveRender(element, index, templateData, progressiveRenderingDisposables)) { if (this.doNextProgressiveRender(element, index, templateData, !!initial, progressiveRenderingDisposables)) {
timer.cancel(); timer.cancel();
} }
}; };
runProgressiveRender(); runProgressiveRender(true);
timer.cancelAndSet(runProgressiveRender, 100); timer.cancelAndSet(runProgressiveRender, 100);
} else if (isResponseVM(element)) { } else if (isResponseVM(element)) {
this.basicRenderElement(element.response.value, element, index, templateData); this.basicRenderElement(element.response.value, element, index, templateData);
@@ -196,6 +197,11 @@ export class InteractiveListItemRenderer extends Disposable implements ITreeRend
templateData.value.appendChild(result.element); templateData.value.appendChild(result.element);
templateData.elementDisposables.add(result); templateData.elementDisposables.add(result);
if (isResponseVM(element) && element.errorDetails) {
const errorDetails = dom.append(templateData.value, $('.interactive-response-error-details', undefined, renderIcon(Codicon.error)));
errorDetails.appendChild($('span', undefined, element.errorDetails.message));
}
if (isResponseVM(element) && index === this.delegate.getListLength() - 1) { if (isResponseVM(element) && index === this.delegate.getListLength() - 1) {
const followupsContainer = dom.append(templateData.value, $('.interactive-response-followups')); const followupsContainer = dom.append(templateData.value, $('.interactive-response-followups'));
const followups = element.commandFollowups ?? element.followups ?? []; const followups = element.commandFollowups ?? element.followups ?? [];
@@ -217,36 +223,43 @@ export class InteractiveListItemRenderer extends Disposable implements ITreeRend
} }
} }
private doNextProgressiveRender(element: IInteractiveResponseViewModel, index: number, templateData: IInteractiveListItemTemplate, disposables: DisposableStore): boolean { private doNextProgressiveRender(element: IInteractiveResponseViewModel, index: number, templateData: IInteractiveListItemTemplate, isInRenderElement: boolean, disposables: DisposableStore): boolean {
disposables.clear(); disposables.clear();
const toRender = this.getProgressiveMarkdownToRender(element);
if (toRender) {
const isFullyRendered = element.renderData?.isFullyRendered;
if (isFullyRendered) {
this.traceLayout('runProgressiveRender', `end progressive render, index=${index}`);
if (element.isComplete) {
this.traceLayout('runProgressiveRender', `and disposing renderData, response is complete, index=${index}`);
element.renderData = undefined;
} else {
this.traceLayout('runProgressiveRender', `Rendered all available words, but model is not complete.`);
}
disposables.clear();
this.basicRenderElement(element.response.value, element, index, templateData);
} else {
const plusCursor = toRender.match(/```.*$/) ? toRender + `\n${InteractiveListItemRenderer.cursorCharacter}` : toRender + ` ${InteractiveListItemRenderer.cursorCharacter}`;
const result = this.renderMarkdown(element, index, new MarkdownString(plusCursor), disposables, templateData, true);
dom.clearNode(templateData.value);
templateData.value.appendChild(result.element);
disposables.add(result);
}
const height = templateData.rowContainer.offsetHeight; // TODO- this method has the side effect of updating element.renderData
element.currentRenderedHeight = height; const toRender = this.getProgressiveMarkdownToRender(element);
this._onDidChangeItemHeight.fire({ element, height: templateData.rowContainer.offsetHeight }); const isFullyRendered = element.renderData?.isFullyRendered;
return !!isFullyRendered; if (isFullyRendered) {
// We've reached the end of the available content, so do a normal render
this.traceLayout('runProgressiveRender', `end progressive render, index=${index}`);
if (element.isComplete) {
this.traceLayout('runProgressiveRender', `and disposing renderData, response is complete, index=${index}`);
element.renderData = undefined;
} else {
this.traceLayout('runProgressiveRender', `Rendered all available words, but model is not complete.`);
}
disposables.clear();
this.basicRenderElement(element.response.value, element, index, templateData);
} else if (toRender) {
// Doing the progressive render
const plusCursor = toRender.match(/```.*$/) ? toRender + `\n${InteractiveListItemRenderer.cursorCharacter}` : toRender + ` ${InteractiveListItemRenderer.cursorCharacter}`;
const result = this.renderMarkdown(element, index, new MarkdownString(plusCursor), disposables, templateData, true);
dom.clearNode(templateData.value);
templateData.value.appendChild(result.element);
disposables.add(result);
} else {
// Nothing new to render, not done, keep waiting
return false;
} }
return false; // Some render happened - update the height
const height = templateData.rowContainer.offsetHeight;
element.currentRenderedHeight = height;
if (!isInRenderElement) {
this._onDidChangeItemHeight.fire({ element, height: templateData.rowContainer.offsetHeight });
}
return !!isFullyRendered;
} }
private renderMarkdown(element: InteractiveTreeItem, index: number, markdown: IMarkdownString, disposables: DisposableStore, templateData: IInteractiveListItemTemplate, fillInIncompleteTokens = false): IMarkdownRenderResult { private renderMarkdown(element: InteractiveTreeItem, index: number, markdown: IMarkdownString, disposables: DisposableStore, templateData: IInteractiveListItemTemplate, fillInIncompleteTokens = false): IMarkdownRenderResult {

View File

@@ -184,3 +184,13 @@
font-size: 11px; font-size: 11px;
font-weight: 600; font-weight: 600;
} }
.interactive-session .interactive-response .interactive-response-error-details {
display: flex;
align-items: center;
gap: 6px;
}
.interactive-session .interactive-response .interactive-response-error-details .codicon {
color: var(--vscode-errorForeground);
}

View File

@@ -8,7 +8,7 @@ import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent';
import { Disposable } from 'vs/base/common/lifecycle'; import { Disposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import { ILogService } from 'vs/platform/log/common/log'; import { ILogService } from 'vs/platform/log/common/log';
import { IInteractiveSession } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionService'; import { IInteractiveResponse, IInteractiveSession } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionService';
export interface IInteractiveRequestModel { export interface IInteractiveRequestModel {
readonly id: string; readonly id: string;
@@ -24,6 +24,11 @@ export interface IInteractiveSessionResponseCommandFollowup {
title: string; // supports codicon strings title: string; // supports codicon strings
} }
export interface IInteractiveResponseErrorDetails {
message: string;
responseIsIncomplete?: boolean;
}
export interface IInteractiveResponseModel { export interface IInteractiveResponseModel {
readonly onDidChange: Event<void>; readonly onDidChange: Event<void>;
readonly id: string; readonly id: string;
@@ -33,6 +38,7 @@ export interface IInteractiveResponseModel {
readonly isComplete: boolean; readonly isComplete: boolean;
readonly followups?: string[]; readonly followups?: string[];
readonly commandFollowups?: IInteractiveSessionResponseCommandFollowup[]; readonly commandFollowups?: IInteractiveSessionResponseCommandFollowup[];
readonly errorDetails?: IInteractiveResponseErrorDetails;
} }
export function isRequest(item: unknown): item is IInteractiveRequestModel { export function isRequest(item: unknown): item is IInteractiveRequestModel {
@@ -89,11 +95,17 @@ export class InteractiveResponseModel extends Disposable implements IInteractive
return this._response; return this._response;
} }
constructor(response: IMarkdownString, public readonly username: string, public readonly avatarIconUri?: URI, isComplete: boolean = false, followups?: string[]) { private _errorDetails: IInteractiveResponseErrorDetails | undefined;
public get errorDetails(): IInteractiveResponseErrorDetails | undefined {
return this._errorDetails;
}
constructor(response: IMarkdownString, public readonly username: string, public readonly avatarIconUri?: URI, isComplete: boolean = false, errorDetails?: IInteractiveResponseErrorDetails, followups?: string[]) {
super(); super();
this._response = response; this._response = response;
this._isComplete = isComplete; this._isComplete = isComplete;
this._followups = followups; this._followups = followups;
this._errorDetails = errorDetails;
this._id = 'response_' + InteractiveResponseModel.nextId++; this._id = 'response_' + InteractiveResponseModel.nextId++;
} }
@@ -102,10 +114,11 @@ export class InteractiveResponseModel extends Disposable implements IInteractive
this._onDidChange.fire(); this._onDidChange.fire();
} }
complete(followups: string[] | undefined, commandFollowups: IInteractiveSessionResponseCommandFollowup[] | undefined): void { complete(followups: string[] | undefined, commandFollowups: IInteractiveSessionResponseCommandFollowup[] | undefined, errorDetails?: IInteractiveResponseErrorDetails): void {
this._isComplete = true; this._isComplete = true;
this._followups = followups; this._followups = followups;
this._commandFollowups = commandFollowups; this._commandFollowups = commandFollowups;
this._errorDetails = errorDetails;
this._onDidChange.fire(); this._onDidChange.fire();
} }
} }
@@ -125,6 +138,7 @@ export interface ISerializableInteractiveSessionsData {
export interface ISerializableInteractiveSessionRequestData { export interface ISerializableInteractiveSessionRequestData {
message: string; message: string;
response: string | undefined; response: string | undefined;
responseErrorDetails: IInteractiveResponseErrorDetails | undefined;
} }
export interface ISerializableInteractiveSessionData { export interface ISerializableInteractiveSessionData {
@@ -181,10 +195,10 @@ export class InteractiveSessionModel extends Disposable implements IInteractiveS
return []; return [];
} }
return requests.map((r: ISerializableInteractiveSessionRequestData) => { return requests.map((raw: ISerializableInteractiveSessionRequestData) => {
const request = new InteractiveRequestModel(r.message, this.session.requesterUsername, this.session.requesterAvatarIconUri); const request = new InteractiveRequestModel(raw.message, this.session.requesterUsername, this.session.requesterAvatarIconUri);
if (r.response) { if (raw.response || raw.responseErrorDetails) {
request.response = new InteractiveResponseModel(new MarkdownString(r.response), this.session.responderUsername, this.session.responderAvatarIconUri, true); request.response = new InteractiveResponseModel(new MarkdownString(raw.response), this.session.responderUsername, this.session.responderAvatarIconUri, true, raw.responseErrorDetails);
} }
return request; return request;
}); });
@@ -224,8 +238,8 @@ export class InteractiveSessionModel extends Disposable implements IInteractiveS
} }
} }
completeResponse(request: InteractiveRequestModel, followups?: string[], commandFollowups?: IInteractiveSessionResponseCommandFollowup[]): void { completeResponse(request: InteractiveRequestModel, response: IInteractiveResponse): void {
request.response!.complete(followups, commandFollowups); request.response!.complete(response.followups, response.commandFollowups, response.errorDetails);
} }
setResponse(request: InteractiveRequestModel, response: InteractiveResponseModel): void { setResponse(request: InteractiveRequestModel, response: InteractiveResponseModel): void {
@@ -239,6 +253,7 @@ export class InteractiveSessionModel extends Disposable implements IInteractiveS
return { return {
message: r.message, message: r.message,
response: r.response ? r.response.response.value : undefined, response: r.response ? r.response.response.value : undefined,
responseErrorDetails: r.response?.errorDetails
}; };
}), }),
providerId: this.providerId, providerId: this.providerId,

View File

@@ -8,7 +8,7 @@ import { IDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import { ProviderResult } from 'vs/editor/common/languages'; import { ProviderResult } from 'vs/editor/common/languages';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IInteractiveSessionResponseCommandFollowup, InteractiveSessionModel } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionModel'; import { IInteractiveResponseErrorDetails, IInteractiveSessionResponseCommandFollowup, InteractiveSessionModel } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionModel';
export interface IInteractiveSession { export interface IInteractiveSession {
id: number; id: number;
@@ -28,6 +28,7 @@ export interface IInteractiveResponse {
session: IInteractiveSession; session: IInteractiveSession;
followups?: string[]; followups?: string[];
commandFollowups?: IInteractiveSessionResponseCommandFollowup[]; commandFollowups?: IInteractiveSessionResponseCommandFollowup[];
errorDetails?: IInteractiveResponseErrorDetails;
} }
export interface IInteractiveProgress { export interface IInteractiveProgress {

View File

@@ -8,6 +8,7 @@ import { groupBy } from 'vs/base/common/collections';
import { Iterable } from 'vs/base/common/iterator'; import { Iterable } from 'vs/base/common/iterator';
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { withNullAsUndefined } from 'vs/base/common/types'; import { withNullAsUndefined } from 'vs/base/common/types';
import { localize } from 'vs/nls';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log'; import { ILogService } from 'vs/platform/log/common/log';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
@@ -125,7 +126,6 @@ export class InteractiveSessionService extends Disposable implements IInteractiv
return false; return false;
} }
// TODO log failures, add dummy response with error message
const _sendRequest = async (): Promise<void> => { const _sendRequest = async (): Promise<void> => {
try { try {
this._pendingRequestSessions.add(sessionId); this._pendingRequestSessions.add(sessionId);
@@ -134,13 +134,13 @@ export class InteractiveSessionService extends Disposable implements IInteractiv
this.trace('sendRequest', `Provider returned progress for session ${sessionId}, ${progress.responsePart.length} chars`); this.trace('sendRequest', `Provider returned progress for session ${sessionId}, ${progress.responsePart.length} chars`);
model.mergeResponseContent(request, progress.responsePart); model.mergeResponseContent(request, progress.responsePart);
}; };
const rawResponse = await provider.provideReply({ session: model.session, message }, progressCallback, token); let rawResponse = await provider.provideReply({ session: model.session, message }, progressCallback, token);
if (!rawResponse) { if (!rawResponse) {
this.trace('sendRequest', `Provider returned no response for session ${sessionId}`); this.trace('sendRequest', `Provider returned no response for session ${sessionId}`);
return; rawResponse = { session: model.session, errorDetails: { message: localize('emptyResponse', "Provider returned null response") } };
} }
model.completeResponse(request, rawResponse.followups, rawResponse.commandFollowups); model.completeResponse(request, rawResponse);
this.trace('sendRequest', `Provider returned response for session ${sessionId} with ${rawResponse.followups} followups`); this.trace('sendRequest', `Provider returned response for session ${sessionId} with ${rawResponse.followups} followups`);
} finally { } finally {
this._pendingRequestSessions.delete(sessionId); this._pendingRequestSessions.delete(sessionId);

View File

@@ -9,7 +9,7 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log'; import { ILogService } from 'vs/platform/log/common/log';
import { IInteractiveRequestModel, IInteractiveResponseModel, IInteractiveSessionModel, IInteractiveSessionResponseCommandFollowup } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionModel'; import { IInteractiveRequestModel, IInteractiveResponseErrorDetails, IInteractiveResponseModel, IInteractiveSessionModel, IInteractiveSessionResponseCommandFollowup } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionModel';
import { IInteractiveSessionService } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionService'; import { IInteractiveSessionService } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionService';
import { countWords } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionWordCounter'; import { countWords } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionWordCounter';
@@ -58,6 +58,7 @@ export interface IInteractiveResponseViewModel {
readonly isComplete: boolean; readonly isComplete: boolean;
readonly followups?: string[]; readonly followups?: string[];
readonly commandFollowups?: IInteractiveSessionResponseCommandFollowup[]; readonly commandFollowups?: IInteractiveSessionResponseCommandFollowup[];
readonly errorDetails?: IInteractiveResponseErrorDetails;
readonly progressiveResponseRenderingEnabled: boolean; readonly progressiveResponseRenderingEnabled: boolean;
readonly contentUpdateTimings?: IInteractiveSessionLiveUpdateData; readonly contentUpdateTimings?: IInteractiveSessionLiveUpdateData;
renderData?: IInteractiveResponseRenderData; renderData?: IInteractiveResponseRenderData;
@@ -196,6 +197,10 @@ export class InteractiveResponseViewModel extends Disposable implements IInterac
return this._model.commandFollowups; return this._model.commandFollowups;
} }
get errorDetails() {
return this._model.errorDetails;
}
renderData: IInteractiveResponseRenderData | undefined = undefined; renderData: IInteractiveResponseRenderData | undefined = undefined;
currentRenderedHeight: number | undefined; currentRenderedHeight: number | undefined;
@@ -224,7 +229,7 @@ export class InteractiveResponseViewModel extends Disposable implements IInterac
} }
this._register(_model.onDidChange(() => { this._register(_model.onDidChange(() => {
if (this._isPlaceholder && _model.response.value) { if (this._isPlaceholder && (_model.response.value || this.isComplete)) {
this._isPlaceholder = false; this._isPlaceholder = false;
if (this.renderData) { if (this.renderData) {
this.renderData.renderedWordCount = 0; this.renderData.renderedWordCount = 0;

View File

@@ -76,9 +76,15 @@ declare module 'vscode' {
followups?: string[]; followups?: string[];
} }
export interface InteractiveResponseErrorDetails {
message: string;
responseIsIncomplete?: boolean;
}
export interface InteractiveResponseForProgress { export interface InteractiveResponseForProgress {
followups?: string[]; followups?: string[];
commands?: InteractiveResponseCommand[]; commands?: InteractiveResponseCommand[];
errorDetails?: InteractiveResponseErrorDetails;
} }
export interface InteractiveProgress { export interface InteractiveProgress {