diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts index 561f040dedb..1d1da2658c3 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import 'mocha'; -import { CancellationToken, chat, ChatAgentRequest, ChatVariableLevel, CompletionItemKind, Disposable, interactive, InteractiveProgress, InteractiveRequest, InteractiveResponseForProgress, InteractiveSession, InteractiveSessionState, Progress, ProviderResult } from 'vscode'; +import { CancellationToken, chat, ChatAgentRequest, ChatVariableLevel, Disposable, interactive, InteractiveSession, ProviderResult } from 'vscode'; import { assertNoRpc, closeAllEditors, DeferredPromise, disposeAll } from '../utils'; suite('chat', () => { @@ -22,24 +22,12 @@ suite('chat', () => { function getDeferredForRequest(): DeferredPromise { disposables.push(interactive.registerInteractiveSessionProvider('provider', { - prepareSession: (_initialState: InteractiveSessionState | undefined, _token: CancellationToken): ProviderResult => { + prepareSession: (_token: CancellationToken): ProviderResult => { return { requester: { name: 'test' }, responder: { name: 'test' }, }; }, - - provideResponseWithProgress: (_request: InteractiveRequest, _progress: Progress, _token: CancellationToken): ProviderResult => { - return null; - }, - - provideSlashCommands: (_session, _token) => { - return [{ command: 'hello', title: 'Hello', kind: CompletionItemKind.Text }]; - }, - - removeRequest: (_session: InteractiveSession, _requestId: string): void => { - throw new Error('Function not implemented.'); - } })); const deferred = new DeferredPromise(); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/interactive.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/interactive.test.ts deleted file mode 100644 index b6b5623a7fb..00000000000 --- a/extensions/vscode-api-tests/src/singlefolder-tests/interactive.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import 'mocha'; -import { CancellationToken, CompletionItemKind, Disposable, interactive, InteractiveProgress, InteractiveRequest, InteractiveResponseForProgress, InteractiveSession, InteractiveSessionState, Progress, ProviderResult } from 'vscode'; -import { assertNoRpc, closeAllEditors, DeferredPromise, disposeAll } from '../utils'; - -suite('InteractiveSessionProvider', () => { - let disposables: Disposable[] = []; - setup(async () => { - disposables = []; - }); - - teardown(async function () { - assertNoRpc(); - await closeAllEditors(); - disposeAll(disposables); - }); - - function getDeferredForRequest(): DeferredPromise { - const deferred = new DeferredPromise(); - disposables.push(interactive.registerInteractiveSessionProvider('provider', { - prepareSession: (_initialState: InteractiveSessionState | undefined, _token: CancellationToken): ProviderResult => { - return { - requester: { name: 'test' }, - responder: { name: 'test' }, - }; - }, - - provideResponseWithProgress: (request: InteractiveRequest, _progress: Progress, _token: CancellationToken): ProviderResult => { - deferred.complete(request); - return null; - }, - - provideSlashCommands: (_session, _token) => { - return [{ command: 'hello', title: 'Hello', kind: CompletionItemKind.Text }]; - }, - - removeRequest: (_session: InteractiveSession, _requestId: string): void => { - throw new Error('Function not implemented.'); - } - })); - return deferred; - } - - test('plain text query', async () => { - const deferred = getDeferredForRequest(); - interactive.sendInteractiveRequestToProvider('provider', { message: 'hello' }); - const lastResult = await deferred.p; - assert.strictEqual(lastResult.message, 'hello'); - }); - - test('slash command', async () => { - const deferred = getDeferredForRequest(); - interactive.sendInteractiveRequestToProvider('provider', { message: '/hello' }); - const lastResult = await deferred.p; - assert.strictEqual(lastResult.message, '/hello'); - }); -}); diff --git a/src/vs/base/common/lifecycle.ts b/src/vs/base/common/lifecycle.ts index 781f4e6eb21..4e5aed3d277 100644 --- a/src/vs/base/common/lifecycle.ts +++ b/src/vs/base/common/lifecycle.ts @@ -781,6 +781,10 @@ export class DisposableMap implements ID return this._store.keys(); } + values(): IterableIterator { + return this._store.values(); + } + [Symbol.iterator](): IterableIterator<[K, V]> { return this._store[Symbol.iterator](); } diff --git a/src/vs/workbench/api/browser/extensionHost.contribution.ts b/src/vs/workbench/api/browser/extensionHost.contribution.ts index b34ef1570cc..a232035c72b 100644 --- a/src/vs/workbench/api/browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/browser/extensionHost.contribution.ts @@ -20,7 +20,6 @@ import { StatusBarItemsExtensionPoint } from 'vs/workbench/api/browser/statusBar import './mainThreadLocalization'; import './mainThreadBulkEdits'; import './mainThreadChatProvider'; -import './mainThreadChatAgents'; import './mainThreadChatAgents2'; import './mainThreadChatVariables'; import './mainThreadCodeInsets'; diff --git a/src/vs/workbench/api/browser/mainThreadChat.ts b/src/vs/workbench/api/browser/mainThreadChat.ts index 23f0d00ef32..476bf6da65c 100644 --- a/src/vs/workbench/api/browser/mainThreadChat.ts +++ b/src/vs/workbench/api/browser/mainThreadChat.ts @@ -3,31 +3,23 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DeferredPromise } from 'vs/base/common/async'; import { Emitter } from 'vs/base/common/event'; -import { IMarkdownString } from 'vs/base/common/htmlContent'; import { Disposable, DisposableMap } from 'vs/base/common/lifecycle'; -import { revive } from 'vs/base/common/marshalling'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { ExtHostChatShape, ExtHostContext, IChatRequestDto, IChatResponseProgressDto, ILocationDto, MainContext, MainThreadChatShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostChatShape, ExtHostContext, MainContext, MainThreadChatShape } from 'vs/workbench/api/common/extHost.protocol'; import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; -import { isCompleteInteractiveProgressTreeData } from 'vs/workbench/contrib/chat/common/chatModel'; -import { IChat, IChatDynamicRequest, IChatProgress, IChatResponse, IChatResponseProgressFileTreeData, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatDynamicRequest, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; @extHostNamedCustomer(MainContext.MainThreadChat) export class MainThreadChat extends Disposable implements MainThreadChatShape { private readonly _providerRegistrations = this._register(new DisposableMap()); - private readonly _activeRequestProgressCallbacks = new Map (DeferredPromise | void)>(); private readonly _stateEmitters = new Map>(); private readonly _proxy: ExtHostChatShape; - private _responsePartHandlePool = 0; - private readonly _activeResponsePartPromises = new Map>(); - constructor( extHostContext: IExtHostContext, @IChatService private readonly _chatService: IChatService, @@ -64,8 +56,8 @@ export class MainThreadChat extends Disposable implements MainThreadChatShape { const unreg = this._chatService.registerProvider({ id, displayName: registration.label, - prepareSession: async (initialState, token) => { - const session = await this._proxy.$prepareChat(handle, initialState, token); + prepareSession: async (token) => { + const session = await this._proxy.$prepareChat(handle, token); if (!session) { return undefined; } @@ -75,14 +67,13 @@ export class MainThreadChat extends Disposable implements MainThreadChatShape { const emitter = new Emitter(); this._stateEmitters.set(session.id, emitter); - return { + return { id: session.id, requesterUsername: session.requesterUsername, requesterAvatarIconUri: URI.revive(session.requesterAvatarIconUri), responderUsername: session.responderUsername, responderAvatarIconUri, inputPlaceholder: session.inputPlaceholder, - onDidChangeState: emitter.event, dispose: () => { emitter.dispose(); this._stateEmitters.delete(session.id); @@ -90,87 +81,17 @@ export class MainThreadChat extends Disposable implements MainThreadChatShape { } }; }, - provideReply: async (request, progress, token) => { - const id = `${handle}_${request.session.id}`; - this._activeRequestProgressCallbacks.set(id, progress); - try { - const requestDto: IChatRequestDto = { - message: request.message, - variables: request.variables - }; - const dto = await this._proxy.$provideReply(handle, request.session.id, requestDto, token); - return { - session: request.session, - ...dto - }; - } finally { - this._activeRequestProgressCallbacks.delete(id); - } - }, provideWelcomeMessage: (token) => { return this._proxy.$provideWelcomeMessage(handle, token); }, provideSampleQuestions: (token) => { return this._proxy.$provideSampleQuestions(handle, token); }, - provideSlashCommands: (session, token) => { - return this._proxy.$provideSlashCommands(handle, session.id, token); - }, - provideFollowups: (session, token) => { - return this._proxy.$provideFollowups(handle, session.id, token); - }, - removeRequest: (session, requestId) => { - return this._proxy.$removeRequest(handle, session.id, requestId); - } }); this._providerRegistrations.set(handle, unreg); } - async $acceptResponseProgress(handle: number, sessionId: number, progress: IChatResponseProgressDto, responsePartHandle?: number): Promise { - const id = `${handle}_${sessionId}`; - - if ('placeholder' in progress) { - const responsePartId = `${id}_${++this._responsePartHandlePool}`; - const deferredContentPromise = new DeferredPromise(); - this._activeResponsePartPromises.set(responsePartId, deferredContentPromise); - this._activeRequestProgressCallbacks.get(id)?.({ ...progress, resolvedContent: deferredContentPromise.p }); - return this._responsePartHandlePool; - } else if (responsePartHandle) { - // Complete an existing deferred promise with resolved content - const responsePartId = `${id}_${responsePartHandle}`; - const deferredContentPromise = this._activeResponsePartPromises.get(responsePartId); - if (deferredContentPromise && isCompleteInteractiveProgressTreeData(progress)) { - const withRevivedUris = revive<{ treeData: IChatResponseProgressFileTreeData }>(progress); - deferredContentPromise.complete(withRevivedUris); - this._activeResponsePartPromises.delete(responsePartId); - } else if (deferredContentPromise && 'content' in progress) { - deferredContentPromise.complete(progress.content); - this._activeResponsePartPromises.delete(responsePartId); - } - return; - } - - // No need to support standalone tree data that's not attached to a placeholder in API - if (isCompleteInteractiveProgressTreeData(progress)) { - return; - } - - // TS won't let us change the type of `progress` - let revivedProgress: IChatProgress; - if ('documents' in progress) { - revivedProgress = { documents: revive(progress.documents) }; - } else if ('reference' in progress) { - revivedProgress = revive<{ reference: UriComponents | ILocationDto }>(progress); - } else if ('inlineReference' in progress) { - revivedProgress = revive<{ inlineReference: UriComponents | ILocationDto; name?: string }>(progress); - } else { - revivedProgress = progress; - } - - this._activeRequestProgressCallbacks.get(id)?.(revivedProgress); - } - async $acceptChatState(sessionId: number, state: any): Promise { this._stateEmitters.get(sessionId)?.fire(state); } diff --git a/src/vs/workbench/api/browser/mainThreadChatAgents.ts b/src/vs/workbench/api/browser/mainThreadChatAgents.ts deleted file mode 100644 index 150da6ae90c..00000000000 --- a/src/vs/workbench/api/browser/mainThreadChatAgents.ts +++ /dev/null @@ -1,75 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { DisposableMap } from 'vs/base/common/lifecycle'; -import { revive } from 'vs/base/common/marshalling'; -import { IProgress } from 'vs/platform/progress/common/progress'; -import { ExtHostChatAgentsShape, ExtHostContext, MainContext, MainThreadChatAgentsShape } from 'vs/workbench/api/common/extHost.protocol'; -import { IChatAgentCommand, IChatAgentMetadata, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { IChatProgress } from 'vs/workbench/contrib/chat/common/chatService'; -import { IChatSlashFragment } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; -import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; - - -@extHostNamedCustomer(MainContext.MainThreadChatAgents) -export class MainThreadChatAgents implements MainThreadChatAgentsShape { - - private readonly _agents = new DisposableMap; - private readonly _pendingProgress = new Map>(); - private readonly _proxy: ExtHostChatAgentsShape; - - constructor( - extHostContext: IExtHostContext, - @IChatAgentService private readonly _chatAgentService: IChatAgentService - ) { - this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatAgents); - } - - $unregisterAgent(handle: number): void { - this._agents.deleteAndDispose(handle); - } - - dispose(): void { - this._agents.clearAndDisposeAll(); - } - - $registerAgent(handle: number, name: string, metadata: IChatAgentMetadata & { subCommands: IChatAgentCommand[] }): void { - const d = this._chatAgentService.registerAgent({ - id: name, - metadata: revive(metadata), - invoke: async (request, progress, history, token) => { - const requestId = Math.random(); - this._pendingProgress.set(requestId, { report: progress }); - try { - const message = request.command ? `/${request.command} ${request.message}` : request.message; - const result = await this._proxy.$invokeAgent(handle, requestId, message, { history }, token); - return { - followUp: result?.followUp ?? [], - }; - } finally { - this._pendingProgress.delete(requestId); - } - }, - async provideSlashCommands() { - return metadata.subCommands; - }, - }); - this._agents.set(handle, d); - } - - async $handleProgressChunk(requestId: number, chunk: IChatSlashFragment): Promise { - // An extra step because TS really struggles with type inference in the Revived generic parameter? - const revived = revive(chunk); - if (typeof revived.content === 'string') { - this._pendingProgress.get(requestId)?.report({ content: revived.content }); - } else { - this._pendingProgress.get(requestId)?.report(revived.content); - } - } - - $unregisterCommand(handle: number): void { - this._agents.deleteAndDispose(handle); - } -} diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 9a38446fbf1..e1fd00232ec 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -6,110 +6,109 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation'; import * as errors from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; +import { combinedDisposable } from 'vs/base/common/lifecycle'; +import { Schemas } from 'vs/base/common/network'; import Severity from 'vs/base/common/severity'; import { URI } from 'vs/base/common/uri'; import { TextEditorCursorStyle } from 'vs/editor/common/config/editorOptions'; -import { OverviewRulerLane } from 'vs/editor/common/model'; -import * as languageConfiguration from 'vs/editor/common/languages/languageConfiguration'; import { score } from 'vs/editor/common/languageSelector'; +import * as languageConfiguration from 'vs/editor/common/languages/languageConfiguration'; +import { OverviewRulerLane } from 'vs/editor/common/model'; +import { ExtensionIdentifierSet, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import * as files from 'vs/platform/files/common/files'; -import { ExtHostContext, MainContext, CandidatePortSource, ExtHostLogLevelServiceShape } from 'vs/workbench/api/common/extHost.protocol'; -import { UIKind } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { ILogService, ILoggerService, LogLevel } from 'vs/platform/log/common/log'; +import { matchesScheme } from 'vs/platform/opener/common/opener'; +import { getRemoteName } from 'vs/platform/remote/common/remoteHosts'; +import { TelemetryTrustedValue } from 'vs/platform/telemetry/common/telemetryUtils'; +import { EditSessionIdentityMatch } from 'vs/platform/workspace/common/editSessions'; +import { CandidatePortSource, ExtHostContext, ExtHostLogLevelServiceShape, MainContext } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostRelatedInformation } from 'vs/workbench/api/common/extHostAiRelatedInformation'; import { ExtHostApiCommands } from 'vs/workbench/api/common/extHostApiCommands'; +import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; +import { ExtHostAuthentication } from 'vs/workbench/api/common/extHostAuthentication'; +import { ExtHostBulkEdits } from 'vs/workbench/api/common/extHostBulkEdits'; +import { ExtHostChat } from 'vs/workbench/api/common/extHostChat'; +import { ExtHostChatAgents2 } from 'vs/workbench/api/common/extHostChatAgents2'; +import { ExtHostChatProvider } from 'vs/workbench/api/common/extHostChatProvider'; +import { ExtHostChatVariables } from 'vs/workbench/api/common/extHostChatVariables'; import { ExtHostClipboard } from 'vs/workbench/api/common/extHostClipboard'; +import { ExtHostEditorInsets } from 'vs/workbench/api/common/extHostCodeInsets'; import { IExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { createExtHostComments } from 'vs/workbench/api/common/extHostComments'; import { ExtHostConfigProvider, IExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration'; +import { ExtHostCustomEditors } from 'vs/workbench/api/common/extHostCustomEditors'; +import { IExtHostDebugService } from 'vs/workbench/api/common/extHostDebugService'; +import { IExtHostDecorations } from 'vs/workbench/api/common/extHostDecorations'; import { ExtHostDiagnostics } from 'vs/workbench/api/common/extHostDiagnostics'; import { ExtHostDialogs } from 'vs/workbench/api/common/extHostDialogs'; import { ExtHostDocumentContentProvider } from 'vs/workbench/api/common/extHostDocumentContentProviders'; import { ExtHostDocumentSaveParticipant } from 'vs/workbench/api/common/extHostDocumentSaveParticipant'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import { IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; +import { IExtHostEditorTabs } from 'vs/workbench/api/common/extHostEditorTabs'; +import { ExtHostAiEmbeddingVector } from 'vs/workbench/api/common/extHostEmbeddingVector'; import { Extension, IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; import { ExtHostFileSystem } from 'vs/workbench/api/common/extHostFileSystem'; +import { IExtHostConsumerFileSystem } from 'vs/workbench/api/common/extHostFileSystemConsumer'; import { ExtHostFileSystemEventService, FileSystemWatcherCreateOptions } from 'vs/workbench/api/common/extHostFileSystemEventService'; +import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; +import { ExtHostInteractiveEditor } from 'vs/workbench/api/common/extHostInlineChat'; +import { ExtHostInteractive } from 'vs/workbench/api/common/extHostInteractive'; +import { ExtHostIssueReporter } from 'vs/workbench/api/common/extHostIssueReporter'; +import { ExtHostLabelService } from 'vs/workbench/api/common/extHostLabelService'; import { ExtHostLanguageFeatures } from 'vs/workbench/api/common/extHostLanguageFeatures'; import { ExtHostLanguages } from 'vs/workbench/api/common/extHostLanguages'; +import { IExtHostLocalizationService } from 'vs/workbench/api/common/extHostLocalizationService'; +import { IExtHostManagedSockets } from 'vs/workbench/api/common/extHostManagedSockets'; import { ExtHostMessageService } from 'vs/workbench/api/common/extHostMessageService'; +import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook'; +import { ExtHostNotebookDocumentSaveParticipant } from 'vs/workbench/api/common/extHostNotebookDocumentSaveParticipant'; +import { ExtHostNotebookDocuments } from 'vs/workbench/api/common/extHostNotebookDocuments'; +import { ExtHostNotebookEditors } from 'vs/workbench/api/common/extHostNotebookEditors'; +import { ExtHostNotebookKernels } from 'vs/workbench/api/common/extHostNotebookKernels'; +import { ExtHostNotebookRenderers } from 'vs/workbench/api/common/extHostNotebookRenderers'; import { IExtHostOutputService } from 'vs/workbench/api/common/extHostOutput'; +import { ExtHostProfileContentHandlers } from 'vs/workbench/api/common/extHostProfileContentHandler'; import { ExtHostProgress } from 'vs/workbench/api/common/extHostProgress'; +import { ExtHostQuickDiff } from 'vs/workbench/api/common/extHostQuickDiff'; import { createExtHostQuickOpen } from 'vs/workbench/api/common/extHostQuickOpen'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { ExtHostSCM } from 'vs/workbench/api/common/extHostSCM'; +import { IExtHostSearch } from 'vs/workbench/api/common/extHostSearch'; +import { IExtHostSecretState } from 'vs/workbench/api/common/extHostSecretState'; +import { ExtHostShare } from 'vs/workbench/api/common/extHostShare'; +import { ExtHostSpeech } from 'vs/workbench/api/common/extHostSpeech'; import { ExtHostStatusBar } from 'vs/workbench/api/common/extHostStatusBar'; import { IExtHostStorage } from 'vs/workbench/api/common/extHostStorage'; +import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; +import { IExtHostTask } from 'vs/workbench/api/common/extHostTask'; +import { ExtHostTelemetryLogger, IExtHostTelemetry, isNewAppInstall } from 'vs/workbench/api/common/extHostTelemetry'; import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService'; +import { ExtHostTesting } from 'vs/workbench/api/common/extHostTesting'; import { ExtHostEditors } from 'vs/workbench/api/common/extHostTextEditors'; +import { ExtHostTheming } from 'vs/workbench/api/common/extHostTheming'; +import { ExtHostTimeline } from 'vs/workbench/api/common/extHostTimeline'; import { ExtHostTreeViews } from 'vs/workbench/api/common/extHostTreeViews'; +import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; -import { TelemetryTrustedValue } from 'vs/platform/telemetry/common/telemetryUtils'; +import { ExtHostUriOpeners } from 'vs/workbench/api/common/extHostUriOpener'; +import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; import { ExtHostUrls } from 'vs/workbench/api/common/extHostUrls'; import { ExtHostWebviews } from 'vs/workbench/api/common/extHostWebview'; +import { ExtHostWebviewPanels } from 'vs/workbench/api/common/extHostWebviewPanels'; +import { ExtHostWebviewViews } from 'vs/workbench/api/common/extHostWebviewView'; import { IExtHostWindow } from 'vs/workbench/api/common/extHostWindow'; import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; -import { ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyIdentifier'; -import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; -import type * as vscode from 'vscode'; -import { ExtensionIdentifierSet, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { ExtHostEditorInsets } from 'vs/workbench/api/common/extHostCodeInsets'; -import { ExtHostLabelService } from 'vs/workbench/api/common/extHostLabelService'; -import { getRemoteName } from 'vs/platform/remote/common/remoteHosts'; -import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IExtHostDecorations } from 'vs/workbench/api/common/extHostDecorations'; -import { IExtHostTask } from 'vs/workbench/api/common/extHostTask'; -import { IExtHostDebugService } from 'vs/workbench/api/common/extHostDebugService'; -import { IExtHostSearch } from 'vs/workbench/api/common/extHostSearch'; -import { ILoggerService, ILogService, LogLevel } from 'vs/platform/log/common/log'; -import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; -import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; -import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; -import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook'; -import { ExtHostTheming } from 'vs/workbench/api/common/extHostTheming'; -import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService'; -import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; -import { ExtHostAuthentication } from 'vs/workbench/api/common/extHostAuthentication'; -import { ExtHostTimeline } from 'vs/workbench/api/common/extHostTimeline'; -import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; -import { IExtHostConsumerFileSystem } from 'vs/workbench/api/common/extHostFileSystemConsumer'; -import { ExtHostWebviewViews } from 'vs/workbench/api/common/extHostWebviewView'; -import { ExtHostCustomEditors } from 'vs/workbench/api/common/extHostCustomEditors'; -import { ExtHostWebviewPanels } from 'vs/workbench/api/common/extHostWebviewPanels'; -import { ExtHostBulkEdits } from 'vs/workbench/api/common/extHostBulkEdits'; -import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo'; -import { ExtHostTesting } from 'vs/workbench/api/common/extHostTesting'; -import { ExtHostUriOpeners } from 'vs/workbench/api/common/extHostUriOpener'; -import { IExtHostSecretState } from 'vs/workbench/api/common/extHostSecretState'; -import { IExtHostEditorTabs } from 'vs/workbench/api/common/extHostEditorTabs'; -import { ExtHostTelemetryLogger, IExtHostTelemetry, isNewAppInstall } from 'vs/workbench/api/common/extHostTelemetry'; -import { ExtHostNotebookKernels } from 'vs/workbench/api/common/extHostNotebookKernels'; -import { TextSearchCompleteMessageType } from 'vs/workbench/services/search/common/searchExtTypes'; -import { ExtHostNotebookRenderers } from 'vs/workbench/api/common/extHostNotebookRenderers'; -import { Schemas } from 'vs/base/common/network'; -import { matchesScheme } from 'vs/platform/opener/common/opener'; -import { ExtHostNotebookEditors } from 'vs/workbench/api/common/extHostNotebookEditors'; -import { ExtHostNotebookDocuments } from 'vs/workbench/api/common/extHostNotebookDocuments'; -import { ExtHostInteractive } from 'vs/workbench/api/common/extHostInteractive'; -import { combinedDisposable } from 'vs/base/common/lifecycle'; -import { checkProposedApiEnabled, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { DebugConfigurationProviderTriggerKind } from 'vs/workbench/contrib/debug/common/debug'; -import { IExtHostLocalizationService } from 'vs/workbench/api/common/extHostLocalizationService'; -import { EditSessionIdentityMatch } from 'vs/platform/workspace/common/editSessions'; -import { ExtHostProfileContentHandlers } from 'vs/workbench/api/common/extHostProfileContentHandler'; -import { ExtHostQuickDiff } from 'vs/workbench/api/common/extHostQuickDiff'; -import { ExtHostChat } from 'vs/workbench/api/common/extHostChat'; -import { ExtHostInteractiveEditor } from 'vs/workbench/api/common/extHostInlineChat'; -import { ExtHostNotebookDocumentSaveParticipant } from 'vs/workbench/api/common/extHostNotebookDocumentSaveParticipant'; -import { ExtHostIssueReporter } from 'vs/workbench/api/common/extHostIssueReporter'; -import { IExtHostManagedSockets } from 'vs/workbench/api/common/extHostManagedSockets'; -import { ExtHostShare } from 'vs/workbench/api/common/extHostShare'; -import { ExtHostChatProvider } from 'vs/workbench/api/common/extHostChatProvider'; -import { ExtHostSpeech } from 'vs/workbench/api/common/extHostSpeech'; -import { ExtHostChatVariables } from 'vs/workbench/api/common/extHostChatVariables'; -import { ExtHostRelatedInformation } from 'vs/workbench/api/common/extHostAiRelatedInformation'; -import { ExtHostAiEmbeddingVector } from 'vs/workbench/api/common/extHostEmbeddingVector'; -import { ExtHostChatAgents } from 'vs/workbench/api/common/extHostChatAgents'; -import { ExtHostChatAgents2 } from 'vs/workbench/api/common/extHostChatAgents2'; +import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; +import { UIKind } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; +import { checkProposedApiEnabled, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; +import { ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyIdentifier'; +import { TextSearchCompleteMessageType } from 'vs/workbench/services/search/common/searchExtTypes'; +import type * as vscode from 'vscode'; export interface IExtensionRegistries { mine: ExtensionDescriptionRegistry; @@ -210,10 +209,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I rpcProtocol.set(ExtHostContext.ExtHostInteractive, new ExtHostInteractive(rpcProtocol, extHostNotebook, extHostDocumentsAndEditors, extHostCommands, extHostLogService)); const extHostInteractiveEditor = rpcProtocol.set(ExtHostContext.ExtHostInlineChat, new ExtHostInteractiveEditor(rpcProtocol, extHostCommands, extHostDocuments, extHostLogService)); const extHostChatProvider = rpcProtocol.set(ExtHostContext.ExtHostChatProvider, new ExtHostChatProvider(rpcProtocol, extHostLogService)); - const extHostChatAgents = rpcProtocol.set(ExtHostContext.ExtHostChatAgents, new ExtHostChatAgents(rpcProtocol, extHostChatProvider, extHostLogService)); const extHostChatAgents2 = rpcProtocol.set(ExtHostContext.ExtHostChatAgents2, new ExtHostChatAgents2(rpcProtocol, extHostChatProvider, extHostLogService)); const extHostChatVariables = rpcProtocol.set(ExtHostContext.ExtHostChatVariables, new ExtHostChatVariables(rpcProtocol)); - const extHostChat = rpcProtocol.set(ExtHostContext.ExtHostChat, new ExtHostChat(rpcProtocol, extHostLogService)); + const extHostChat = rpcProtocol.set(ExtHostContext.ExtHostChat, new ExtHostChat(rpcProtocol)); const extHostAiRelatedInformation = rpcProtocol.set(ExtHostContext.ExtHostAiRelatedInformation, new ExtHostRelatedInformation(rpcProtocol)); const extHostAiEmbeddingVector = rpcProtocol.set(ExtHostContext.ExtHostAiEmbeddingVector, new ExtHostAiEmbeddingVector(rpcProtocol)); const extHostIssueReporter = rpcProtocol.set(ExtHostContext.ExtHostIssueReporter, new ExtHostIssueReporter(rpcProtocol)); @@ -1382,10 +1380,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension, 'chatAgents2'); return extHostChatAgents2.createChatAgent(extension, name, handler); }, - registerAgent(name: string, agent: vscode.ChatAgent, metadata: vscode.ChatAgentMetadata) { - checkProposedApiEnabled(extension, 'chatAgents'); - return extHostChatAgents.registerAgent(extension.identifier, name, agent, metadata); - } }; // namespace: speech diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 4da9a5a9267..ba221bd753f 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -52,8 +52,7 @@ import { IRevealOptions, ITreeItem, IViewBadge } from 'vs/workbench/common/views import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { IChatAgentCommand, IChatAgentMetadata, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatMessage, IChatResponseFragment, IChatResponseProviderMetadata } from 'vs/workbench/contrib/chat/common/chatProvider'; -import { IChatAgentDetection, IChatDynamicRequest, IChatFollowup, IChatReplyFollowup, IChatResponseErrorDetails, IChatUserActionEvent, ISlashCommand, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; -import { IChatSlashFragment } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; +import { IChatAgentDetection, IChatDynamicRequest, IChatFollowup, IChatReplyFollowup, IChatResponseErrorDetails, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue, IChatVariableData } from 'vs/workbench/contrib/chat/common/chatVariables'; import { DebugConfigurationProviderTriggerKind, IAdapterDescriptor, IConfig, IDebugSessionReplMode } from 'vs/workbench/contrib/debug/common/debug'; import { IInlineChatBulkEditResponse, IInlineChatEditResponse, IInlineChatMessageResponse, IInlineChatProgressItem, IInlineChatRequest, IInlineChatSession, InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; @@ -63,6 +62,7 @@ import { ICellExecutionComplete, ICellExecutionStateUpdate } from 'vs/workbench/ import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { InputValidationType } from 'vs/workbench/contrib/scm/common/scm'; import { IWorkspaceSymbol, NotebookPriorityInfo } from 'vs/workbench/contrib/search/common/search'; +import { IRawClosedNotebookFileMatch } from 'vs/workbench/contrib/search/common/searchNotebookHelpers'; import { ISpeechProviderMetadata, ISpeechToTextEvent } from 'vs/workbench/contrib/speech/common/speechService'; import { CoverageDetails, ExtensionRunTestsRequest, ICallProfileRunHandler, IFileCoverage, ISerializedTestResults, IStartControllerTests, ITestItem, ITestMessage, ITestRunProfile, ITestRunTask, ResolvedTestRunRequest, TestResultState, TestsDiffOp } from 'vs/workbench/contrib/testing/common/testTypes'; import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor } from 'vs/workbench/contrib/timeline/common/timeline'; @@ -80,7 +80,6 @@ import { CandidatePort } from 'vs/workbench/services/remote/common/tunnelModel'; import { ITextQueryBuilderOptions } from 'vs/workbench/services/search/common/queryBuilder'; import * as search from 'vs/workbench/services/search/common/search'; import { ISaveProfileResult } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; -import { IRawClosedNotebookFileMatch } from 'vs/workbench/contrib/search/common/searchNotebookHelpers'; export interface IWorkspaceData extends IStaticWorkspaceData { folders: { uri: UriComponents; name: string; index: number }[]; @@ -1166,12 +1165,6 @@ export interface ExtHostChatProviderShape { $handleResponseFragment(requestId: number, chunk: IChatResponseFragment): Promise; } -export interface MainThreadChatAgentsShape extends IDisposable { - $registerAgent(handle: number, name: string, metadata: IChatAgentMetadata & { subCommands: IChatAgentCommand[] }): void; - $unregisterAgent(handle: number): void; - $handleProgressChunk(requestId: number, chunk: IChatSlashFragment): Promise; -} - export interface IExtensionChatAgentMetadata extends Dto { hasSlashCommands?: boolean; hasFollowup?: boolean; @@ -1184,10 +1177,6 @@ export interface MainThreadChatAgentsShape2 extends IDisposable { $handleProgressChunk(requestId: string, chunk: IChatResponseProgressDto, responsePartHandle?: number): Promise; } -export interface ExtHostChatAgentsShape { - $invokeAgent(handle: number, requestId: number, prompt: string, context: { history: IChatMessage[] }, token: CancellationToken): Promise; -} - export interface ExtHostChatAgentsShape2 { $invokeAgent(handle: number, sessionId: string, requestId: string, request: IChatAgentRequest, context: { history: IChatMessage[] }, token: CancellationToken): Promise; $provideSlashCommands(handle: number, token: CancellationToken): Promise; @@ -1263,7 +1252,6 @@ export type IDocumentContextDto = { export type IChatResponseProgressDto = | { content: string | IMarkdownString } - | { requestId: string } | { placeholder: string } | { treeData: IChatResponseProgressFileTreeData } | { documents: IDocumentContextDto[] } @@ -1276,18 +1264,13 @@ export interface MainThreadChatShape extends IDisposable { $acceptChatState(sessionId: number, state: any): Promise; $sendRequestToProvider(providerId: string, message: IChatDynamicRequest): void; $unregisterChatProvider(handle: number): Promise; - $acceptResponseProgress(handle: number, sessionId: number, progress: IChatResponseProgressDto, responsePartHandle?: number): Promise; $transferChatSession(sessionId: number, toWorkspace: UriComponents): void; } export interface ExtHostChatShape { - $prepareChat(handle: number, initialState: any, token: CancellationToken): Promise; + $prepareChat(handle: number, token: CancellationToken): Promise; $provideWelcomeMessage(handle: number, token: CancellationToken): Promise<(string | IMarkdownString | IChatReplyFollowup[])[] | undefined>; $provideSampleQuestions(handle: number, token: CancellationToken): Promise; - $provideFollowups(handle: number, sessionId: number, token: CancellationToken): Promise; - $provideReply(handle: number, sessionId: number, request: IChatRequestDto, token: CancellationToken): Promise; - $removeRequest(handle: number, sessionId: number, requestId: string): void; - $provideSlashCommands(handle: number, sessionId: number, token: CancellationToken): Promise; $releaseSession(sessionId: number): void; $onDidPerformUserAction(event: IChatUserActionEvent): Promise; } @@ -2721,7 +2704,6 @@ export const MainContext = { MainThreadAuthentication: createProxyIdentifier('MainThreadAuthentication'), MainThreadBulkEdits: createProxyIdentifier('MainThreadBulkEdits'), MainThreadChatProvider: createProxyIdentifier('MainThreadChatProvider'), - MainThreadChatAgents: createProxyIdentifier('MainThreadChatAgents'), MainThreadChatAgents2: createProxyIdentifier('MainThreadChatAgents2'), MainThreadChatVariables: createProxyIdentifier('MainThreadChatVariables'), MainThreadClipboard: createProxyIdentifier('MainThreadClipboard'), @@ -2844,7 +2826,6 @@ export const ExtHostContext = { ExtHostInteractive: createProxyIdentifier('ExtHostInteractive'), ExtHostInlineChat: createProxyIdentifier('ExtHostInlineChatShape'), ExtHostChat: createProxyIdentifier('ExtHostChat'), - ExtHostChatAgents: createProxyIdentifier('ExtHostChatAgents'), ExtHostChatAgents2: createProxyIdentifier('ExtHostChatAgents'), ExtHostChatVariables: createProxyIdentifier('ExtHostChatVariables'), ExtHostChatProvider: createProxyIdentifier('ExtHostChatProvider'), diff --git a/src/vs/workbench/api/common/extHostChat.ts b/src/vs/workbench/api/common/extHostChat.ts index d2ace71d47c..870c8a78848 100644 --- a/src/vs/workbench/api/common/extHostChat.ts +++ b/src/vs/workbench/api/common/extHostChat.ts @@ -3,19 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { raceCancellation } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter } from 'vs/base/common/event'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { Iterable } from 'vs/base/common/iterator'; import { toDisposable } from 'vs/base/common/lifecycle'; -import { StopWatch } from 'vs/base/common/stopwatch'; -import { localize } from 'vs/nls'; import { IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { ILogService } from 'vs/platform/log/common/log'; -import { ExtHostChatShape, IChatRequestDto, IChatResponseDto, IChatDto, IMainContext, MainContext, MainThreadChatShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostChatShape, IChatDto, IMainContext, MainContext, MainThreadChatShape } from 'vs/workbench/api/common/extHost.protocol'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; -import { IChatFollowup, IChatReplyFollowup, IChatUserActionEvent, ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatReplyFollowup, IChatUserActionEvent } from 'vs/workbench/contrib/chat/common/chatService'; import type * as vscode from 'vscode'; class ChatProviderWrapper { @@ -36,7 +32,6 @@ export class ExtHostChat implements ExtHostChatShape { private readonly _chatProvider = new Map>(); private readonly _chatSessions = new Map(); - // private readonly _providerResponsesByRequestId = new Map; sessionId: number }>(); private readonly _onDidPerformUserAction = new Emitter(); public readonly onDidPerformUserAction = this._onDidPerformUserAction.event; @@ -45,7 +40,6 @@ export class ExtHostChat implements ExtHostChatShape { constructor( mainContext: IMainContext, - private readonly logService: ILogService ) { this._proxy = mainContext.getProxy(MainContext.MainThreadChat); } @@ -75,13 +69,13 @@ export class ExtHostChat implements ExtHostChatShape { this._proxy.$sendRequestToProvider(providerId, message); } - async $prepareChat(handle: number, initialState: any, token: CancellationToken): Promise { + async $prepareChat(handle: number, token: CancellationToken): Promise { const entry = this._chatProvider.get(handle); if (!entry) { return undefined; } - const session = await entry.provider.prepareSession(initialState, token); + const session = await entry.provider.prepareSession(token); if (!session) { return undefined; } @@ -124,25 +118,6 @@ export class ExtHostChat implements ExtHostChatShape { }); } - async $provideFollowups(handle: number, sessionId: number, token: CancellationToken): Promise { - const entry = this._chatProvider.get(handle); - if (!entry) { - return undefined; - } - - const realSession = this._chatSessions.get(sessionId); - if (!realSession) { - return; - } - - if (!entry.provider.provideFollowups) { - return undefined; - } - - const rawFollowups = await entry.provider.provideFollowups(realSession, token); - return rawFollowups?.map(f => typeConvert.ChatFollowup.from(f)); - } - async $provideSampleQuestions(handle: number, token: CancellationToken): Promise { const entry = this._chatProvider.get(handle); if (!entry) { @@ -161,121 +136,6 @@ export class ExtHostChat implements ExtHostChatShape { return rawFollowups?.map(f => typeConvert.ChatReplyFollowup.from(f)); } - $removeRequest(handle: number, sessionId: number, requestId: string): void { - const entry = this._chatProvider.get(handle); - if (!entry) { - return; - } - - const realSession = this._chatSessions.get(sessionId); - if (!realSession) { - return; - } - - if (!entry.provider.removeRequest) { - return; - } - - entry.provider.removeRequest(realSession, requestId); - } - - async $provideReply(handle: number, sessionId: number, request: IChatRequestDto, token: CancellationToken): Promise { - const entry = this._chatProvider.get(handle); - if (!entry) { - return undefined; - } - - const realSession = this._chatSessions.get(sessionId); - if (!realSession) { - return; - } - - const requestObj: vscode.InteractiveRequest = { - session: realSession, - message: request.message, - variables: {} - }; - - if (request.variables) { - for (const key of Object.keys(request.variables)) { - requestObj.variables[key] = request.variables[key].map(typeConvert.ChatVariable.to); - } - } - - const stopWatch = StopWatch.create(false); - let firstProgress: number | undefined; - const progressObj: vscode.Progress = { - report: (progress: vscode.InteractiveProgress) => { - if (token.isCancellationRequested) { - return; - } - - if (typeof firstProgress === 'undefined') { - firstProgress = stopWatch.elapsed(); - } - - const convertedProgress = typeConvert.ChatResponseProgress.from(entry.extension, progress); - if ('placeholder' in progress && 'resolvedContent' in progress) { - const resolvedContent = Promise.all([this._proxy.$acceptResponseProgress(handle, sessionId, convertedProgress), progress.resolvedContent]); - raceCancellation(resolvedContent, token).then((res) => { - if (!res) { - return; /* Cancelled */ - } - const [progressHandle, progressContent] = res; - this._proxy.$acceptResponseProgress(handle, sessionId, progressContent, progressHandle ?? undefined); - }); - } else { - this._proxy.$acceptResponseProgress(handle, sessionId, convertedProgress); - } - } - }; - let result: vscode.InteractiveResponseForProgress | undefined | null; - try { - result = await entry.provider.provideResponseWithProgress(requestObj, progressObj, token); - if (!result) { - result = { errorDetails: { message: localize('emptyResponse', "Provider returned null response") } }; - } - } catch (err) { - result = { errorDetails: { message: localize('errorResponse', "Error from provider: {0}", err.message), responseIsIncomplete: true } }; - this.logService.error(err); - } - - try { - // Check that the session has not been released since the request started - if (realSession.saveState && this._chatSessions.has(sessionId)) { - const newState = realSession.saveState(); - this._proxy.$acceptChatState(sessionId, newState); - } - } catch (err) { - this.logService.warn(err); - } - - const timings = { firstProgress: firstProgress ?? 0, totalElapsed: stopWatch.elapsed() }; - return { errorDetails: result.errorDetails, timings }; - } - - async $provideSlashCommands(handle: number, sessionId: number, token: CancellationToken): Promise { - const entry = this._chatProvider.get(handle); - if (!entry) { - return undefined; - } - - const realSession = this._chatSessions.get(sessionId); - if (!realSession) { - return undefined; - } - - if (!entry.provider.provideSlashCommands) { - return undefined; - } - - const slashCommands = await entry.provider.provideSlashCommands(realSession, token); - return slashCommands?.map(c => ({ - ...c, - kind: typeConvert.CompletionItemKind.from(c.kind) - })); - } - $releaseSession(sessionId: number) { this._chatSessions.delete(sessionId); } diff --git a/src/vs/workbench/api/common/extHostChatAgents.ts b/src/vs/workbench/api/common/extHostChatAgents.ts deleted file mode 100644 index eca6e389255..00000000000 --- a/src/vs/workbench/api/common/extHostChatAgents.ts +++ /dev/null @@ -1,91 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { DeferredPromise, raceCancellation } from 'vs/base/common/async'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { ILogService } from 'vs/platform/log/common/log'; -import { Progress } from 'vs/platform/progress/common/progress'; -import { ExtHostChatAgentsShape, IMainContext, MainContext, MainThreadChatAgentsShape } from 'vs/workbench/api/common/extHost.protocol'; -import { ExtHostChatProvider } from 'vs/workbench/api/common/extHostChatProvider'; -import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; -import { ChatMessageRole } from 'vs/workbench/api/common/extHostTypes'; -import { IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; -import type * as vscode from 'vscode'; - -export class ExtHostChatAgents implements ExtHostChatAgentsShape { - - private static _idPool = 0; - - private readonly _agents = new Map(); - private readonly _proxy: MainThreadChatAgentsShape; - - constructor( - mainContext: IMainContext, - private readonly _extHostChatProvider: ExtHostChatProvider, - private readonly _logService: ILogService, - ) { - this._proxy = mainContext.getProxy(MainContext.MainThreadChatAgents); - } - - registerAgent(extension: ExtensionIdentifier, name: string, agent: vscode.ChatAgent, metadata: vscode.ChatAgentMetadata): IDisposable { - const handle = ExtHostChatAgents._idPool++; - this._agents.set(handle, { extension, agent }); - this._proxy.$registerAgent(handle, name, metadata); - - return toDisposable(() => { - this._proxy.$unregisterAgent(handle); - this._agents.delete(handle); - }); - } - - async $invokeAgent(handle: number, requestId: number, prompt: string, context: { history: IChatMessage[] }, token: CancellationToken): Promise { - const data = this._agents.get(handle); - if (!data) { - this._logService.warn(`[CHAT](${handle}) CANNOT invoke agent because the agent is not registered`); - return; - } - - let done = false; - function throwIfDone() { - if (done) { - throw new Error('Only valid while executing the command'); - } - } - - const commandExecution = new DeferredPromise(); - token.onCancellationRequested(() => commandExecution.complete()); - setTimeout(() => commandExecution.complete(), 10 * 1000); - this._extHostChatProvider.allowListExtensionWhile(data.extension, commandExecution.p); - - const task = data.agent( - { role: ChatMessageRole.User, content: prompt }, - { history: context.history.map(typeConvert.ChatMessage.to) }, - new Progress(p => { - throwIfDone(); - this._proxy.$handleProgressChunk(requestId, { content: isInteractiveProgressFileTree(p.message) ? p.message : p.message.value }); - }), - token - ); - - try { - return await raceCancellation(Promise.resolve(task).then((v) => { - if (v && 'followUp' in v) { - const convertedFollowup = v?.followUp?.map(f => typeConvert.ChatFollowup.from(f)); - return { followUp: convertedFollowup }; - } - return undefined; - }), token); - } finally { - done = true; - commandExecution.complete(); - } - } -} - -function isInteractiveProgressFileTree(thing: unknown): thing is vscode.InteractiveProgressFileTree { - return !!thing && typeof thing === 'object' && 'treeData' in thing; -} diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index a0c7cd50b8d..8ae2479fd2f 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2157,20 +2157,10 @@ export namespace DataTransfer { } export namespace ChatReplyFollowup { - export function to(followup: IChatReplyFollowup): vscode.InteractiveSessionReplyFollowup { - return { - message: followup.message, - metadata: followup.metadata, - title: followup.title, - tooltip: followup.tooltip, - }; - } - export function from(followup: vscode.InteractiveSessionReplyFollowup): IChatReplyFollowup { return { kind: 'reply', message: followup.message, - metadata: followup.metadata, title: followup.title, tooltip: followup.tooltip, }; @@ -2178,7 +2168,7 @@ export namespace ChatReplyFollowup { } export namespace ChatFollowup { - export function from(followup: string | vscode.InteractiveSessionFollowup): IChatFollowup { + export function from(followup: string | vscode.ChatAgentFollowup): IChatFollowup { if (typeof followup === 'string') { return { title: followup, message: followup, kind: 'reply' }; } else if ('commandId' in followup) { @@ -2303,11 +2293,9 @@ export namespace InteractiveEditorResponseFeedbackKind { } export namespace ChatResponseProgress { - export function from(extension: IExtensionDescription, progress: vscode.InteractiveProgress | vscode.ChatAgentExtendedProgress): extHostProtocol.IChatResponseProgressDto { + export function from(extension: IExtensionDescription, progress: vscode.ChatAgentExtendedProgress): extHostProtocol.IChatResponseProgressDto { if ('placeholder' in progress && 'resolvedContent' in progress) { return { placeholder: progress.placeholder }; - } else if ('responseId' in progress) { - return { requestId: progress.responseId }; } else if ('markdownContent' in progress) { checkProposedApiEnabled(extension, 'chatAgents2Additions'); return { content: MarkdownString.from(progress.markdownContent) }; diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts index f7cc45da136..c21627a7a10 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts @@ -28,7 +28,7 @@ import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatAct import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { ICodeBlockActionContext } from 'vs/workbench/contrib/chat/browser/codeBlockPart'; import { CONTEXT_IN_CHAT_SESSION, CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; -import { IChatCopyAction, IChatService, IDocumentContext, InteractiveSessionCopyKind } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatService, IDocumentContext, InteractiveSessionCopyKind } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatResponseViewModel, isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { CTX_INLINE_CHAT_VISIBLE } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { insertCell } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; @@ -111,9 +111,8 @@ export function registerChatCodeBlockActions() { agentId: context.element.agent?.id, sessionId: context.element.sessionId, requestId: context.element.requestId, - action: { + action: { kind: 'copy', - responseId: context.element.providerResponseId, codeBlockIndex: context.codeBlockIndex, copyType: InteractiveSessionCopyKind.Toolbar, copiedCharacters: context.code.length, @@ -149,24 +148,21 @@ export function registerChatCodeBlockActions() { const totalCharacters = editorModel.getValueLength(); // Report copy to extensions - if (context.element.providerResponseId) { - const chatService = accessor.get(IChatService); - chatService.notifyUserAction({ - providerId: context.element.providerId, - agentId: context.element.agent?.id, - sessionId: context.element.sessionId, - requestId: context.element.requestId, - action: { - kind: 'copy', - codeBlockIndex: context.codeBlockIndex, - responseId: context.element.providerResponseId, - copyType: InteractiveSessionCopyKind.Action, - copiedText, - copiedCharacters: copiedText.length, - totalCharacters, - } - }); - } + const chatService = accessor.get(IChatService); + chatService.notifyUserAction({ + providerId: context.element.providerId, + agentId: context.element.agent?.id, + sessionId: context.element.sessionId, + requestId: context.element.requestId, + action: { + kind: 'copy', + codeBlockIndex: context.codeBlockIndex, + copyType: InteractiveSessionCopyKind.Action, + copiedText, + copiedCharacters: copiedText.length, + totalCharacters, + } + }); // Copy full cell if no selection, otherwise fall back on normal editor implementation if (noSelection) { @@ -337,7 +333,6 @@ export function registerChatCodeBlockActions() { requestId: context.element.requestId, action: { kind: 'insert', - responseId: context.element.providerResponseId!, codeBlockIndex: context.codeBlockIndex, totalCharacters: context.code.length, } @@ -386,7 +381,6 @@ export function registerChatCodeBlockActions() { requestId: context.element.requestId, action: { kind: 'insert', - responseId: context.element.providerResponseId!, codeBlockIndex: context.codeBlockIndex, totalCharacters: context.code.length, newFile: true @@ -482,7 +476,6 @@ export function registerChatCodeBlockActions() { requestId: context.element.requestId, action: { kind: 'runInTerminal', - responseId: context.element.providerResponseId!, codeBlockIndex: context.codeBlockIndex, languageId: context.languageId, } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts index 4d9bf1fe75b..2d960c61a33 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts @@ -60,7 +60,6 @@ export function registerChatTitleActions() { action: { kind: 'vote', direction: InteractiveSessionVoteDirection.Up, - responseId: item.providerResponseId!, } }); item.setVote(InteractiveSessionVoteDirection.Up); @@ -103,7 +102,6 @@ export function registerChatTitleActions() { action: { kind: 'vote', direction: InteractiveSessionVoteDirection.Down, - responseId: item.providerResponseId!, } }); item.setVote(InteractiveSessionVoteDirection.Down); @@ -222,12 +220,12 @@ export function registerChatTitleActions() { item = widget?.getFocus(); } - const providerRequestId = isRequestVM(item) ? item.providerRequestId : - isResponseVM(item) ? item.providerResponseId : undefined; + const requestId = isRequestVM(item) ? item.id : + isResponseVM(item) ? item.requestId : undefined; - if (providerRequestId) { + if (requestId) { const chatService = accessor.get(IChatService); - chatService.removeRequest(item.sessionId, providerRequestId); + chatService.removeRequest(item.sessionId, requestId); } } }); diff --git a/src/vs/workbench/contrib/chat/browser/chat.ts b/src/vs/workbench/contrib/chat/browser/chat.ts index ff049f509f0..3df1e83af19 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.ts @@ -3,14 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService'; -import { IChatRequestViewModel, IChatResponseViewModel, IChatViewModel, IChatWelcomeMessageViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { Event } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { Selection } from 'vs/editor/common/core/selection'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IChatWidgetContrib } from 'vs/workbench/contrib/chat/browser/chatWidget'; -import { Selection } from 'vs/editor/common/core/selection'; +import { IChatRequestViewModel, IChatResponseViewModel, IChatViewModel, IChatWelcomeMessageViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; export const IChatWidgetService = createDecorator('chatWidgetService'); export const IQuickChatService = createDecorator('quickChatService'); @@ -121,7 +120,6 @@ export interface IChatWidget { focusLastMessage(): void; focusInput(): void; hasInputFocus(): boolean; - getSlashCommands(): Promise; getCodeBlockInfoForEditor(uri: URI): IChatCodeBlockInfo | undefined; getCodeBlockInfosForResponse(response: IChatResponseViewModel): IChatCodeBlockInfo[]; getFileTreeInfosForResponse(response: IChatResponseViewModel): IChatFileTreeInfo[]; diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 7ca9fde120a..ebfb67e8d2a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -54,10 +54,10 @@ import { convertParsedRequestToMarkdown, reduceInlineContentReferences, walkTree import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions'; import { CodeBlockPart, ICodeBlockData, ICodeBlockPart } from 'vs/workbench/contrib/chat/browser/codeBlockPart'; import { IChatAgentMetadata } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_HAS_PROVIDER_ID, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +import { CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IPlaceholderMarkdownString } from 'vs/workbench/contrib/chat/common/chatModel'; import { chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { IChatContentReference, IChatReplyFollowup, IChatResponseProgressFileTreeData, IChatService, ISlashCommand, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatContentReference, IChatReplyFollowup, IChatResponseProgressFileTreeData, IChatService, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatResponseMarkdownRenderData, IChatResponseRenderData, IChatResponseViewModel, IChatWelcomeMessageViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { IWordCountResult, getNWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; import { createFileIconThemableTreeContainerScope } from 'vs/workbench/contrib/files/browser/views/explorerView'; @@ -89,7 +89,6 @@ const forceVerboseLayoutTracing = false; export interface IChatRendererDelegate { getListLength(): number; - getSlashCommands(): ISlashCommand[]; } export interface IChatListItemRendererOptions { @@ -264,7 +263,6 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer codeElement.textContent === `/${command.command}`)) { - codeElement.classList.add('interactive-slash-command'); - } - } templateData.value.appendChild(result.element); templateData.elementDisposables.add(result); } @@ -779,11 +771,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer markdown.value.startsWith(`/${s.command} `)); - const toRender = usedSlashCommand ? markdown.value.slice(usedSlashCommand.command.length + 2) : markdown.value; - markdown = new MarkdownString(toRender, { + markdown = new MarkdownString(markdown.value, { isTrusted: { // Disable all other config options except isTrusted enabledCommands: typeof markdown.isTrusted === 'object' ? markdown.isTrusted?.enabledCommands : [] ?? [] @@ -831,15 +819,6 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer disposables.add(d)); return { element: result.element, diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 9a21a8da6ca..7634b50e741 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -6,7 +6,6 @@ import * as dom from 'vs/base/browser/dom'; import { ITreeContextMenuEvent, ITreeElement } from 'vs/base/browser/ui/tree/tree'; import { disposableTimeout } from 'vs/base/common/async'; -import { CancellationToken } from 'vs/base/common/cancellation'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Emitter } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable, MutableDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -23,7 +22,7 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle import { WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; import { ILogService } from 'vs/platform/log/common/log'; import { IViewsService } from 'vs/workbench/common/views'; -import { ChatTreeItem, IChatWidgetViewOptions, IChatAccessibilityService, IChatCodeBlockInfo, IChatFileTreeInfo, IChatWidget, IChatWidgetService, IChatWidgetViewContext } from 'vs/workbench/contrib/chat/browser/chat'; +import { ChatTreeItem, IChatAccessibilityService, IChatCodeBlockInfo, IChatFileTreeInfo, IChatWidget, IChatWidgetService, IChatWidgetViewContext, IChatWidgetViewOptions } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart'; import { ChatAccessibilityProvider, ChatListDelegate, ChatListItemRenderer, IChatListItemRendererOptions, IChatRendererDelegate } from 'vs/workbench/contrib/chat/browser/chatListRenderer'; import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions'; @@ -31,7 +30,7 @@ import { ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane'; import { CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_SESSION } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; import { ChatModelInitState, IChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; -import { IChatReplyFollowup, IChatService, ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatReplyFollowup, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { ChatViewModel, IChatResponseViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; const $ = dom.$; @@ -109,15 +108,6 @@ export class ChatWidget extends Disposable implements IChatWidget { this.viewModelDisposables.add(viewModel); } - this.slashCommandsPromise = undefined; - this.lastSlashCommands = undefined; - - this.getSlashCommands().then(() => { - if (!this._isDisposed) { - this.onDidChangeItems(); - } - }); - this._onDidChangeViewModel.fire(); } @@ -125,9 +115,6 @@ export class ChatWidget extends Disposable implements IChatWidget { return this._viewModel; } - private lastSlashCommands: ISlashCommand[] | undefined; - private slashCommandsPromise: Promise | undefined; - constructor( readonly viewContext: IChatWidgetViewContext, private readonly viewOptions: IChatWidgetViewOptions, @@ -164,12 +151,6 @@ export class ChatWidget extends Disposable implements IChatWidget { return this.inputPart.inputUri; } - private _isDisposed: boolean = false; - public override dispose(): void { - this._isDisposed = true; - super.dispose(); - } - render(parent: HTMLElement): void { const viewId = 'viewId' in this.viewContext ? this.viewContext.viewId : undefined; this.editorOptions = this._register(this.instantiationService.createInstance(ChatEditorOptions, viewId, this.styles.listForeground, this.styles.inputEditorBackground, this.styles.resultEditorBackground)); @@ -260,7 +241,7 @@ export class ChatWidget extends Disposable implements IChatWidget { // TODO? We can give the welcome message a proper VM or get rid of the rest of the VMs ((isWelcomeVM(element) && this.viewModel) ? `_${ChatModelInitState[this.viewModel.initState]}` : '') + // Ensure re-rendering an element once slash commands are loaded, so the colorization can be applied. - `${(isRequestVM(element) || isWelcomeVM(element)) && !!this.lastSlashCommands ? '_scLoaded' : ''}` + + `${(isRequestVM(element) || isWelcomeVM(element)) /* && !!this.lastSlashCommands ? '_scLoaded' : '' */}` + // If a response is in the process of progressive rendering, we need to ensure that it will // be re-rendered so progressive rendering is restarted, even if the model wasn't updated. `${isResponseVM(element) && element.renderData ? `_${this.visibleChangeCount}` : ''}` + @@ -309,27 +290,11 @@ export class ChatWidget extends Disposable implements IChatWidget { } } - async getSlashCommands(): Promise { - if (!this.viewModel) { - return; - } - - if (!this.slashCommandsPromise) { - this.slashCommandsPromise = this.chatService.getSlashCommands(this.viewModel.sessionId, CancellationToken.None).then(commands => { - this.lastSlashCommands = commands ?? []; - return this.lastSlashCommands; - }); - } - - return this.slashCommandsPromise; - } - private createList(listContainer: HTMLElement, options: IChatListItemRendererOptions): void { const scopedInstantiationService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.contextKeyService])); const delegate = scopedInstantiationService.createInstance(ChatListDelegate); const rendererDelegate: IChatRendererDelegate = { getListLength: () => this.tree.getNode(null).visibleChildrenCount, - getSlashCommands: () => this.lastSlashCommands ?? [], }; this.renderer = this._register(scopedInstantiationService.createInstance( ChatListItemRenderer, @@ -462,7 +427,6 @@ export class ChatWidget extends Disposable implements IChatWidget { this.container.setAttribute('data-session-id', model.sessionId); this.viewModel = this.instantiationService.createInstance(ChatViewModel, model); this.viewModelDisposables.add(this.viewModel.onDidChange(e => { - this.slashCommandsPromise = undefined; this.requestInProgress.set(this.viewModel!.requestInProgress); this.onDidChangeItems(); if (e?.kind === 'addRequest') { @@ -536,8 +500,7 @@ export class ChatWidget extends Disposable implements IChatWidget { 'query' in opts ? opts.query : `${opts.prefix} ${editorValue}`; const isUserQuery = !opts || 'query' in opts; - const usedSlashCommand = this.lookupSlashCommand(input); - const result = await this.chatService.sendRequest(this.viewModel.sessionId, input, usedSlashCommand); + const result = await this.chatService.sendRequest(this.viewModel.sessionId, input); if (result) { this.inputPart.acceptInput(isUserQuery ? input : undefined); @@ -552,10 +515,6 @@ export class ChatWidget extends Disposable implements IChatWidget { } } - private lookupSlashCommand(input: string): ISlashCommand | undefined { - return this.lastSlashCommands?.find(sc => input.startsWith(`/${sc.command}`)); - } - getCodeBlockInfosForResponse(response: IChatResponseViewModel): IChatCodeBlockInfo[] { return this.renderer.getCodeBlockInfosForResponse(response); } diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index 5d7a0f6b205..a470b14d1e3 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -32,6 +32,7 @@ import { chatSlashCommandBackground, chatSlashCommandForeground } from 'vs/workb import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, ChatRequestTextPart, ChatRequestVariablePart, chatAgentLeader, chatSubcommandLeader, chatVariableLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; import { IChatService, ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; import { isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; @@ -49,7 +50,7 @@ class InputEditorDecorations extends Disposable { public readonly id = 'inputEditorDecorations'; - private readonly previouslyUsedSlashCommands = new Set(); + private readonly previouslyUsedAgents = new Set(); private readonly viewModelDisposables = this._register(new MutableDisposable()); @@ -71,16 +72,12 @@ class InputEditorDecorations extends Disposable { this._register(this.widget.inputEditor.onDidChangeModelContent(() => this.updateInputEditorDecorations())); this._register(this.widget.onDidChangeViewModel(() => { this.registerViewModelListeners(); - this.previouslyUsedSlashCommands.clear(); + this.previouslyUsedAgents.clear(); this.updateInputEditorDecorations(); })); - this._register(this.chatService.onDidSubmitSlashCommand((e) => { + this._register(this.chatService.onDidSubmitAgent((e) => { if (e.sessionId === this.widget.viewModel?.sessionId) { - if ('agent' in e) { - this.previouslyUsedSlashCommands.add(agentAndCommandToKey(e.agent.id, e.slashCommand.name)); - } else { - this.previouslyUsedSlashCommands.add(e.slashCommand); - } + this.previouslyUsedAgents.add(agentAndCommandToKey(e.agent.id, e.slashCommand.name)); } })); @@ -187,7 +184,7 @@ class InputEditorDecorations extends Disposable { const onlyAgentCommandAndWhitespace = agentPart && agentSubcommandPart && parsedRequest.every(p => p instanceof ChatRequestTextPart && !p.text.trim().length || p instanceof ChatRequestAgentPart || p instanceof ChatRequestAgentSubcommandPart); if (onlyAgentCommandAndWhitespace) { // Agent reference and subcommand with no other text - show the placeholder - const isFollowupSlashCommand = this.previouslyUsedSlashCommands.has(agentAndCommandToKey(agentPart.agent.id, agentSubcommandPart.command.name)); + const isFollowupSlashCommand = this.previouslyUsedAgents.has(agentAndCommandToKey(agentPart.agent.id, agentSubcommandPart.command.name)); const shouldRenderFollowupPlaceholder = isFollowupSlashCommand && agentSubcommandPart.command.followupPlaceholder; if (agentSubcommandPart?.command.description) { placeholderDecoration = [{ @@ -210,7 +207,7 @@ class InputEditorDecorations extends Disposable { const onlySlashCommandAndWhitespace = slashCommandPart && parsedRequest.every(p => p instanceof ChatRequestTextPart && !p.text.trim().length || p instanceof ChatRequestSlashCommandPart); if (onlySlashCommandAndWhitespace) { // Command reference with no other text - show the placeholder - const isFollowupSlashCommand = this.previouslyUsedSlashCommands.has(slashCommandPart.slashCommand.command); + const isFollowupSlashCommand = this.previouslyUsedAgents.has(slashCommandPart.slashCommand.command); const shouldRenderFollowupPlaceholder = isFollowupSlashCommand && slashCommandPart.slashCommand.followupPlaceholder; if (shouldRenderFollowupPlaceholder || slashCommandPart.slashCommand.detail) { placeholderDecoration = [{ @@ -264,15 +261,12 @@ class InputEditorSlashCommandMode extends Disposable { @IChatService private readonly chatService: IChatService ) { super(); - this._register(this.chatService.onDidSubmitSlashCommand(e => { + this._register(this.chatService.onDidSubmitAgent(e => { if (this.widget.viewModel?.sessionId !== e.sessionId) { return; } - if ('agent' in e) { - this.repopulateAgentCommand(e.agent, e.slashCommand); - } else { - this.repopulateSlashCommand(e.slashCommand); - } + + this.repopulateAgentCommand(e.agent, e.slashCommand); })); } @@ -283,20 +277,6 @@ class InputEditorSlashCommandMode extends Disposable { this.widget.inputEditor.setPosition({ lineNumber: 1, column: value.length + 1 }); } } - - private async repopulateSlashCommand(slashCommand: string) { - const slashCommands = await this.widget.getSlashCommands(); - - if (this.widget.inputEditor.getValue().trim().length !== 0) { - return; - } - - if (slashCommands?.find(c => c.command === slashCommand)?.shouldRepopulate) { - const value = `/${slashCommand} `; - this.widget.inputEditor.setValue(value); - this.widget.inputEditor.setPosition({ lineNumber: 1, column: value.length + 1 }); - } - } } ChatWidget.CONTRIBS.push(InputEditorDecorations, InputEditorSlashCommandMode); @@ -306,11 +286,12 @@ class SlashCommandCompletions extends Disposable { @ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService, @IChatWidgetService private readonly chatWidgetService: IChatWidgetService, @IInstantiationService private readonly instantiationService: IInstantiationService, + @IChatSlashCommandService private readonly chatSlashCommandService: IChatSlashCommandService ) { super(); this._register(this.languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, { - _debugDisplayName: 'chatSlashCommand', + _debugDisplayName: 'globalSlashCommands', triggerCharacters: ['/'], provideCompletionItems: async (model: ITextModel, _position: Position, _context: CompletionContext, _token: CancellationToken) => { const widget = this.chatWidgetService.getWidgetByInputUri(model.uri); @@ -329,7 +310,7 @@ class SlashCommandCompletions extends Disposable { return; } - const slashCommands = await widget.getSlashCommands(); + const slashCommands = this.chatSlashCommandService.getCommands(); if (!slashCommands) { return null; } diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 22f1b82d3df..9c46adfed29 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -390,10 +390,6 @@ color: var(--vscode-notificationsInfoIcon-foreground) !important; /* Have to override default styles which apply to all lists */ } -.interactive-item-container .value .interactive-slash-command { - color: var(--vscode-textLink-foreground); -} - .interactive-session .interactive-input-part { padding: 12px 0px; display: flex; diff --git a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts index 631f0af7d00..ed242d3b415 100644 --- a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts +++ b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts @@ -6,7 +6,6 @@ import { localize } from 'vs/nls'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -export const CONTEXT_RESPONSE_HAS_PROVIDER_ID = new RawContextKey('chatSessionResponseHasProviderId', false, { type: 'boolean', description: localize('interactiveSessionResponseHasProviderId', "True when the provider has assigned an id to this response.") }); export const CONTEXT_RESPONSE_VOTE = new RawContextKey('chatSessionResponseVote', '', { type: 'string', description: localize('interactiveSessionResponseVote', "When the response has been voted up, is set to 'up'. When voted down, is set to 'down'. Otherwise an empty string.") }); export const CONTEXT_RESPONSE_FILTERED = new RawContextKey('chatSessionResponseFiltered', false, { type: 'boolean', description: localize('chatResponseFiltered', "True when the chat response was filtered out by the server.") }); export const CONTEXT_CHAT_REQUEST_IN_PROGRESS = new RawContextKey('chatSessionRequestInProgress', false, { type: 'boolean', description: localize('interactiveSessionRequestInProgress', "True when the current request is still in progress.") }); diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 9415c63f9c7..b743e6915fe 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -20,7 +20,6 @@ import { IChat, IChatContentInlineReference, IChatContentReference, IChatFollowu export interface IChatRequestModel { readonly id: string; - readonly providerRequestId: string | undefined; readonly username: string; readonly avatarIconUri?: URI; readonly session: IChatModel; @@ -53,7 +52,6 @@ export interface IChatResponseModel { readonly onDidChange: Event; readonly id: string; readonly providerId: string; - readonly providerResponseId: string | undefined; readonly requestId: string; readonly username: string; readonly avatarIconUri?: URI; @@ -79,10 +77,6 @@ export class ChatRequestModel implements IChatRequestModel { return this._id; } - public get providerRequestId(): string | undefined { - return this._providerRequestId; - } - public get username(): string { return this.session.requesterUsername; } @@ -93,14 +87,9 @@ export class ChatRequestModel implements IChatRequestModel { constructor( public readonly session: ChatModel, - public readonly message: IParsedChatRequest, - private _providerRequestId?: string) { + public readonly message: IParsedChatRequest) { this._id = 'request_' + ChatRequestModel.nextId++; } - - setProviderRequestId(providerRequestId: string) { - this._providerRequestId = providerRequestId; - } } export interface IPlaceholderMarkdownString extends IMarkdownString { @@ -256,10 +245,6 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel return this._id; } - public get providerResponseId(): string | undefined { - return this._providerResponseId; - } - public get isComplete(): boolean { return this._isComplete; } @@ -317,7 +302,6 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel private _isComplete: boolean = false, private _isCanceled = false, private _vote?: InteractiveSessionVoteDirection, - private _providerResponseId?: string, private _errorDetails?: IChatResponseErrorDetails, followups?: ReadonlyArray ) { @@ -339,10 +323,6 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel this._onDidChange.fire(); } - setProviderResponseId(providerResponseId: string) { - this._providerResponseId = providerResponseId; - } - setErrorDetails(errorDetails?: IChatResponseErrorDetails): void { this._errorDetails = errorDetails; this._onDidChange.fire(); @@ -392,7 +372,6 @@ export interface ISerializableChatsData { export type ISerializableChatAgentData = UriDto; export interface ISerializableChatRequestData { - providerRequestId: string | undefined; message: string | IParsedChatRequest; response: ReadonlyArray | undefined; agent?: ISerializableChatAgentData; @@ -414,7 +393,6 @@ export interface IExportableChatData { responderUsername: string; requesterAvatarIconUri: UriComponents | undefined; responderAvatarIconUri: UriComponents | undefined; - providerState: any; } export interface ISerializableChatData extends IExportableChatData { @@ -490,11 +468,6 @@ export class ChatModel extends Disposable implements IChatModel { return this._welcomeMessage; } - private _providerState: any; - get providerState(): any { - return this._providerState; - } - // TODO to be clear, this is not the same as the id from the session object, which belongs to the provider. // It's easier to be able to identify this model before its async initialization is complete private _sessionId: string; @@ -560,7 +533,6 @@ export class ChatModel extends Disposable implements IChatModel { this._isImported = (!!initialData && !isSerializableSessionData(initialData)) || (initialData?.isImported ?? false); this._sessionId = (isSerializableSessionData(initialData) && initialData.sessionId) || generateUuid(); this._requests = initialData ? this._deserialize(initialData) : []; - this._providerState = initialData ? initialData.providerState : undefined; this._creationDate = (isSerializableSessionData(initialData) && initialData.creationDate) || Date.now(); this._initialRequesterAvatarIconUri = initialData?.requesterAvatarIconUri && URI.revive(initialData.requesterAvatarIconUri); @@ -585,11 +557,11 @@ export class ChatModel extends Disposable implements IChatModel { typeof raw.message === 'string' ? this.getParsedRequestFromString(raw.message) : reviveParsedChatRequest(raw.message); - const request = new ChatRequestModel(this, parsedRequest, raw.providerRequestId); + const request = new ChatRequestModel(this, parsedRequest); if (raw.response || raw.responseErrorDetails) { const agent = (raw.agent && 'metadata' in raw.agent) ? // Check for the new format, ignore entries in the old format revive(raw.agent) : undefined; - request.response = new ChatResponseModel(raw.response ?? [new MarkdownString(raw.response)], this, agent, request.id, true, raw.isCanceled, raw.vote, raw.providerRequestId, raw.responseErrorDetails, raw.followups); + request.response = new ChatResponseModel(raw.response ?? [new MarkdownString(raw.response)], this, agent, request.id, true, raw.isCanceled, raw.vote, raw.responseErrorDetails, raw.followups); if (raw.usedContext) { // @ulugbekna: if this's a new vscode sessions, doc versions are incorrect anyway? request.response.updateContent(raw.usedContext); } @@ -642,13 +614,6 @@ export class ChatModel extends Disposable implements IChatModel { } this._isInitializedDeferred.complete(); - - if (session.onDidChangeState) { - this._register(session.onDidChangeState(state => { - this._providerState = state; - this.logService.trace('ChatModel#acceptNewSessionState'); - })); - } this._onDidChange.fire({ kind: 'initialize' }); } @@ -707,21 +672,15 @@ export class ChatModel extends Disposable implements IChatModel { if (agent) { request.response.setAgent(agent, progress.command); } - } else { - request.setProviderRequestId(progress.requestId); - request.response.setProviderResponseId(progress.requestId); } } - removeRequest(requestId: string): void { - const index = this._requests.findIndex(request => request.providerRequestId === requestId); + removeRequest(id: string): void { + const index = this._requests.findIndex(request => request.id === id); const request = this._requests[index]; - if (!request.providerRequestId) { - return; - } if (index !== -1) { - this._onDidChange.fire({ kind: 'removeRequest', requestId: request.providerRequestId, responseId: request.response?.providerResponseId }); + this._onDidChange.fire({ kind: 'removeRequest', requestId: request.id, responseId: request.response?.id }); this._requests.splice(index, 1); request.response?.dispose(); } @@ -782,21 +741,19 @@ export class ChatModel extends Disposable implements IChatModel { }), requests: this._requests.map((r): ISerializableChatRequestData => { return { - providerRequestId: r.providerRequestId, message: r.message, response: r.response ? r.response.response.value : undefined, responseErrorDetails: r.response?.errorDetails, followups: r.response?.followups, isCanceled: r.response?.isCanceled, vote: r.response?.vote, - agent: r.response?.agent, + agent: r.response?.agent ? { id: r.response.agent.id, metadata: r.response.agent.metadata } : undefined, // May actually be the full IChatAgent instance, just take the data props slashCommand: r.response?.slashCommand, usedContext: r.response?.response.usedContext, contentReferences: r.response?.response.contentReferences }; }), providerId: this.providerId, - providerState: this._providerState }; } diff --git a/src/vs/workbench/contrib/chat/common/chatRequestParser.ts b/src/vs/workbench/contrib/chat/common/chatRequestParser.ts index da606aac369..4a47172525c 100644 --- a/src/vs/workbench/contrib/chat/common/chatRequestParser.ts +++ b/src/vs/workbench/contrib/chat/common/chatRequestParser.ts @@ -9,7 +9,7 @@ import { IPosition, Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestDynamicReferencePart, ChatRequestSlashCommandPart, ChatRequestTextPart, ChatRequestVariablePart, IParsedChatRequest, IParsedChatRequestPart, chatAgentLeader, chatSubcommandLeader, chatVariableLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatVariablesService, IDynamicReference } from 'vs/workbench/contrib/chat/common/chatVariables'; const agentReg = /^@([\w_\-]+)(?=(\s|$|\b))/i; // An @-agent @@ -21,7 +21,7 @@ export class ChatRequestParser { constructor( @IChatAgentService private readonly agentService: IChatAgentService, @IChatVariablesService private readonly variableService: IChatVariablesService, - @IChatService private readonly chatService: IChatService, + @IChatSlashCommandService private readonly slashCommandService: IChatSlashCommandService ) { } async parseChatRequest(sessionId: string, message: string): Promise { @@ -174,7 +174,7 @@ export class ChatRequestParser { return new ChatRequestAgentSubcommandPart(slashRange, slashEditorRange, subCommand); } } else { - const slashCommands = await this.chatService.getSlashCommands(sessionId, CancellationToken.None); + const slashCommands = this.slashCommandService.getCommands(); const slashCommand = slashCommands.find(c => c.command === command); if (slashCommand) { // Valid standalone slash command diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 24d969082d1..8f5d6054790 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -23,7 +23,6 @@ export interface IChat { responderUsername: string; responderAvatarIconUri?: URI; inputPlaceholder?: string; - onDidChangeState?: Event; dispose?(): void; } @@ -100,7 +99,6 @@ export interface IChatAgentDetection { export type IChatProgress = | { content: string | IMarkdownString } - | { requestId: string } | { treeData: IChatResponseProgressFileTreeData } | { placeholder: string; resolvedContent: Promise } | IUsedContext @@ -108,18 +106,13 @@ export type IChatProgress = | IChatContentInlineReference | IChatAgentDetection; -export interface IPersistedChatState { } export interface IChatProvider { readonly id: string; readonly displayName: string; readonly iconUrl?: string; - prepareSession(initialState: IPersistedChatState | undefined, token: CancellationToken): ProviderResult; + prepareSession(token: CancellationToken): ProviderResult; provideWelcomeMessage?(token: CancellationToken): ProviderResult<(string | IMarkdownString | IChatReplyFollowup[])[] | undefined>; provideSampleQuestions?(token: CancellationToken): ProviderResult; - provideFollowups?(session: IChat, token: CancellationToken): ProviderResult; - provideReply(request: IChatRequest, progress: (progress: IChatProgress) => void, token: CancellationToken): ProviderResult; - provideSlashCommands?(session: IChat, token: CancellationToken): ProviderResult; - removeRequest?(session: IChat, requestId: string): void; } export interface ISlashCommand { @@ -156,7 +149,6 @@ export interface IChatReplyFollowup { message: string; title?: string; tooltip?: string; - metadata?: any; } export interface IChatResponseCommandFollowup { @@ -177,7 +169,6 @@ export enum InteractiveSessionVoteDirection { export interface IChatVoteAction { kind: 'vote'; - responseId: string; direction: InteractiveSessionVoteDirection; } @@ -189,7 +180,6 @@ export enum InteractiveSessionCopyKind { export interface IChatCopyAction { kind: 'copy'; - responseId: string; codeBlockIndex: number; copyType: InteractiveSessionCopyKind; copiedCharacters: number; @@ -199,7 +189,6 @@ export interface IChatCopyAction { export interface IChatInsertAction { kind: 'insert'; - responseId: string; codeBlockIndex: number; totalCharacters: number; newFile?: boolean; @@ -207,7 +196,6 @@ export interface IChatInsertAction { export interface IChatTerminalAction { kind: 'runInTerminal'; - responseId: string; codeBlockIndex: number; languageId?: string; } @@ -271,7 +259,7 @@ export interface IChatService { _serviceBrand: undefined; transferredSessionData: IChatTransferredSessionData | undefined; - onDidSubmitSlashCommand: Event<{ slashCommand: string; sessionId: string } | { agent: IChatAgentData; slashCommand: IChatAgentCommand; sessionId: string }>; + onDidSubmitAgent: Event<{ agent: IChatAgentData; slashCommand: IChatAgentCommand; sessionId: string }>; onDidRegisterProvider: Event<{ providerId: string }>; registerProvider(provider: IChatProvider): IDisposable; hasSessions(providerId: string): boolean; @@ -285,10 +273,9 @@ export interface IChatService { /** * Returns whether the request was accepted. */ - sendRequest(sessionId: string, message: string, usedSlashCommand?: ISlashCommand): Promise<{ responseCompletePromise: Promise } | undefined>; + sendRequest(sessionId: string, message: string): Promise<{ responseCompletePromise: Promise } | undefined>; removeRequest(sessionid: string, requestId: string): Promise; cancelCurrentRequestForSession(sessionId: string): void; - getSlashCommands(sessionId: string, token: CancellationToken): Promise; clearSession(sessionId: string): void; addCompleteRequest(sessionId: string, message: IParsedChatRequest | string, response: IChatCompleteResponse): void; sendRequestToProvider(sessionId: string, message: IChatDynamicRequest): void; diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index d1537d81132..f1c3f8454ee 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -8,7 +8,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { MarkdownString, isMarkdownString } from 'vs/base/common/htmlContent'; import { Iterable } from 'vs/base/common/iterator'; -import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableMap, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { revive } from 'vs/base/common/marshalling'; import { StopWatch } from 'vs/base/common/stopwatch'; import { URI, UriComponents } from 'vs/base/common/uri'; @@ -27,7 +27,7 @@ import { ChatModel, ChatModelInitState, ChatRequestModel, ChatWelcomeMessageMode import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatMessageRole, IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; -import { IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatRequest, IChatResponse, IChatService, IChatTransferredSessionData, IChatUserActionEvent, ISlashCommand, InteractiveSessionCopyKind, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatResponse, IChatService, IChatTransferredSessionData, IChatUserActionEvent, InteractiveSessionCopyKind, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -134,7 +134,7 @@ export class ChatService extends Disposable implements IChatService { private readonly _providers = new Map(); - private readonly _sessionModels = new Map(); + private readonly _sessionModels = this._register(new DisposableMap()); private readonly _pendingRequests = new Map>(); private readonly _persistedSessions: ISerializableChatsData; private readonly _hasProvider: IContextKey; @@ -147,8 +147,8 @@ export class ChatService extends Disposable implements IChatService { private readonly _onDidPerformUserAction = this._register(new Emitter()); public readonly onDidPerformUserAction: Event = this._onDidPerformUserAction.event; - private readonly _onDidSubmitSlashCommand = this._register(new Emitter<{ slashCommand: string; sessionId: string } | { agent: IChatAgentData; slashCommand: IChatAgentCommand; sessionId: string }>()); - public readonly onDidSubmitSlashCommand = this._onDidSubmitSlashCommand.event; + private readonly _onDidSubmitAgent = this._register(new Emitter<{ agent: IChatAgentData; slashCommand: IChatAgentCommand; sessionId: string }>()); + public readonly onDidSubmitAgent = this._onDidSubmitAgent.event; private readonly _onDidDisposeSession = this._register(new Emitter<{ sessionId: string; providerId: string; reason: 'initializationFailed' | 'cleared' }>()); public readonly onDidDisposeSession = this._onDidDisposeSession.event; @@ -365,7 +365,7 @@ export class ChatService extends Disposable implements IChatService { let session: IChat | undefined; try { - session = await provider.prepareSession(model.providerState, token) ?? undefined; + session = await provider.prepareSession(token) ?? undefined; } catch (err) { this.trace('initializeSession', `Provider initializeSession threw: ${err}`); } @@ -387,8 +387,7 @@ export class ChatService extends Disposable implements IChatService { } catch (err) { this.trace('startSession', `initializeSession failed: ${err}`); model.setInitializationError(err); - model.dispose(); - this._sessionModels.delete(model.sessionId); + this._sessionModels.deleteAndDispose(model.sessionId); this._onDidDisposeSession.fire({ sessionId: model.sessionId, providerId: model.providerId, reason: 'initializationFailed' }); } } @@ -424,7 +423,7 @@ export class ChatService extends Disposable implements IChatService { return this._startSession(data.providerId, data, CancellationToken.None); } - async sendRequest(sessionId: string, request: string, usedSlashCommand?: ISlashCommand): Promise<{ responseCompletePromise: Promise } | undefined> { + async sendRequest(sessionId: string, request: string): Promise<{ responseCompletePromise: Promise } | undefined> { this.trace('sendRequest', `sessionId: ${sessionId}, message: ${request.substring(0, 20)}${request.length > 20 ? '[...]' : ''}}`); if (!request.trim()) { this.trace('sendRequest', 'Rejected empty message'); @@ -448,10 +447,10 @@ export class ChatService extends Disposable implements IChatService { } // This method is only returning whether the request was accepted - don't block on the actual request - return { responseCompletePromise: this._sendRequestAsync(model, sessionId, provider, request, usedSlashCommand) }; + return { responseCompletePromise: this._sendRequestAsync(model, sessionId, provider, request) }; } - private async _sendRequestAsync(model: ChatModel, sessionId: string, provider: IChatProvider, message: string, usedSlashCommand?: ISlashCommand): Promise { + private async _sendRequestAsync(model: ChatModel, sessionId: string, provider: IChatProvider, message: string): Promise { const parsedRequest = await this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(sessionId, message); let request: ChatRequestModel; @@ -486,7 +485,7 @@ export class ChatService extends Disposable implements IChatService { } else if ('agentName' in progress) { this.trace('sendRequest', `Provider returned an agent detection for session ${model.sessionId}:\n ${JSON.stringify(progress, null, '\t')}`); } else { - this.trace('sendRequest', `Provider returned id for session ${model.sessionId}, ${progress.requestId}`); + this.trace('sendRequest', `Provider returned unknown progress for session ${model.sessionId}:\n ${JSON.stringify(progress, null, '\t')}`); } model.acceptResponseProgress(request, progress); @@ -503,7 +502,7 @@ export class ChatService extends Disposable implements IChatService { result: 'cancelled', requestType, agent: agentPart?.agent.id ?? '', - slashCommand: agentSlashCommandPart ? agentSlashCommandPart.command.name : usedSlashCommand?.command, + slashCommand: agentSlashCommandPart ? agentSlashCommandPart.command.name : commandPart?.slashCommand.command, chatSessionId: model.sessionId }); @@ -511,10 +510,8 @@ export class ChatService extends Disposable implements IChatService { }); try { - if (usedSlashCommand?.command) { - this._onDidSubmitSlashCommand.fire({ slashCommand: usedSlashCommand.command, sessionId: model.sessionId }); - } else if (agentPart && agentSlashCommandPart?.command) { - this._onDidSubmitSlashCommand.fire({ agent: agentPart.agent, slashCommand: agentSlashCommandPart.command, sessionId: model.sessionId }); + if (agentPart && agentSlashCommandPart?.command) { + this._onDidSubmitAgent.fire({ agent: agentPart.agent, slashCommand: agentSlashCommandPart.command, sessionId: model.sessionId }); } let rawResponse: IChatResponse | null | undefined; @@ -574,19 +571,7 @@ export class ChatService extends Disposable implements IChatService { rawResponse = { session: model.session! }; } else { - request = model.addRequest(parsedRequest); - const requestProps: IChatRequest = { - session: model.session!, - message, - variables: {} - }; - - if ('parts' in parsedRequest) { - const varResult = await this.chatVariablesService.resolveVariables(parsedRequest, model, token); - requestProps.variables = varResult.variables; - requestProps.message = varResult.prompt; - } - rawResponse = await provider.provideReply(requestProps, progressCallback, token); + throw new Error(`Can't handle request`); } if (token.isCancellationRequested) { @@ -608,7 +593,7 @@ export class ChatService extends Disposable implements IChatService { result, requestType, agent: agentPart?.agent.id ?? '', - slashCommand: agentSlashCommandPart ? agentSlashCommandPart.command.name : usedSlashCommand?.command, + slashCommand: agentSlashCommandPart ? agentSlashCommandPart.command.name : commandPart?.slashCommand.command, chatSessionId: model.sessionId }); model.setResponse(request, rawResponse); @@ -620,11 +605,6 @@ export class ChatService extends Disposable implements IChatService { model.setFollowups(request, followups); model.completeResponse(request); }); - } else if (provider.provideFollowups) { - Promise.resolve(provider.provideFollowups(model.session!, CancellationToken.None)).then(providerFollowups => { - model.setFollowups(request, providerFollowups ?? undefined); - model.completeResponse(request); - }); } else { model.completeResponse(request); } @@ -653,43 +633,6 @@ export class ChatService extends Disposable implements IChatService { } model.removeRequest(requestId); - provider.removeRequest?.(model.session!, requestId); - } - - async getSlashCommands(sessionId: string, token: CancellationToken): Promise { - const model = this._sessionModels.get(sessionId); - if (!model) { - throw new Error(`Unknown session: ${sessionId}`); - } - - await model.waitForInitialization(); - const provider = this._providers.get(model.providerId); - if (!provider) { - throw new Error(`Unknown provider: ${model.providerId}`); - } - - const serviceResults = this.chatSlashCommandService.getCommands().map(data => { - return { - command: data.command, - detail: data.detail, - sortText: data.sortText, - executeImmediately: data.executeImmediately - }; - }); - - const mainProviderRequest = provider.provideSlashCommands?.(model.session!, token); - - try { - const providerResults = await mainProviderRequest; - if (providerResults) { - return providerResults.concat(serviceResults); - } - return serviceResults; - - } catch (e) { - this.logService.error(e); - return serviceResults; - } } async sendRequestToProvider(sessionId: string, message: IChatDynamicRequest): Promise<{ responseCompletePromise: Promise } | undefined> { @@ -746,8 +689,7 @@ export class ChatService extends Disposable implements IChatService { this._persistedSessions[sessionId] = model.toJSON(); - model.dispose(); - this._sessionModels.delete(sessionId); + this._sessionModels.deleteAndDispose(sessionId); this._pendingRequests.get(sessionId)?.cancel(); this._onDidDisposeSession.fire({ sessionId, providerId: model.providerId, reason: 'cleared' }); } diff --git a/src/vs/workbench/contrib/chat/common/chatViewModel.ts b/src/vs/workbench/contrib/chat/common/chatViewModel.ts index d6337756df3..3a64a27a3c8 100644 --- a/src/vs/workbench/contrib/chat/common/chatViewModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatViewModel.ts @@ -55,7 +55,6 @@ export interface IChatViewModel { export interface IChatRequestViewModel { readonly id: string; - readonly providerRequestId: string | undefined; readonly sessionId: string; /** This ID updates every time the underlying data changes */ readonly dataId: string; @@ -88,7 +87,6 @@ export interface IChatResponseViewModel { /** This ID updates every time the underlying data changes */ readonly dataId: string; readonly providerId: string; - readonly providerResponseId: string | undefined; /** The ID of the associated IChatRequestViewModel */ readonly requestId: string; readonly username: string; @@ -173,12 +171,12 @@ export class ChatViewModel extends Disposable implements IChatViewModel { } else if (e.kind === 'addResponse') { this.onAddResponse(e.response); } else if (e.kind === 'removeRequest') { - const requestIdx = this._items.findIndex(item => isRequestVM(item) && item.providerRequestId === e.requestId); + const requestIdx = this._items.findIndex(item => isRequestVM(item) && item.id === e.requestId); if (requestIdx >= 0) { this._items.splice(requestIdx, 1); } - const responseIdx = e.responseId && this._items.findIndex(item => isResponseVM(item) && item.providerResponseId === e.responseId); + const responseIdx = e.responseId && this._items.findIndex(item => isResponseVM(item) && item.id === e.responseId); if (typeof responseIdx === 'number' && responseIdx >= 0) { const items = this._items.splice(responseIdx, 1); const item = items[0]; @@ -218,10 +216,6 @@ export class ChatRequestViewModel implements IChatRequestViewModel { return this._model.id; } - get providerRequestId() { - return this._model.providerRequestId; - } - get dataId() { return this.id + `_${ChatModelInitState[this._model.session.initState]}`; } @@ -269,10 +263,6 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi return this._model.providerId; } - get providerResponseId() { - return this._model.providerResponseId; - } - get sessionId() { return this._model.session.sessionId; } diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap index 3d2c801f677..e5e02c302c8 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap @@ -6,32 +6,53 @@ welcomeMessage: undefined, requests: [ { - providerRequestId: undefined, message: { - text: "test request", + text: "@ChatProviderWithUsedContext test request", parts: [ { range: { start: 0, - endExclusive: 12 + endExclusive: 28 }, editorRange: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, - endColumn: 13 + endColumn: 29 }, - text: "test request", + agent: { + id: "ChatProviderWithUsedContext", + metadata: { }, + provideSlashCommands: [Function provideSlashCommands], + invoke: [Function invoke] + }, + kind: "agent" + }, + { + range: { + start: 28, + endExclusive: 41 + }, + editorRange: { + startLineNumber: 1, + startColumn: 29, + endLineNumber: 1, + endColumn: 42 + }, + text: " test request", kind: "text" } ] }, response: [ ], responseErrorDetails: undefined, - followups: undefined, + followups: [ ], isCanceled: false, vote: undefined, - agent: undefined, + agent: { + id: "ChatProviderWithUsedContext", + metadata: { } + }, slashCommand: undefined, usedContext: { documents: [ { @@ -58,6 +79,5 @@ contentReferences: [ ] } ], - providerId: "ChatProviderWithUsedContext", - providerState: undefined + providerId: "testProvider" } \ No newline at end of file diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.0.snap index ba62978586c..0939983222f 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.0.snap @@ -5,6 +5,5 @@ responderAvatarIconUri: undefined, welcomeMessage: undefined, requests: [ ], - providerId: "ChatProviderWithUsedContext", - providerState: undefined + providerId: "testProvider" } \ No newline at end of file diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap index 140a7b80d27..178b1177890 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap @@ -6,32 +6,53 @@ welcomeMessage: undefined, requests: [ { - providerRequestId: undefined, message: { parts: [ { range: { start: 0, - endExclusive: 12 + endExclusive: 28 }, editorRange: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, - endColumn: 13 + endColumn: 29 }, - text: "test request", + agent: { + id: "ChatProviderWithUsedContext", + metadata: { }, + provideSlashCommands: [Function provideSlashCommands], + invoke: [Function invoke] + }, + kind: "agent" + }, + { + range: { + start: 28, + endExclusive: 41 + }, + editorRange: { + startLineNumber: 1, + startColumn: 29, + endLineNumber: 1, + endColumn: 42 + }, + text: " test request", kind: "text" } ], - text: "test request" + text: "@ChatProviderWithUsedContext test request" }, response: [ ], responseErrorDetails: undefined, - followups: undefined, + followups: [ ], isCanceled: false, vote: undefined, - agent: undefined, + agent: { + id: "ChatProviderWithUsedContext", + metadata: { } + }, slashCommand: undefined, usedContext: { documents: [ { @@ -58,6 +79,5 @@ contentReferences: [ ] } ], - providerId: "ChatProviderWithUsedContext", - providerState: undefined + providerId: "testProvider" } \ No newline at end of file diff --git a/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts b/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts index fdd44be5c58..56d9cf5d323 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts @@ -6,11 +6,14 @@ import * as assert from 'assert'; import { timeout } from 'vs/base/common/async'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { Range } from 'vs/editor/common/core/range'; +import { OffsetRange } from 'vs/editor/common/core/offsetRange'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ChatAgentService, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; +import { ChatRequestTextPart } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; @@ -98,4 +101,18 @@ suite('ChatModel', () => { assert.throws(() => model.initialize({} as any, undefined)); }); + + test('removeRequest', async () => { + const model = testDisposables.add(instantiationService.createInstance(ChatModel, 'provider', undefined)); + + model.startInitialize(); + model.initialize({} as any, undefined); + const text = 'hello'; + model.addRequest({ text, parts: [new ChatRequestTextPart(new OffsetRange(0, text.length), new Range(1, text.length, 1, text.length), text)] }); + const requests = model.getRequests(); + assert.strictEqual(requests.length, 1); + + model.removeRequest(requests[0].id); + assert.strictEqual(model.getRequests().length, 0); + }); }); diff --git a/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts b/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts index 7619ef46437..cd2aefa23cc 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts @@ -11,7 +11,7 @@ import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ChatAgentService, IChatAgent, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; -import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; @@ -48,9 +48,9 @@ suite('ChatRequestParser', () => { }); test('slash command', async () => { - const chatService = mockObject()({}); - chatService.getSlashCommands.returns(Promise.resolve([{ command: 'fix' }])); - instantiationService.stub(IChatService, chatService as any); + const slashCommandService = mockObject()({}); + slashCommandService.getCommands.returns([{ command: 'fix' }]); + instantiationService.stub(IChatSlashCommandService, slashCommandService as any); parser = instantiationService.createInstance(ChatRequestParser); const text = '/fix this'; @@ -59,9 +59,9 @@ suite('ChatRequestParser', () => { }); test('invalid slash command', async () => { - const chatService = mockObject()({}); - chatService.getSlashCommands.returns(Promise.resolve([{ command: 'fix' }])); - instantiationService.stub(IChatService, chatService as any); + const slashCommandService = mockObject()({}); + slashCommandService.getCommands.returns([{ command: 'fix' }]); + instantiationService.stub(IChatSlashCommandService, slashCommandService as any); parser = instantiationService.createInstance(ChatRequestParser); const text = '/explain this'; @@ -70,9 +70,9 @@ suite('ChatRequestParser', () => { }); test('multiple slash commands', async () => { - const chatService = mockObject()({}); - chatService.getSlashCommands.returns(Promise.resolve([{ command: 'fix' }])); - instantiationService.stub(IChatService, chatService as any); + const slashCommandService = mockObject()({}); + slashCommandService.getCommands.returns([{ command: 'fix' }]); + instantiationService.stub(IChatSlashCommandService, slashCommandService as any); parser = instantiationService.createInstance(ChatRequestParser); const text = '/fix /fix'; diff --git a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts index eebf8589487..e2ae43a90e1 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts @@ -5,7 +5,6 @@ import * as assert from 'assert'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { assertSnapshot } from 'vs/base/test/common/snapshot'; @@ -22,10 +21,10 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IViewsService } from 'vs/workbench/common/views'; -import { ChatAgentService, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { ChatAgentService, IChatAgent, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; import { ISerializableChatData } from 'vs/workbench/contrib/chat/common/chatModel'; -import { IChat, IChatProgress, IChatProvider, IChatRequest, IChatResponse, IPersistedChatState, ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChat, IChatProgress, IChatProvider, IChatRequest } from 'vs/workbench/contrib/chat/common/chatService'; import { ChatService } from 'vs/workbench/contrib/chat/common/chatServiceImpl'; import { ChatSlashCommandService, IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; @@ -36,29 +35,18 @@ import { TestContextService, TestExtensionService, TestStorageService } from 'vs class SimpleTestProvider extends Disposable implements IChatProvider { private static sessionId = 0; - lastInitialState = undefined; - readonly displayName = 'Test'; - private _onDidChangeState = this._register(new Emitter()); - constructor(readonly id: string) { super(); } - prepareSession(initialState: any) { - this.lastInitialState = initialState; - return Promise.resolve({ + async prepareSession(): Promise { + return { id: SimpleTestProvider.sessionId++, - username: 'test', responderUsername: 'test', requesterUsername: 'test', - onDidChangeState: this._onDidChangeState.event - }); - } - - changeState(state: any) { - this._onDidChangeState.fire(state); + }; } async provideReply(request: IChatRequest, progress: (progress: IChatProgress) => void): Promise<{ session: IChat; followups: never[] }> { @@ -66,10 +54,14 @@ class SimpleTestProvider extends Disposable implements IChatProvider { } } -/** Chat provider for testing that returns used context */ -class ChatProviderWithUsedContext extends SimpleTestProvider implements IChatProvider { - override provideReply(request: IChatRequest, progress: (progress: IChatProgress) => void): Promise<{ session: IChat; followups: never[] }> { - +const chatAgentWithUsedContextId = 'ChatProviderWithUsedContext'; +const chatAgentWithUsedContext: IChatAgent = { + id: chatAgentWithUsedContextId, + metadata: {}, + async provideSlashCommands(token) { + return []; + }, + async invoke(request, progress, history, token) { progress({ documents: [ { @@ -82,9 +74,9 @@ class ChatProviderWithUsedContext extends SimpleTestProvider implements IChatPro ] }); - return super.provideReply(request, progress); - } -} + return {}; + }, +}; suite('Chat', () => { const testDisposables = ensureNoDisposablesAreLeakedInTestSuite(); @@ -92,6 +84,8 @@ suite('Chat', () => { let storageService: IStorageService; let instantiationService: TestInstantiationService; + let chatAgentService: IChatAgentService; + setup(async () => { instantiationService = testDisposables.add(new TestInstantiationService(new ServiceCollection( [IChatVariablesService, new MockChatVariablesService()], @@ -105,7 +99,18 @@ suite('Chat', () => { instantiationService.stub(IChatContributionService, new TestExtensionService()); instantiationService.stub(IWorkspaceContextService, new TestContextService()); instantiationService.stub(IChatSlashCommandService, testDisposables.add(instantiationService.createInstance(ChatSlashCommandService))); - instantiationService.stub(IChatAgentService, testDisposables.add(instantiationService.createInstance(ChatAgentService))); + + chatAgentService = testDisposables.add(instantiationService.createInstance(ChatAgentService)); + instantiationService.stub(IChatAgentService, chatAgentService); + + const agent = { + id: 'testAgent', + metadata: { isDefault: true }, + async invoke(request, progress, history, token) { + return {}; + }, + } as IChatAgent; + testDisposables.add(chatAgentService.registerAgent(agent)); }); test('retrieveSession', async () => { @@ -123,12 +128,7 @@ suite('Chat', () => { await session2.waitForInitialization(); session2!.addRequest({ parts: [], text: 'request 2' }); - assert.strictEqual(provider1.lastInitialState, undefined); - assert.strictEqual(provider2.lastInitialState, undefined); - provider1.changeState({ state: 'provider1_state' }); - provider2.changeState({ state: 'provider2_state' }); storageService.flush(); - const testService2 = testDisposables.add(instantiationService.createInstance(ChatService)); testDisposables.add(testService2.registerProvider(provider1)); testDisposables.add(testService2.registerProvider(provider2)); @@ -136,8 +136,8 @@ suite('Chat', () => { await retrieved1!.waitForInitialization(); const retrieved2 = testDisposables.add(testService2.getOrRestoreSession(session2.sessionId)!); await retrieved2!.waitForInitialization(); - assert.deepStrictEqual(provider1.lastInitialState, { state: 'provider1_state' }); - assert.deepStrictEqual(provider2.lastInitialState, { state: 'provider2_state' }); + assert.deepStrictEqual(retrieved1.getRequests()[0]?.message.text, 'request 1'); + assert.deepStrictEqual(retrieved2.getRequests()[0]?.message.text, 'request 2'); }); test('Handles failed session startup', async () => { @@ -172,10 +172,7 @@ suite('Chat', () => { testDisposables.add(testService.registerProvider({ id, displayName: 'Test', - prepareSession: function (initialState: IPersistedChatState | undefined, token: CancellationToken): ProviderResult { - throw new Error('Function not implemented.'); - }, - provideReply: function (request: IChatRequest, progress: (progress: IChatProgress) => void, token: CancellationToken): ProviderResult { + prepareSession: function (token: CancellationToken): ProviderResult { throw new Error('Function not implemented.'); } })); @@ -184,45 +181,13 @@ suite('Chat', () => { testDisposables.add(testService.registerProvider({ id, displayName: 'Test', - prepareSession: function (initialState: IPersistedChatState | undefined, token: CancellationToken): ProviderResult { - throw new Error('Function not implemented.'); - }, - provideReply: function (request: IChatRequest, progress: (progress: IChatProgress) => void, token: CancellationToken): ProviderResult { + prepareSession: function (token: CancellationToken): ProviderResult { throw new Error('Function not implemented.'); } })); }, 'Expected to throw for dupe provider'); }); - test('getSlashCommands', async () => { - const testService = testDisposables.add(instantiationService.createInstance(ChatService)); - const provider = testDisposables.add(new class extends SimpleTestProvider { - constructor() { - super('testProvider'); - } - - provideSlashCommands(): ProviderResult { - return [ - { - command: 'command', - detail: 'detail', - sortText: 'sortText', - } - ]; - } - }); - - testDisposables.add(testService.registerProvider(provider)); - - const model = testDisposables.add(testService.startSession('testProvider', CancellationToken.None)); - const commands = await testService.getSlashCommands(model.sessionId, CancellationToken.None); - - assert.strictEqual(commands?.length, 1); - assert.strictEqual(commands?.[0].command, 'command'); - assert.strictEqual(commands?.[0].detail, 'detail'); - assert.strictEqual(commands?.[0].sortText, 'sortText'); - }); - test('sendRequestToProvider', async () => { const testService = testDisposables.add(instantiationService.createInstance(ChatService)); testDisposables.add(testService.registerProvider(testDisposables.add(new SimpleTestProvider('testProvider')))); @@ -249,18 +214,16 @@ suite('Chat', () => { }); test('can serialize', async () => { - + testDisposables.add(chatAgentService.registerAgent(chatAgentWithUsedContext)); const testService = testDisposables.add(instantiationService.createInstance(ChatService)); - const providerId = 'ChatProviderWithUsedContext'; + testDisposables.add(testService.registerProvider(testDisposables.add(new SimpleTestProvider('testProvider')))); - testDisposables.add(testService.registerProvider(testDisposables.add(new ChatProviderWithUsedContext(providerId)))); - - const model = testDisposables.add(testService.startSession(providerId, CancellationToken.None)); + const model = testDisposables.add(testService.startSession('testProvider', CancellationToken.None)); assert.strictEqual(model.getRequests().length, 0); await assertSnapshot(model.toExport()); - const response = await testService.sendRequest(model.sessionId, 'test request'); + const response = await testService.sendRequest(model.sessionId, `@${chatAgentWithUsedContextId} test request`); assert(response); await response.responseCompletePromise; @@ -271,21 +234,18 @@ suite('Chat', () => { }); test('can deserialize', async () => { - let serializedChatData: ISerializableChatData; - - const providerId = 'ChatProviderWithUsedContext'; + testDisposables.add(chatAgentService.registerAgent(chatAgentWithUsedContext)); // create the first service, send request, get response, and serialize the state { // serapate block to not leak variables in outer scope const testService = testDisposables.add(instantiationService.createInstance(ChatService)); + testDisposables.add(testService.registerProvider(testDisposables.add(new SimpleTestProvider('testProvider')))); - testDisposables.add(testService.registerProvider(testDisposables.add(new ChatProviderWithUsedContext(providerId)))); - - const chatModel1 = testDisposables.add(testService.startSession(providerId, CancellationToken.None)); + const chatModel1 = testDisposables.add(testService.startSession('testProvider', CancellationToken.None)); assert.strictEqual(chatModel1.getRequests().length, 0); - const response = await testService.sendRequest(chatModel1.sessionId, 'test request'); + const response = await testService.sendRequest(chatModel1.sessionId, `@${chatAgentWithUsedContextId} test request`); assert(response); await response.responseCompletePromise; @@ -296,17 +256,11 @@ suite('Chat', () => { // try deserializing the state into a new service const testService2 = testDisposables.add(instantiationService.createInstance(ChatService)); - - testDisposables.add(testService2.registerProvider(testDisposables.add(new ChatProviderWithUsedContext(providerId)))); + testDisposables.add(testService2.registerProvider(testDisposables.add(new SimpleTestProvider('testProvider')))); const chatModel2 = testService2.loadSessionFromContent(serializedChatData); assert(chatModel2); - // should `loadSessionFromContent` return `ChatModel` that's disposable instead of `IChatModel`? - testDisposables.add({ - dispose: () => testService2.clearSession(serializedChatData.sessionId) - }); - await assertSnapshot(chatModel2.toExport()); }); }); diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 577da2ca078..b9bc1798098 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -11,7 +11,6 @@ export const allApiProposals = Object.freeze({ authSession: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.authSession.d.ts', canonicalUriProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.canonicalUriProvider.d.ts', chat: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chat.d.ts', - chatAgents: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatAgents.d.ts', chatAgents2: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatAgents2.d.ts', chatAgents2Additions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts', chatProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatProvider.d.ts', diff --git a/src/vscode-dts/vscode.proposed.chatAgents.d.ts b/src/vscode-dts/vscode.proposed.chatAgents.d.ts deleted file mode 100644 index 6f52c6498df..00000000000 --- a/src/vscode-dts/vscode.proposed.chatAgents.d.ts +++ /dev/null @@ -1,39 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -declare module 'vscode' { - - export interface ChatAgentContext { - history: ChatMessage[]; - } - - export interface ChatAgentResponse { - message: MarkdownString | InteractiveProgressFileTree; - } - - export interface ChatAgentResult { - followUp?: InteractiveSessionFollowup[]; - } - - export interface ChatAgentCommand { - name: string; - description: string; - } - - export interface ChatAgentMetadata { - description: string; - fullName?: string; - icon?: Uri; - subCommands: ChatAgentCommand[]; - } - - export interface ChatAgent { - (prompt: ChatMessage, context: ChatAgentContext, progress: Progress, token: CancellationToken): Thenable; - } - - export namespace chat { - export function registerAgent(id: string, agent: ChatAgent, metadata: ChatAgentMetadata): Disposable; - } -} diff --git a/src/vscode-dts/vscode.proposed.interactive.d.ts b/src/vscode-dts/vscode.proposed.interactive.d.ts index 5cd71adbc05..8595d59c3ab 100644 --- a/src/vscode-dts/vscode.proposed.interactive.d.ts +++ b/src/vscode-dts/vscode.proposed.interactive.d.ts @@ -85,9 +85,6 @@ declare module 'vscode' { handleInteractiveEditorResponseFeedback?(session: S, response: R, kind: InteractiveEditorResponseFeedbackKind): void; } - - export interface InteractiveSessionState { } - export interface InteractiveSessionParticipantInformation { name: string; @@ -101,81 +98,8 @@ declare module 'vscode' { requester: InteractiveSessionParticipantInformation; responder: InteractiveSessionParticipantInformation; inputPlaceholder?: string; - - saveState?(): InteractiveSessionState; } - export interface InteractiveSessionRequestArgs { - command: string; - args: any; - } - - export interface InteractiveRequest { - session: InteractiveSession; - message: string; - } - - export interface InteractiveResponseErrorDetails { - message: string; - responseIsIncomplete?: boolean; - responseIsFiltered?: boolean; - } - - export interface InteractiveResponseForProgress { - errorDetails?: InteractiveResponseErrorDetails; - } - - export interface InteractiveContentReference { - reference: Uri | Location; - } - - export interface InteractiveInlineContentReference { - inlineReference: Uri | Location; - title?: string; // eg symbol name - } - - export interface InteractiveProgressContent { - content: string | MarkdownString; - } - - export interface InteractiveProgressId { - responseId: string; - } - - export interface InteractiveProgressTask { - placeholder: string; - resolvedContent: Thenable; - } - - export interface FileTreeData { - label: string; - uri: Uri; - children?: FileTreeData[]; - } - - export interface InteractiveProgressFileTree { - treeData: FileTreeData; - } - - export interface DocumentContext { - uri: Uri; - version: number; - ranges: Range[]; - } - - export interface InteractiveProgressUsedContext { - documents: DocumentContext[]; - } - - export type InteractiveProgress = - | InteractiveProgressContent - | InteractiveProgressId - | InteractiveProgressTask - | InteractiveProgressFileTree - | InteractiveProgressUsedContext - | InteractiveContentReference - | InteractiveInlineContentReference; - export interface InteractiveResponseCommand { commandId: string; args?: any[]; @@ -183,40 +107,18 @@ declare module 'vscode' { when?: string; } - export interface InteractiveSessionSlashCommand { - command: string; - kind: CompletionItemKind; - detail?: string; - shouldRepopulate?: boolean; - followupPlaceholder?: string; - executeImmediately?: boolean; - yieldTo?: ReadonlyArray<{ readonly command: string }>; - } - export interface InteractiveSessionReplyFollowup { message: string; tooltip?: string; title?: string; - - // Extensions can put any serializable data here, such as an ID/version - metadata?: any; } - export type InteractiveSessionFollowup = InteractiveSessionReplyFollowup | InteractiveResponseCommand; - export type InteractiveWelcomeMessageContent = string | MarkdownString | InteractiveSessionReplyFollowup[]; export interface InteractiveSessionProvider { provideWelcomeMessage?(token: CancellationToken): ProviderResult; provideSampleQuestions?(token: CancellationToken): ProviderResult; - provideFollowups?(session: S, token: CancellationToken): ProviderResult<(string | InteractiveSessionFollowup)[]>; - provideSlashCommands?(session: S, token: CancellationToken): ProviderResult; - - prepareSession(initialState: InteractiveSessionState | undefined, token: CancellationToken): ProviderResult; - provideResponseWithProgress(request: InteractiveRequest, progress: Progress, token: CancellationToken): ProviderResult; - - // eslint-disable-next-line local/vscode-dts-provider-naming - removeRequest(session: S, requestId: string): void; + prepareSession(token: CancellationToken): ProviderResult; } export interface InteractiveSessionDynamicRequest { @@ -224,12 +126,6 @@ declare module 'vscode' { * The message that will be displayed in the UI */ message: string; - - /** - * Any extra metadata/context that will go to the provider. - * NOTE not actually used yet. - */ - metadata?: any; } export namespace interactive { diff --git a/src/vscode-dts/vscode.proposed.interactiveUserActions.d.ts b/src/vscode-dts/vscode.proposed.interactiveUserActions.d.ts index e038f1f2ed6..976651c2675 100644 --- a/src/vscode-dts/vscode.proposed.interactiveUserActions.d.ts +++ b/src/vscode-dts/vscode.proposed.interactiveUserActions.d.ts @@ -13,8 +13,6 @@ declare module 'vscode' { export interface InteractiveSessionVoteAction { // eslint-disable-next-line local/vscode-dts-string-type-literals kind: 'vote'; - // sessionId: string; - responseId: string; direction: InteractiveSessionVoteDirection; } @@ -27,8 +25,6 @@ declare module 'vscode' { export interface InteractiveSessionCopyAction { // eslint-disable-next-line local/vscode-dts-string-type-literals kind: 'copy'; - // sessionId: string; - responseId: string; codeBlockIndex: number; copyType: InteractiveSessionCopyKind; copiedCharacters: number; @@ -39,8 +35,6 @@ declare module 'vscode' { export interface InteractiveSessionInsertAction { // eslint-disable-next-line local/vscode-dts-string-type-literals kind: 'insert'; - // sessionId: string; - responseId: string; codeBlockIndex: number; totalCharacters: number; newFile?: boolean; @@ -49,8 +43,6 @@ declare module 'vscode' { export interface InteractiveSessionTerminalAction { // eslint-disable-next-line local/vscode-dts-string-type-literals kind: 'runInTerminal'; - // sessionId: string; - responseId: string; codeBlockIndex: number; languageId?: string; }