From 2a347db49e9f632aaac51d542df05512e680ef47 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 5 Apr 2024 16:27:17 +0200 Subject: [PATCH] Prepare for inline chat participants (#209628) * debt - remove bridge agent when "true" agent appears * create a SessionExchange from chat model request/response pair * fix markdown message passing * use (a little) more of ChatModel world and less of inline chat models * remove unused test * fix tests --- .../contrib/chat/common/chatAgents.ts | 1 + .../contrib/chat/common/chatModel.ts | 4 + .../browser/inlineChatController.ts | 54 ++++-- .../inlineChat/browser/inlineChatSession.ts | 22 +-- .../browser/inlineChatSessionServiceImpl.ts | 182 ++++++++++++++---- .../test/browser/inlineChatSession.test.ts | 45 +---- 6 files changed, 192 insertions(+), 116 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index 2aa2893de49..fb19f9f6abd 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -39,6 +39,7 @@ export namespace ChatAgentLocation { case 'panel': return ChatAgentLocation.Panel; case 'terminal': return ChatAgentLocation.Terminal; case 'notebook': return ChatAgentLocation.Notebook; + case 'editor': return ChatAgentLocation.Editor; } return ChatAgentLocation.Panel; } diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 17813f0d464..3f27837a5c9 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -527,6 +527,10 @@ export class ChatModel extends Disposable implements IChatModel { return !!lastRequest && !!lastRequest.response && !lastRequest.response.isComplete; } + get hasRequests(): boolean { + return this._requests.length > 0; + } + private _creationDate: number; get creationDate(): number { return this._creationDate; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index d13c2fae2dc..8ad246733d4 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -45,7 +45,7 @@ import { IModelDeltaDecoration, ITextModel, IValidEditOperation } from 'vs/edito import { InlineChatContentWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatContentWidget'; import { MessageController } from 'vs/editor/contrib/message/browser/messageController'; import { tail } from 'vs/base/common/arrays'; -import { IChatRequestModel } from 'vs/workbench/contrib/chat/common/chatModel'; +import { IChatRequestModel, IResponse } from 'vs/workbench/contrib/chat/common/chatModel'; import { InlineChatError } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart'; @@ -197,10 +197,9 @@ export class InlineChatController implements IEditorContribution { dispose(): void { if (this._currentRun) { - this._messages.fire((this._session?.lastExchange + this._messages.fire(this._session?.chatModel.hasRequests ? Message.PAUSE_SESSION - : Message.CANCEL_SESSION) - ); + : Message.CANCEL_SESSION); } this._store.dispose(); this._isDisposed = true; @@ -381,10 +380,10 @@ export class InlineChatController implements IEditorContribution { this._zone.value.widget.updateInfo(message); - this._showWidget(!this._session.lastExchange); + this._showWidget(!this._session.chatModel.hasRequests); this._sessionStore.add(this._editor.onDidChangeModel((e) => { - const msg = this._session?.lastExchange + const msg = this._session?.chatModel.hasRequests ? Message.PAUSE_SESSION // pause when switching models/tabs and when having a previous exchange : Message.CANCEL_SESSION; this._log('model changed, pause or cancel session', msg, e); @@ -520,7 +519,7 @@ export class InlineChatController implements IEditorContribution { //#endregion ------- DEBT - if (!this._session.lastExchange) { + if (!this._session.chatModel.hasRequests) { return State.WAIT_FOR_INPUT; } else if (options.isUnstashed) { delete options.isUnstashed; @@ -646,7 +645,7 @@ export class InlineChatController implements IEditorContribution { private async [State.SHOW_REQUEST](options: InlineChatRunOptions): Promise { assertType(this._session); - assertType(this._session.lastInput); + assertType(this._session.chatModel.requestInProgress); const request: IChatRequestModel | undefined = tail(this._session.chatModel.getRequests()); @@ -789,12 +788,11 @@ export class InlineChatController implements IEditorContribution { const { response } = this._session.lastExchange!; let responseTypes: InlineChatResponseTypes | undefined; - for (const { response } of this._session.exchanges) { - - const thisType = response instanceof ReplyResponse - ? response.responseType - : undefined; - + for (const request of this._session.chatModel.getRequests()) { + if (!request.response) { + continue; + } + const thisType = asInlineChatResponseType(request.response.response); if (responseTypes === undefined) { responseTypes = thisType; } else if (responseTypes !== thisType) { @@ -1144,7 +1142,7 @@ export class InlineChatController implements IEditorContribution { } feedbackLast(kind: InlineChatResponseFeedbackKind) { - if (this._session?.lastExchange && this._session.lastExchange.response instanceof ReplyResponse) { + if (this._session?.lastExchange?.response instanceof ReplyResponse) { this._session.provider.handleInlineChatResponseFeedback?.(this._session.session, this._session.lastExchange.response.raw, kind); switch (kind) { case InlineChatResponseFeedbackKind.Helpful: @@ -1167,7 +1165,7 @@ export class InlineChatController implements IEditorContribution { } acceptSession(): void { - if (this._session?.lastExchange && this._session.lastExchange.response instanceof ReplyResponse) { + if (this._session?.lastExchange?.response instanceof ReplyResponse) { this._session.provider.handleInlineChatResponseFeedback?.(this._session.session, this._session.lastExchange.response.raw, InlineChatResponseFeedbackKind.Accepted); } this._messages.fire(Message.ACCEPT_SESSION); @@ -1189,7 +1187,7 @@ export class InlineChatController implements IEditorContribution { const diff = await this._editorWorkerService.computeDiff(this._session.textModel0.uri, this._session.textModelN.uri, { ignoreTrimWhitespace: false, maxComputationTimeMs: 5000, computeMoves: false }, 'advanced'); result = this._session.asChangedText(diff?.changes ?? []); - if (this._session.lastExchange && this._session.lastExchange.response instanceof ReplyResponse) { + if (this._session.lastExchange?.response instanceof ReplyResponse) { this._session.provider.handleInlineChatResponseFeedback?.(this._session.session, this._session.lastExchange.response.raw, InlineChatResponseFeedbackKind.Undone); } } @@ -1253,3 +1251,25 @@ async function sendRequest(accessor: ServicesAccessor, query: string) { widget.focusInput(); widget.acceptInput(query); } + +function asInlineChatResponseType(response: IResponse): InlineChatResponseTypes { + let result: InlineChatResponseTypes | undefined; + for (const item of response.value) { + let thisType: InlineChatResponseTypes; + switch (item.kind) { + case 'textEdit': + thisType = InlineChatResponseTypes.OnlyEdits; + break; + case 'markdownContent': + default: + thisType = InlineChatResponseTypes.OnlyMessages; + break; + } + if (result === undefined) { + result = thisType; + } else if (result !== thisType) { + return InlineChatResponseTypes.Mixed; + } + } + return result ?? InlineChatResponseTypes.Empty; +} diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts index b63d0d4355f..f8f415489d6 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts @@ -8,7 +8,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { ResourceEdit, ResourceFileEdit, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService'; import { IWorkspaceTextEdit, TextEdit, WorkspaceEdit } from 'vs/editor/common/languages'; import { IIdentifiedSingleEditOperation, IModelDecorationOptions, IModelDeltaDecoration, ITextModel, IValidEditOperation, TrackedRangeStickiness } from 'vs/editor/common/model'; -import { EditMode, IInlineChatSessionProvider, IInlineChatSession, IInlineChatBulkEditResponse, IInlineChatEditResponse, InlineChatResponseType, InlineChatResponseTypes, CTX_INLINE_CHAT_HAS_STASHED_SESSION } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { EditMode, IInlineChatSessionProvider, IInlineChatSession, IInlineChatBulkEditResponse, IInlineChatEditResponse, InlineChatResponseType, CTX_INLINE_CHAT_HAS_STASHED_SESSION } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { IRange, Range } from 'vs/editor/common/core/range'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { toErrorMessage } from 'vs/base/common/errorMessage'; @@ -212,11 +212,7 @@ export class Session { this._isUnstashed = false; const newLen = this._exchange.push(exchange); this._teldata.rounds += `${newLen}|`; - this._teldata.responseTypes += `${exchange.response instanceof ReplyResponse ? exchange.response.responseType : InlineChatResponseTypes.Empty}|`; - } - - get exchanges(): Iterable { - return this._exchange; + // this._teldata.responseTypes += `${exchange.response instanceof ReplyResponse ? exchange.response.responseType : InlineChatResponseTypes.Empty}|`; } get lastExchange(): SessionExchange | undefined { @@ -321,7 +317,6 @@ export class ReplyResponse { readonly untitledTextModel: IUntitledTextEditorModel | undefined; readonly workspaceEdit: WorkspaceEdit | undefined; - readonly responseType: InlineChatResponseTypes; constructor( readonly raw: IInlineChatBulkEditResponse | IInlineChatEditResponse, @@ -405,19 +400,6 @@ export class ReplyResponse { } this.workspaceEdit = { edits: workspaceEdits }; } - - - const hasEdits = editsMap.size > 0; - const hasMessage = mdContent.value.length > 0; - if (hasEdits && hasMessage) { - this.responseType = InlineChatResponseTypes.Mixed; - } else if (hasEdits) { - this.responseType = InlineChatResponseTypes.OnlyEdits; - } else if (hasMessage) { - this.responseType = InlineChatResponseTypes.OnlyMessages; - } else { - this.responseType = InlineChatResponseTypes.Empty; - } } } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts index 672b2be6119..097dff843f2 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts @@ -4,13 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; import { Emitter, Event } from 'vs/base/common/event'; -import { EditMode, IInlineChatSession, IInlineChatService, IInlineChatSessionProvider, InlineChatResponseFeedbackKind, IInlineChatProgressItem, IInlineChatResponse, IInlineChatRequest } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { EditMode, IInlineChatSession, IInlineChatService, IInlineChatSessionProvider, InlineChatResponseFeedbackKind, IInlineChatProgressItem, IInlineChatResponse, IInlineChatRequest, InlineChatResponseType, IInlineChatBulkEditResponse } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { Range } from 'vs/editor/common/core/range'; import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IModelService } from 'vs/editor/common/services/model'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { DisposableMap, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { DisposableMap, DisposableStore, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; import { ILogService } from 'vs/platform/log/common/log'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -35,12 +35,15 @@ import { MarkdownString } from 'vs/base/common/htmlContent'; import { TextEdit } from 'vs/editor/common/languages'; import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { Codicon } from 'vs/base/common/codicons'; +import { CancellationError } from 'vs/base/common/errors'; +import { LRUCache } from 'vs/base/common/map'; class BridgeAgent implements IChatAgentImplementation { constructor( private readonly _data: IChatAgentData, private readonly _sessions: ReadonlyMap, + private readonly _postLastResponse: (data: { id: string; response: ReplyResponse | ErrorResponse | EmptyResponse }) => void, @IInstantiationService private readonly _instaService: IInstantiationService, ) { } @@ -133,13 +136,17 @@ class BridgeAgent implements IChatAgentImplementation { response = new ErrorResponse(e); } - session.addExchange(new SessionExchange(session.lastInput!, response)); + this._postLastResponse({ id: request.requestId, response }); // TODO@jrieken // result?.placeholder // result?.wholeRange - return { metadata: { inlineChatResponse: result } }; + return { + metadata: { + inlineChatResponse: result + } + }; } async provideFollowups(request: IChatAgentRequest, result: IChatAgentResult, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise { @@ -192,6 +199,8 @@ export class InlineChatError extends Error { } } +const _bridgeAgentId = 'brigde.editor'; + export class InlineChatSessionServiceImpl implements IInlineChatSessionService { declare _serviceBrand: undefined; @@ -214,6 +223,8 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { private readonly _keyComputers = new Map(); private _recordings: Recording[] = []; + private readonly _lastResponsesFromBridgeAgent = new LRUCache(5); + constructor( @IInlineChatService private readonly _inlineChatService: IInlineChatService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @@ -228,41 +239,66 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { ) { // MARK: register fake chat agent - - const that = this; - const agentData: IChatAgentData = { - id: 'editor', - name: 'editor', - extensionId: nullExtensionDescription.identifier, - isDefault: true, - locations: [ChatAgentLocation.Editor], - get slashCommands(): IChatAgentCommand[] { - // HACK@jrieken - // find the active session and return its slash commands - let candidate: Session | undefined; - for (const data of that._sessions.values()) { - if (data.editor.hasWidgetFocus()) { - candidate = data.session; - break; + const addOrRemoveBridgeAgent = () => { + const that = this; + const agentData: IChatAgentData = { + id: _bridgeAgentId, + name: 'editor', + extensionId: nullExtensionDescription.identifier, + isDefault: true, + locations: [ChatAgentLocation.Editor], + get slashCommands(): IChatAgentCommand[] { + // HACK@jrieken + // find the active session and return its slash commands + let candidate: Session | undefined; + for (const data of that._sessions.values()) { + if (data.editor.hasWidgetFocus()) { + candidate = data.session; + break; + } } + if (!candidate || !candidate.session.slashCommands) { + return []; + } + return candidate.session.slashCommands.map(c => { + return { + name: c.command, + description: c.detail ?? '', + } satisfies IChatAgentCommand; + }); + }, + defaultImplicitVariables: [], + metadata: { + isSticky: false, + themeIcon: Codicon.copilot, + }, + }; + + let otherEditorAgent: IChatAgentData | undefined; + let myEditorAgent: IChatAgentData | undefined; + + for (const candidate of this._chatAgentService.getActivatedAgents()) { + if (!myEditorAgent && candidate.id === agentData.id) { + myEditorAgent = candidate; + } else if (!otherEditorAgent && candidate.isDefault && candidate.locations.includes(ChatAgentLocation.Editor)) { + otherEditorAgent = candidate; } - if (!candidate || !candidate.session.slashCommands) { - return []; - } - return candidate.session.slashCommands.map(c => { - return { - name: c.command, - description: c.detail ?? '', - } satisfies IChatAgentCommand; - }); - }, - defaultImplicitVariables: [], - metadata: { - isSticky: false, - themeIcon: Codicon.copilot, - }, + } + + if (otherEditorAgent) { + brigdeAgent.clear(); + _logService.debug(`REMOVED bridge agent "${agentData.id}", found "${otherEditorAgent.id}"`); + } else if (!myEditorAgent) { + brigdeAgent.value = this._chatAgentService.registerDynamicAgent(agentData, this._instaService.createInstance(BridgeAgent, agentData, this._sessions, data => { + this._lastResponsesFromBridgeAgent.set(data.id, data.response); + })); + _logService.debug(`ADDED bridge agent "${agentData.id}"`); + } }; - this._store.add(this._chatAgentService.registerDynamicAgent(agentData, this._instaService.createInstance(BridgeAgent, agentData, this._sessions))); + + this._store.add(this._chatAgentService.onDidChangeAgents(() => addOrRemoveBridgeAgent())); + const brigdeAgent = this._store.add(new MutableDisposable()); + addOrRemoveBridgeAgent(); // MARK: register fake chat provider @@ -347,6 +383,80 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { this._logService.trace(`[IE] creating NEW session for ${editor.getId()}, ${provider.extensionId}`); + const lastResponseListener = store.add(new MutableDisposable()); + store.add(chatModel.onDidChange(e => { + if (e.kind !== 'addRequest' || !e.request.response) { + return; + } + + const modelAltVersionIdNow = textModel.getAlternativeVersionId(); + + const { response } = e.request; + + lastResponseListener.value = response.onDidChange(() => { + + if (!response.isComplete) { + return; + } + + lastResponseListener.clear(); // ONCE + + let inlineResponse: ErrorResponse | EmptyResponse | ReplyResponse; + if (response.agent?.id === _bridgeAgentId) { + // use result that was provided by + inlineResponse = this._lastResponsesFromBridgeAgent.get(response.requestId) ?? new ErrorResponse(new Error('Missing Response')); + this._lastResponsesFromBridgeAgent.delete(response.requestId); + + } else { + // make an artificial response from the ChatResponseModel + if (response.isCanceled) { + // error: cancelled + inlineResponse = new ErrorResponse(new CancellationError()); + } else if (response.result?.errorDetails) { + // error: "real" error + inlineResponse = new ErrorResponse(new Error(response.result.errorDetails.message)); + } else if (response.response.value.length === 0) { + // epmty response + inlineResponse = new EmptyResponse(); + } else { + // replay response + const markdownContent = new MarkdownString(); + const raw: IInlineChatBulkEditResponse = { + id: Math.random(), + type: InlineChatResponseType.BulkEdit, + message: markdownContent, + edits: { edits: [] }, + }; + for (const item of response.response.value) { + if (item.kind === 'markdownContent') { + markdownContent.value += item.content.value; + } else if (item.kind === 'textEdit') { + for (const edit of item.edits) { + raw.edits.edits.push({ + resource: session.textModelN.uri, + textEdit: edit, + versionId: undefined + }); + } + } + } + + inlineResponse = this._instaService.createInstance( + ReplyResponse, + raw, + markdownContent, + session.textModelN.uri, + modelAltVersionIdNow, + [], + e.request.id + ); + } + } + + session.addExchange(new SessionExchange(session.lastInput!, inlineResponse)); + }); + })); + store.add(this._chatService.onDidPerformUserAction(e => { if (e.sessionId !== chatModel.sessionId || e.action.kind !== 'vote') { return; diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts index f066fd6d133..d74482948f1 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { URI } from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; import { mock } from 'vs/base/test/common/mock'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; @@ -12,7 +11,6 @@ import { TestDiffProviderFactoryService } from 'vs/editor/test/browser/diff/test import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffProviderFactoryService } from 'vs/editor/browser/widget/diffEditor/diffProviderFactoryService'; import { Range } from 'vs/editor/common/core/range'; -import { ILanguageService } from 'vs/editor/common/languages/language'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; import { instantiateTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; @@ -30,11 +28,10 @@ import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/brows import { IChatAccessibilityService, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { IInlineChatSavingService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSavingService'; -import { HunkState, ReplyResponse, Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +import { HunkState, Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionService'; import { InlineChatSessionServiceImpl } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl'; -import { EditMode, IInlineChatEditResponse, IInlineChatService, InlineChatResponseType, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { EditMode, IInlineChatService, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; import { CancellationToken } from 'vs/base/common/cancellation'; import { assertType } from 'vs/base/common/types'; @@ -61,44 +58,6 @@ import { TestExtensionService, TestContextService } from 'vs/workbench/test/comm import { IChatAgentService, ChatAgentService, ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents'; import { MockChatContributionService } from 'vs/workbench/contrib/chat/test/common/mockChatContributionService'; - -suite('ReplyResponse', function () { - - ensureNoDisposablesAreLeakedInTestSuite(); - - test('Inline chat widget should not contain Accept and Discard buttons for responses which do not include changes. #3143', async function () { - const textFileService = new class extends mock() { }; - const languageService = new class extends mock() { }; - - const message = { value: 'hello' }; - const emptyMessage = { value: '' }; - - const raw: IInlineChatEditResponse = { - type: InlineChatResponseType.EditorEdit, - edits: [], - message: emptyMessage, - id: 1234 - }; - - { - const res2 = new ReplyResponse(raw, emptyMessage, URI.parse('test:uri'), 1, [], '1', textFileService, languageService); - assert.strictEqual(res2.responseType, InlineChatResponseTypes.Empty); - } - { - const res1 = new ReplyResponse({ ...raw, message }, message, URI.parse('test:uri'), 1, [], '1', textFileService, languageService); - assert.strictEqual(res1.responseType, InlineChatResponseTypes.OnlyMessages); - } - { - const res3 = new ReplyResponse({ ...raw, edits: [{ text: 'EDIT', range: new Range(1, 1, 1, 1) }] }, emptyMessage, URI.parse('test:uri'), 1, [], '1', textFileService, languageService); - assert.strictEqual(res3.responseType, InlineChatResponseTypes.OnlyEdits); - } - { - const res4 = new ReplyResponse({ ...raw, edits: [{ text: 'EDIT', range: new Range(1, 1, 1, 1) }], message }, message, URI.parse('test:uri'), 1, [], '1', textFileService, languageService); - assert.strictEqual(res4.responseType, InlineChatResponseTypes.Mixed); - } - }); -}); - suite('InlineChatSession', function () { const store = new DisposableStore();