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 cd8614a86e7..1fe4909bbac 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 { commands, CancellationToken, ChatContext, ChatRequest, ChatResult, ChatVariableLevel, Disposable, Event, EventEmitter, InteractiveSession, ProviderResult, chat, interactive } from 'vscode'; +import { ChatContext, ChatRequest, ChatResult, ChatVariableLevel, Disposable, Event, EventEmitter, chat, commands } from 'vscode'; import { DeferredPromise, assertNoRpc, closeAllEditors, disposeAll } from '../utils'; suite('chat', () => { @@ -31,14 +31,6 @@ suite('chat', () => { function setupParticipant(): Event<{ request: ChatRequest; context: ChatContext }> { const emitter = new EventEmitter<{ request: ChatRequest; context: ChatContext }>(); disposables.push(emitter); - disposables.push(interactive.registerInteractiveSessionProvider('provider', { - prepareSession: (_token: CancellationToken): ProviderResult => { - return { - requester: { name: 'test' }, - responder: { name: 'test' }, - }; - }, - })); const participant = chat.createChatParticipant('api-test.participant', (request, context, _progress, _token) => { emitter.fire({ request, context }); @@ -52,19 +44,29 @@ suite('chat', () => { const onRequest = setupParticipant(); commands.executeCommand('workbench.action.chat.open', { query: '@participant /hello friend' }); + const deferred = new DeferredPromise(); let i = 0; disposables.push(onRequest(request => { - if (i === 0) { - assert.deepStrictEqual(request.request.command, 'hello'); - assert.strictEqual(request.request.prompt, 'friend'); - i++; - commands.executeCommand('workbench.action.chat.open', { query: '@participant /hello friend' }); - } else { - assert.strictEqual(request.context.history.length, 1); - assert.strictEqual(request.context.history[0].participant, 'api-test.participant'); - assert.strictEqual(request.context.history[0].command, 'hello'); + try { + if (i === 0) { + assert.deepStrictEqual(request.request.command, 'hello'); + assert.strictEqual(request.request.prompt, 'friend'); + i++; + setTimeout(() => { + commands.executeCommand('workbench.action.chat.open', { query: '@participant /hello friend' }); + }, 0); + } else { + assert.strictEqual(request.context.history.length, 2); + assert.strictEqual(request.context.history[0].participant, 'api-test.participant'); + assert.strictEqual(request.context.history[0].command, 'hello'); + deferred.complete(); + } + } catch (e) { + deferred.error(e); } })); + + await deferred.p; }); test('participant and variable', async () => { @@ -82,15 +84,6 @@ suite('chat', () => { }); test('result metadata is returned to the followup provider', async () => { - disposables.push(interactive.registerInteractiveSessionProvider('provider', { - prepareSession: (_token: CancellationToken): ProviderResult => { - return { - requester: { name: 'test' }, - responder: { name: 'test' }, - }; - }, - })); - const deferred = new DeferredPromise(); const participant = chat.createChatParticipant('api-test.participant', (_request, _context, _progress, _token) => { return { metadata: { key: 'value' } }; diff --git a/src/vs/workbench/api/browser/extensionHost.contribution.ts b/src/vs/workbench/api/browser/extensionHost.contribution.ts index 1b0939e0f77..b8d406e1691 100644 --- a/src/vs/workbench/api/browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/browser/extensionHost.contribution.ts @@ -76,7 +76,6 @@ import './mainThreadNotebookRenderers'; import './mainThreadNotebookSaveParticipant'; import './mainThreadInteractive'; import './mainThreadInlineChat'; -import './mainThreadChat'; import './mainThreadTask'; import './mainThreadLabelService'; import './mainThreadTunnelService'; diff --git a/src/vs/workbench/api/browser/mainThreadChat.ts b/src/vs/workbench/api/browser/mainThreadChat.ts deleted file mode 100644 index 3a85f9dc1be..00000000000 --- a/src/vs/workbench/api/browser/mainThreadChat.ts +++ /dev/null @@ -1,84 +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 { Emitter } from 'vs/base/common/event'; -import { Disposable, DisposableMap } from 'vs/base/common/lifecycle'; -import { URI, UriComponents } from 'vs/base/common/uri'; -import { ILogService } from 'vs/platform/log/common/log'; -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 { 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 _stateEmitters = new Map>(); - - private readonly _proxy: ExtHostChatShape; - - constructor( - extHostContext: IExtHostContext, - @IChatService private readonly _chatService: IChatService, - @IChatWidgetService private readonly _chatWidgetService: IChatWidgetService, - @IChatContributionService private readonly _chatContribService: IChatContributionService, - @ILogService private readonly _logService: ILogService, - ) { - super(); - this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChat); - } - - $transferActiveChatSession(toWorkspace: UriComponents): void { - const widget = this._chatWidgetService.lastFocusedWidget; - const sessionId = widget?.viewModel?.model.sessionId; - if (!sessionId) { - this._logService.error(`MainThreadChat#$transferActiveChatSession: No active chat session found`); - return; - } - - const inputValue = widget?.inputEditor.getValue() ?? ''; - this._chatService.transferChatSession({ sessionId, inputValue }, URI.revive(toWorkspace)); - } - - async $registerChatProvider(handle: number, id: string): Promise { - const registration = this._chatContribService.registeredProviders.find(staticProvider => staticProvider.id === id); - if (!registration) { - throw new Error(`Provider ${id} must be declared in the package.json.`); - } - - const unreg = this._chatService.registerProvider({ - id, - prepareSession: async (token) => { - const session = await this._proxy.$prepareChat(handle, token); - if (!session) { - return undefined; - } - - const emitter = new Emitter(); - this._stateEmitters.set(session.id, emitter); - return { - id: session.id, - dispose: () => { - emitter.dispose(); - this._stateEmitters.delete(session.id); - this._proxy.$releaseSession(session.id); - } - }; - }, - }); - - this._providerRegistrations.set(handle, unreg); - } - - async $acceptChatState(sessionId: number, state: any): Promise { - this._stateEmitters.get(sessionId)?.fire(state); - } - - async $unregisterChatProvider(handle: number): Promise { - this._providerRegistrations.deleteAndDispose(handle); - } -} diff --git a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts index 0aaaee8cf14..7fcb534f6d8 100644 --- a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts +++ b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts @@ -7,6 +7,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Disposable, DisposableMap, IDisposable } from 'vs/base/common/lifecycle'; import { revive } from 'vs/base/common/marshalling'; import { escapeRegExpCharacters } from 'vs/base/common/strings'; +import { URI, UriComponents } from 'vs/base/common/uri'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { getWordAtText } from 'vs/editor/common/core/wordHelper'; @@ -15,6 +16,7 @@ import { ITextModel } from 'vs/editor/common/model'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostChatAgentsShape2, ExtHostContext, IChatProgressDto, IExtensionChatAgentMetadata, MainContext, MainThreadChatAgentsShape2 } from 'vs/workbench/api/common/extHost.protocol'; import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart'; @@ -48,6 +50,7 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, @IChatWidgetService private readonly _chatWidgetService: IChatWidgetService, @IInstantiationService private readonly _instantiationService: IInstantiationService, + @ILogService private readonly _logService: ILogService, ) { super(); this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatAgents2); @@ -75,6 +78,18 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA this._agents.deleteAndDispose(handle); } + $transferActiveChatSession(toWorkspace: UriComponents): void { + const widget = this._chatWidgetService.lastFocusedWidget; + const sessionId = widget?.viewModel?.model.sessionId; + if (!sessionId) { + this._logService.error(`MainThreadChat#$transferActiveChatSession: No active chat session found`); + return; + } + + const inputValue = widget?.inputEditor.getValue() ?? ''; + this._chatService.transferChatSession({ sessionId, inputValue }, URI.revive(toWorkspace)); + } + $registerAgent(handle: number, extension: ExtensionIdentifier, id: string, metadata: IExtensionChatAgentMetadata, dynamicProps: { name: string; description: string } | undefined): void { const staticAgentRegistration = this._chatAgentService.getAgent(id); if (!staticAgentRegistration && !dynamicProps) { diff --git a/src/vs/workbench/api/browser/mainThreadEditorTabs.ts b/src/vs/workbench/api/browser/mainThreadEditorTabs.ts index 880bc7dee23..a4ca80ada36 100644 --- a/src/vs/workbench/api/browser/mainThreadEditorTabs.ts +++ b/src/vs/workbench/api/browser/mainThreadEditorTabs.ts @@ -196,7 +196,6 @@ export class MainThreadEditorTabs implements MainThreadEditorTabsShape { if (editor instanceof ChatEditorInput) { return { kind: TabInputKind.ChatEditorInput, - providerId: editor.providerId ?? 'unknown', }; } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 5211bd91579..1599c9e4500 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -6,7 +6,7 @@ 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 { Disposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { Schemas, matchesScheme } from 'vs/base/common/network'; import Severity from 'vs/base/common/severity'; import { URI } from 'vs/base/common/uri'; @@ -27,9 +27,7 @@ import { ExtHostApiCommands } from 'vs/workbench/api/common/extHostApiCommands'; import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; import { IExtHostAuthentication } 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 { IExtHostLanguageModels } from 'vs/workbench/api/common/extHostLanguageModels'; import { ExtHostChatVariables } from 'vs/workbench/api/common/extHostChatVariables'; import { ExtHostClipboard } from 'vs/workbench/api/common/extHostClipboard'; import { ExtHostEditorInsets } from 'vs/workbench/api/common/extHostCodeInsets'; @@ -57,6 +55,7 @@ import { ExtHostInteractiveEditor } from 'vs/workbench/api/common/extHostInlineC import { ExtHostInteractive } from 'vs/workbench/api/common/extHostInteractive'; import { ExtHostLabelService } from 'vs/workbench/api/common/extHostLabelService'; import { ExtHostLanguageFeatures } from 'vs/workbench/api/common/extHostLanguageFeatures'; +import { IExtHostLanguageModels } from 'vs/workbench/api/common/extHostLanguageModels'; import { ExtHostLanguages } from 'vs/workbench/api/common/extHostLanguages'; import { IExtHostLocalizationService } from 'vs/workbench/api/common/extHostLocalizationService'; import { IExtHostManagedSockets } from 'vs/workbench/api/common/extHostManagedSockets'; @@ -84,6 +83,7 @@ import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePa 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 { IExtHostTerminalShellIntegration } from 'vs/workbench/api/common/extHostTerminalShellIntegration'; import { ExtHostTesting } from 'vs/workbench/api/common/extHostTesting'; import { ExtHostEditors } from 'vs/workbench/api/common/extHostTextEditors'; import { ExtHostTheming } from 'vs/workbench/api/common/extHostTheming'; @@ -107,7 +107,6 @@ import { checkProposedApiEnabled, isProposedApiEnabled } from 'vs/workbench/serv import { ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import { TextSearchCompleteMessageType } from 'vs/workbench/services/search/common/searchExtTypes'; import type * as vscode from 'vscode'; -import { IExtHostTerminalShellIntegration } from 'vs/workbench/api/common/extHostTerminalShellIntegration'; export interface IExtensionRegistries { mine: ExtensionDescriptionRegistry; @@ -213,7 +212,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostInteractiveEditor = rpcProtocol.set(ExtHostContext.ExtHostInlineChat, new ExtHostInteractiveEditor(rpcProtocol, extHostCommands, extHostDocuments, extHostLogService)); const extHostChatAgents2 = rpcProtocol.set(ExtHostContext.ExtHostChatAgents2, new ExtHostChatAgents2(rpcProtocol, extHostLogService, extHostCommands)); const extHostChatVariables = rpcProtocol.set(ExtHostContext.ExtHostChatVariables, new ExtHostChatVariables(rpcProtocol)); - 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 extHostStatusBar = rpcProtocol.set(ExtHostContext.ExtHostStatusBar, new ExtHostStatusBar(rpcProtocol, extHostCommands.converter)); @@ -1392,12 +1390,13 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostInteractiveEditor.registerProvider(extension, provider, metadata); }, registerInteractiveSessionProvider(id: string, provider: vscode.InteractiveSessionProvider) { + // temp stub checkProposedApiEnabled(extension, 'interactive'); - return extHostChat.registerChatProvider(extension, id, provider); + return Disposable.None; }, transferActiveChat(toWorkspace: vscode.Uri) { checkProposedApiEnabled(extension, 'interactive'); - return extHostChat.transferActiveChat(toWorkspace); + return extHostChatAgents2.transferActiveChat(toWorkspace); } }; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index ca40d5c6e87..3cd16ac8580 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -769,7 +769,6 @@ export interface InteractiveEditorInputDto { export interface ChatEditorInputDto { kind: TabInputKind.ChatEditorInput; - providerId: string; } export interface MultiDiffEditorInputDto { @@ -1219,6 +1218,8 @@ export interface MainThreadChatAgentsShape2 extends IDisposable { $updateAgent(handle: number, metadataUpdate: IExtensionChatAgentMetadata): void; $unregisterAgent(handle: number): void; $handleProgressChunk(requestId: string, chunk: IChatProgressDto): Promise; + + $transferActiveChatSession(toWorkspace: UriComponents): void; } export interface IChatAgentCompletionItem { @@ -1287,7 +1288,6 @@ export interface MainThreadUrlsShape extends IDisposable { } export interface IChatDto { - id: number; } export interface IChatRequestDto { @@ -1318,18 +1318,6 @@ export type IDocumentContextDto = { export type IChatProgressDto = | Dto; -export interface MainThreadChatShape extends IDisposable { - $registerChatProvider(handle: number, id: string): Promise; - $acceptChatState(sessionId: number, state: any): Promise; - $unregisterChatProvider(handle: number): Promise; - $transferActiveChatSession(toWorkspace: UriComponents): void; -} - -export interface ExtHostChatShape { - $prepareChat(handle: number, token: CancellationToken): Promise; - $releaseSession(sessionId: number): void; -} - export interface ExtHostUrlsShape { $handleExternalUri(handle: number, uri: UriComponents): Promise; } @@ -2839,7 +2827,6 @@ export const MainContext = { MainThreadNotebookKernels: createProxyIdentifier('MainThreadNotebookKernels'), MainThreadNotebookRenderers: createProxyIdentifier('MainThreadNotebookRenderers'), MainThreadInteractive: createProxyIdentifier('MainThreadInteractive'), - MainThreadChat: createProxyIdentifier('MainThreadChat'), MainThreadInlineChat: createProxyIdentifier('MainThreadInlineChatShape'), MainThreadTheming: createProxyIdentifier('MainThreadTheming'), MainThreadTunnelService: createProxyIdentifier('MainThreadTunnelService'), @@ -2904,7 +2891,6 @@ export const ExtHostContext = { ExtHostNotebookDocumentSaveParticipant: createProxyIdentifier('ExtHostNotebookDocumentSaveParticipant'), ExtHostInteractive: createProxyIdentifier('ExtHostInteractive'), ExtHostInlineChat: createProxyIdentifier('ExtHostInlineChatShape'), - ExtHostChat: createProxyIdentifier('ExtHostChat'), 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 deleted file mode 100644 index 38199dfef3b..00000000000 --- a/src/vs/workbench/api/common/extHostChat.ts +++ /dev/null @@ -1,79 +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 { CancellationToken } from 'vs/base/common/cancellation'; -import { toDisposable } from 'vs/base/common/lifecycle'; -import { IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { ExtHostChatShape, IChatDto, IMainContext, MainContext, MainThreadChatShape } from 'vs/workbench/api/common/extHost.protocol'; -import type * as vscode from 'vscode'; - -class ChatProviderWrapper { - - private static _pool = 0; - - readonly handle: number = ChatProviderWrapper._pool++; - - constructor( - readonly extension: Readonly, - readonly provider: T, - ) { } -} - -export class ExtHostChat implements ExtHostChatShape { - private static _nextId = 0; - - private readonly _chatProvider = new Map>(); - - private readonly _chatSessions = new Map(); - - private readonly _proxy: MainThreadChatShape; - - constructor( - mainContext: IMainContext, - ) { - this._proxy = mainContext.getProxy(MainContext.MainThreadChat); - } - - //#region interactive session - - registerChatProvider(extension: Readonly, id: string, provider: vscode.InteractiveSessionProvider): vscode.Disposable { - const wrapper = new ChatProviderWrapper(extension, provider); - this._chatProvider.set(wrapper.handle, wrapper); - this._proxy.$registerChatProvider(wrapper.handle, id); - return toDisposable(() => { - this._proxy.$unregisterChatProvider(wrapper.handle); - this._chatProvider.delete(wrapper.handle); - }); - } - - transferActiveChat(newWorkspace: vscode.Uri): void { - this._proxy.$transferActiveChatSession(newWorkspace); - } - - async $prepareChat(handle: number, token: CancellationToken): Promise { - const entry = this._chatProvider.get(handle); - if (!entry) { - return undefined; - } - - const session = await entry.provider.prepareSession(token); - if (!session) { - return undefined; - } - - const id = ExtHostChat._nextId++; - this._chatSessions.set(id, session); - - return { - id, - }; - } - - $releaseSession(sessionId: number) { - this._chatSessions.delete(sessionId); - } - - //#endregion -} diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index b2fc70c4346..8fa58e97724 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -213,6 +213,10 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { this._proxy = mainContext.getProxy(MainContext.MainThreadChatAgents2); } + transferActiveChat(newWorkspace: vscode.Uri): void { + this._proxy.$transferActiveChatSession(newWorkspace); + } + createChatAgent(extension: IExtensionDescription, id: string, handler: vscode.ChatExtendedRequestHandler): vscode.ChatParticipant { const handle = ExtHostChatAgents2._idPool++; const agent = new ExtHostChatAgent(extension, id, this._proxy, handle, handler); diff --git a/src/vs/workbench/api/common/extHostEditorTabs.ts b/src/vs/workbench/api/common/extHostEditorTabs.ts index d0e035825a6..3f89c17b81f 100644 --- a/src/vs/workbench/api/common/extHostEditorTabs.ts +++ b/src/vs/workbench/api/common/extHostEditorTabs.ts @@ -99,7 +99,7 @@ class ExtHostEditorTab { case TabInputKind.InteractiveEditorInput: return new InteractiveWindowInput(URI.revive(this._dto.input.uri), URI.revive(this._dto.input.inputBoxUri)); case TabInputKind.ChatEditorInput: - return new ChatEditorTabInput(this._dto.input.providerId); + return new ChatEditorTabInput(); case TabInputKind.MultiDiffEditorInput: return new TextMultiDiffTabInput(this._dto.input.diffEditors.map(diff => new TextDiffTabInput(URI.revive(diff.original), URI.revive(diff.modified)))); default: diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index d177924bb00..52ede752dd3 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4207,7 +4207,7 @@ export class InteractiveWindowInput { } export class ChatEditorTabInput { - constructor(readonly providerId: string) { } + constructor() { } } export class TextMultiDiffTabInput { diff --git a/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts b/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts index 10f0392f382..189bbf2a96e 100644 --- a/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts @@ -18,7 +18,7 @@ import { AccessibleViewProviderId, AccessibilityVerbositySettingId } from 'vs/wo import { descriptionForCommand } from 'vs/workbench/contrib/accessibility/browser/accessibleViewContributions'; import { IAccessibleViewService, IAccessibleContentProvider, IAccessibleViewOptions, AccessibleViewType } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; -import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +import { CONTEXT_HAS_DEFAULT_AGENT } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { CommentAccessibilityHelpNLS } from 'vs/workbench/contrib/comments/browser/commentsAccessibility'; import { CommentCommandId } from 'vs/workbench/contrib/comments/common/commentCommandIds'; import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/commentContextKeys'; @@ -117,7 +117,7 @@ export function getCommentCommandInfo(keybindingService: IKeybindingService, con } export function getChatCommandInfo(keybindingService: IKeybindingService, contextKeyService: IContextKeyService): string | undefined { - if (CONTEXT_PROVIDER_EXISTS.getValue(contextKeyService)) { + if (CONTEXT_HAS_DEFAULT_AGENT.getValue(contextKeyService)) { const commentCommandInfo: string[] = []; commentCommandInfo.push(descriptionForCommand('workbench.action.quickchat.toggle', AccessibilityHelpNLS.quickChat, AccessibilityHelpNLS.quickChatNoKb, keybindingService)); commentCommandInfo.push(descriptionForCommand('inlineChat.start', AccessibilityHelpNLS.startInlineChat, AccessibilityHelpNLS.startInlineChatNoKb, keybindingService)); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index 3c5fb0a252f..8b512d690de 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -11,7 +11,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction2, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { localize, localize2 } from 'vs/nls'; -import { Action2, IAction2Options, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IsLinuxContext, IsWindowsContext } from 'vs/platform/contextkey/common/contextkeys'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -21,19 +21,26 @@ import { ViewAction } from 'vs/workbench/browser/parts/views/viewPane'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; import { runAccessibilityHelpAction } from 'vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp'; -import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; +import { CHAT_VIEW_ID, IChatWidgetService, showChatView } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatEditor'; import { ChatEditorInput } from 'vs/workbench/contrib/chat/browser/chatEditorInput'; import { ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane'; import { ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { CONTEXT_CHAT_INPUT_CURSOR_AT_TOP, CONTEXT_CHAT_LOCATION, CONTEXT_IN_CHAT_INPUT, CONTEXT_IN_CHAT_SESSION, CONTEXT_PROVIDER_EXISTS, CONTEXT_REQUEST, CONTEXT_RESPONSE } from 'vs/workbench/contrib/chat/common/chatContextKeys'; -import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; +import { CONTEXT_CHAT_INPUT_CURSOR_AT_TOP, CONTEXT_CHAT_LOCATION, CONTEXT_IN_CHAT_INPUT, CONTEXT_IN_CHAT_SESSION, CONTEXT_HAS_DEFAULT_AGENT, CONTEXT_REQUEST, CONTEXT_RESPONSE } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatDetail, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatWidgetHistoryService } from 'vs/workbench/contrib/chat/common/chatWidgetHistoryService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; +export interface IChatViewTitleActionContext { + chatView: ChatViewPane; +} + +export function isChatViewTitleActionContext(obj: unknown): obj is IChatViewTitleActionContext { + return obj instanceof Object && 'chatView' in obj; +} + export const CHAT_CATEGORY = localize2('chat.category', 'Chat'); export const CHAT_OPEN_ACTION_ID = 'workbench.action.chat.open'; @@ -53,7 +60,7 @@ class OpenChatGlobalAction extends Action2 { super({ id: CHAT_OPEN_ACTION_ID, title: localize2('openChat', "Open Chat"), - precondition: CONTEXT_PROVIDER_EXISTS, + precondition: CONTEXT_HAS_DEFAULT_AGENT, icon: Codicon.commentDiscussion, f1: false, category: CHAT_CATEGORY, @@ -70,13 +77,7 @@ class OpenChatGlobalAction extends Action2 { override async run(accessor: ServicesAccessor, opts?: string | IChatViewOpenOptions): Promise { opts = typeof opts === 'string' ? { query: opts } : opts; - const chatService = accessor.get(IChatService); - const chatWidgetService = accessor.get(IChatWidgetService); - const providers = chatService.getProviderInfos(); - if (!providers.length) { - return; - } - const chatWidget = await chatWidgetService.revealViewForProvider(providers[0].id); + const chatWidget = await showChatView(accessor.get(IViewsService)); if (!chatWidget) { return; } @@ -92,15 +93,82 @@ class OpenChatGlobalAction extends Action2 { } } +class ChatHistoryAction extends ViewAction { + constructor() { + super({ + viewId: CHAT_VIEW_ID, + id: `workbench.action.chat.history`, + title: localize2('chat.history.label', "Show Chats..."), + menu: { + id: MenuId.ViewTitle, + when: ContextKeyExpr.equals('view', CHAT_VIEW_ID), + group: 'navigation', + order: -1 + }, + category: CHAT_CATEGORY, + icon: Codicon.history, + f1: true, + precondition: CONTEXT_HAS_DEFAULT_AGENT + }); + } + + async runInView(accessor: ServicesAccessor, view: ChatViewPane) { + const chatService = accessor.get(IChatService); + const quickInputService = accessor.get(IQuickInputService); + const viewsService = accessor.get(IViewsService); + const items = chatService.getHistory(); + const picks = items.map(i => ({ + label: i.title, + chat: i, + buttons: [{ + iconClass: ThemeIcon.asClassName(Codicon.x), + tooltip: localize('interactiveSession.history.delete', "Delete"), + }] + })); + const selection = await quickInputService.pick(picks, + { + placeHolder: localize('interactiveSession.history.pick', "Switch to chat"), + onDidTriggerItemButton: context => { + chatService.removeHistoryEntry(context.item.chat.sessionId); + context.removeItem(); + } + }); + if (selection) { + const sessionId = selection.chat.sessionId; + const view = await viewsService.openView(CHAT_VIEW_ID) as ChatViewPane; + view.loadSession(sessionId); + } + } +} + +class OpenChatEditorAction extends Action2 { + constructor() { + super({ + id: `workbench.action.openChat`, + title: localize2('interactiveSession.open', "Open Editor"), + f1: true, + category: CHAT_CATEGORY, + precondition: CONTEXT_HAS_DEFAULT_AGENT + }); + } + + async run(accessor: ServicesAccessor) { + const editorService = accessor.get(IEditorService); + await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options: { pinned: true } satisfies IChatEditorOptions }); + } +} + export function registerChatActions() { registerAction2(OpenChatGlobalAction); + registerAction2(ChatHistoryAction); + registerAction2(OpenChatEditorAction); registerAction2(class ClearChatInputHistoryAction extends Action2 { constructor() { super({ id: 'workbench.action.chat.clearInputHistory', title: localize2('interactiveSession.clearHistory.label', "Clear Input History"), - precondition: CONTEXT_PROVIDER_EXISTS, + precondition: CONTEXT_HAS_DEFAULT_AGENT, category: CHAT_CATEGORY, f1: true, }); @@ -116,7 +184,7 @@ export function registerChatActions() { super({ id: 'workbench.action.chat.clearHistory', title: localize2('chat.clear.label', "Clear All Workspace Chats"), - precondition: CONTEXT_PROVIDER_EXISTS, + precondition: CONTEXT_HAS_DEFAULT_AGENT, category: CHAT_CATEGORY, f1: true, }); @@ -193,79 +261,3 @@ export function registerChatActions() { } }); } - -export function getOpenChatEditorAction(id: string, label: string, when?: string) { - return class OpenChatEditor extends Action2 { - constructor() { - super({ - id: `workbench.action.openChat.${id}`, - title: localize2('interactiveSession.open', "Open Editor ({0})", label), - f1: true, - category: CHAT_CATEGORY, - precondition: ContextKeyExpr.deserialize(when) - }); - } - - async run(accessor: ServicesAccessor) { - const editorService = accessor.get(IEditorService); - await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options: { target: { providerId: id }, pinned: true } }); - } - }; -} - -const getHistoryChatActionDescriptorForViewTitle = (viewId: string, providerId: string): Readonly & { viewId: string } => ({ - viewId, - id: `workbench.action.chat.${providerId}.history`, - title: localize2('chat.history.label', "Show Chats"), - menu: { - id: MenuId.ViewTitle, - when: ContextKeyExpr.equals('view', viewId), - group: 'navigation', - order: -1 - }, - category: CHAT_CATEGORY, - icon: Codicon.history, - f1: true, - precondition: CONTEXT_PROVIDER_EXISTS -}); - -export function getHistoryAction(viewId: string, providerId: string) { - return class HistoryAction extends ViewAction { - constructor() { - super(getHistoryChatActionDescriptorForViewTitle(viewId, providerId)); - } - - async runInView(accessor: ServicesAccessor, view: ChatViewPane) { - const chatService = accessor.get(IChatService); - const quickInputService = accessor.get(IQuickInputService); - const chatContribService = accessor.get(IChatContributionService); - const viewsService = accessor.get(IViewsService); - const items = chatService.getHistory(); - const picks = items.map(i => ({ - label: i.title, - chat: i, - buttons: [{ - iconClass: ThemeIcon.asClassName(Codicon.x), - tooltip: localize('interactiveSession.history.delete', "Delete"), - }] - })); - const selection = await quickInputService.pick(picks, - { - placeHolder: localize('interactiveSession.history.pick', "Switch to chat"), - onDidTriggerItemButton: context => { - chatService.removeHistoryEntry(context.item.chat.sessionId); - context.removeItem(); - } - }); - if (selection) { - const sessionId = selection.chat.sessionId; - const provider = chatContribService.registeredProviders[0]?.id; - if (provider) { - const viewId = chatContribService.getViewIdForProvider(provider); - const view = await viewsService.openView(viewId) as ChatViewPane; - view.loadSession(sessionId); - } - } - } - }; -} diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatClear.ts b/src/vs/workbench/contrib/chat/browser/actions/chatClear.ts index 8edda6bd059..00ffcaed8c7 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatClear.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatClear.ts @@ -14,10 +14,10 @@ export async function clearChatEditor(accessor: ServicesAccessor): Promise const editorGroupsService = accessor.get(IEditorGroupsService); const chatEditorInput = editorService.activeEditor; - if (chatEditorInput instanceof ChatEditorInput && chatEditorInput.providerId) { + if (chatEditorInput instanceof ChatEditorInput) { await editorService.replaceEditors([{ editor: chatEditorInput, - replacement: { resource: ChatEditorInput.getNewEditorUri(), options: { target: { providerId: chatEditorInput.providerId, pinned: true } } } + replacement: { resource: ChatEditorInput.getNewEditorUri(), options: { pinned: true } satisfies IChatEditorOptions } }], editorGroupsService.activeGroup); } } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts index 2736da158c5..3f776ba34c9 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts @@ -7,23 +7,20 @@ import { Codicon } from 'vs/base/common/codicons'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { localize2 } from 'vs/nls'; -import { Action2, IAction2Options, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; +import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { ViewAction } from 'vs/workbench/browser/parts/views/viewPane'; import { ActiveEditorContext } from 'vs/workbench/common/contextkeys'; -import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; +import { CHAT_CATEGORY, isChatViewTitleActionContext } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; import { clearChatEditor } from 'vs/workbench/contrib/chat/browser/actions/chatClear'; -import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; +import { CHAT_VIEW_ID, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatEditorInput } from 'vs/workbench/contrib/chat/browser/chatEditorInput'; -import { ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane'; -import { CONTEXT_IN_CHAT_SESSION, CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +import { CONTEXT_IN_CHAT_SESSION, CONTEXT_HAS_DEFAULT_AGENT } from 'vs/workbench/contrib/chat/common/chatContextKeys'; export const ACTION_ID_NEW_CHAT = `workbench.action.chat.newChat`; export function registerNewChatActions() { - registerAction2(class NewChatEditorAction extends Action2 { constructor() { super({ @@ -31,7 +28,7 @@ export function registerNewChatActions() { title: localize2('chat.newChat.label', "New Chat"), icon: Codicon.plus, f1: false, - precondition: CONTEXT_PROVIDER_EXISTS, + precondition: CONTEXT_HAS_DEFAULT_AGENT, menu: [{ id: MenuId.EditorTitle, group: 'navigation', @@ -46,7 +43,6 @@ export function registerNewChatActions() { } }); - registerAction2(class GlobalClearChatAction extends Action2 { constructor() { super({ @@ -54,7 +50,7 @@ export function registerNewChatActions() { title: localize2('chat.newChat.label', "New Chat"), category: CHAT_CATEGORY, icon: Codicon.plus, - precondition: CONTEXT_PROVIDER_EXISTS, + precondition: CONTEXT_HAS_DEFAULT_AGENT, f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -64,58 +60,42 @@ export function registerNewChatActions() { }, when: CONTEXT_IN_CHAT_SESSION }, - menu: { + menu: [{ id: MenuId.ChatContext, group: 'z_clear' - } + }, + { + id: MenuId.ViewTitle, + when: ContextKeyExpr.equals('view', CHAT_VIEW_ID), + group: 'navigation', + order: -1 + }] }); } run(accessor: ServicesAccessor, ...args: any[]) { - const widgetService = accessor.get(IChatWidgetService); + const context = args[0]; + if (isChatViewTitleActionContext(context)) { + // Is running in the Chat view title + announceChatCleared(accessor); + context.chatView.clear(); + context.chatView.widget.focusInput(); + } else { + // Is running from f1 or keybinding + const widgetService = accessor.get(IChatWidgetService); - const widget = widgetService.lastFocusedWidget; - if (!widget) { - return; + const widget = widgetService.lastFocusedWidget; + if (!widget) { + return; + } + announceChatCleared(accessor); + widget.clear(); + widget.focusInput(); } - - announceChatCleared(accessor); - widget.clear(); - widget.focusInput(); } }); } -const getNewChatActionDescriptorForViewTitle = (viewId: string, providerId: string): Readonly & { viewId: string } => ({ - viewId, - id: `workbench.action.chat.${providerId}.newChat`, - title: localize2('chat.newChat.label', "New Chat"), - menu: { - id: MenuId.ViewTitle, - when: ContextKeyExpr.equals('view', viewId), - group: 'navigation', - order: -1 - }, - precondition: CONTEXT_PROVIDER_EXISTS, - category: CHAT_CATEGORY, - icon: Codicon.plus, - f1: false -}); - -export function getNewChatAction(viewId: string, providerId: string) { - return class NewChatAction extends ViewAction { - constructor() { - super(getNewChatActionDescriptorForViewTitle(viewId, providerId)); - } - - async runInView(accessor: ServicesAccessor, view: ChatViewPane) { - announceChatCleared(accessor); - await view.clear(); - view.widget.focusInput(); - } - }; -} - function announceChatCleared(accessor: ServicesAccessor): void { accessor.get(IAccessibilitySignalService).playSignal(AccessibilitySignal.clear); } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts index 145a74b8f08..f57f61f53b8 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts @@ -29,7 +29,7 @@ import { accessibleViewInCodeBlock } from 'vs/workbench/contrib/accessibility/br import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; import { IChatWidgetService, IChatCodeBlockContextProviderService } from 'vs/workbench/contrib/chat/browser/chat'; import { ICodeBlockActionContext, ICodeCompareBlockActionContext } from 'vs/workbench/contrib/chat/browser/codeBlockPart'; -import { CONTEXT_IN_CHAT_INPUT, CONTEXT_IN_CHAT_SESSION, CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +import { CONTEXT_IN_CHAT_INPUT, CONTEXT_IN_CHAT_SESSION, CONTEXT_HAS_DEFAULT_AGENT } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { ChatCopyKind, IChatService, IDocumentContext } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatResponseViewModel, isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { insertCell } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; @@ -109,7 +109,6 @@ export function registerChatCodeBlockActions() { if (isResponseVM(context.element)) { const chatService = accessor.get(IChatService); chatService.notifyUserAction({ - providerId: context.element.providerId, agentId: context.element.agent?.id, sessionId: context.element.sessionId, requestId: context.element.requestId, @@ -155,7 +154,6 @@ export function registerChatCodeBlockActions() { const element = context.element as IChatResponseViewModel | undefined; if (element) { chatService.notifyUserAction({ - providerId: element.providerId, agentId: element.agent?.id, sessionId: element.sessionId, requestId: element.requestId, @@ -185,7 +183,7 @@ export function registerChatCodeBlockActions() { super({ id: 'workbench.action.chat.insertCodeBlock', title: localize2('interactive.insertCodeBlock.label', "Insert at Cursor"), - precondition: CONTEXT_PROVIDER_EXISTS, + precondition: CONTEXT_HAS_DEFAULT_AGENT, f1: true, category: CHAT_CATEGORY, icon: Codicon.insert, @@ -331,7 +329,6 @@ export function registerChatCodeBlockActions() { if (isResponseVM(context.element)) { const chatService = accessor.get(IChatService); chatService.notifyUserAction({ - providerId: context.element.providerId, agentId: context.element.agent?.id, sessionId: context.element.sessionId, requestId: context.element.requestId, @@ -352,7 +349,7 @@ export function registerChatCodeBlockActions() { super({ id: 'workbench.action.chat.insertIntoNewFile', title: localize2('interactive.insertIntoNewFile.label', "Insert into New File"), - precondition: CONTEXT_PROVIDER_EXISTS, + precondition: CONTEXT_HAS_DEFAULT_AGENT, f1: true, category: CHAT_CATEGORY, icon: Codicon.newFile, @@ -377,7 +374,6 @@ export function registerChatCodeBlockActions() { if (isResponseVM(context.element)) { chatService.notifyUserAction({ - providerId: context.element.providerId, agentId: context.element.agent?.id, sessionId: context.element.sessionId, requestId: context.element.requestId, @@ -407,7 +403,7 @@ export function registerChatCodeBlockActions() { super({ id: 'workbench.action.chat.runInTerminal', title: localize2('interactive.runInTerminal.label', "Insert into Terminal"), - precondition: CONTEXT_PROVIDER_EXISTS, + precondition: CONTEXT_HAS_DEFAULT_AGENT, f1: true, category: CHAT_CATEGORY, icon: Codicon.terminal, @@ -470,7 +466,6 @@ export function registerChatCodeBlockActions() { if (isResponseVM(context.element)) { chatService.notifyUserAction({ - providerId: context.element.providerId, agentId: context.element.agent?.id, sessionId: context.element.sessionId, requestId: context.element.requestId, @@ -526,7 +521,7 @@ export function registerChatCodeBlockActions() { weight: KeybindingWeight.WorkbenchContrib, when: CONTEXT_IN_CHAT_SESSION, }, - precondition: CONTEXT_PROVIDER_EXISTS, + precondition: CONTEXT_HAS_DEFAULT_AGENT, f1: true, category: CHAT_CATEGORY, }); @@ -548,7 +543,7 @@ export function registerChatCodeBlockActions() { weight: KeybindingWeight.WorkbenchContrib, when: CONTEXT_IN_CHAT_SESSION, }, - precondition: CONTEXT_PROVIDER_EXISTS, + precondition: CONTEXT_HAS_DEFAULT_AGENT, f1: true, category: CHAT_CATEGORY, }); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatFileTreeActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatFileTreeActions.ts index 4b769210522..2bf9d4e1e32 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatFileTreeActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatFileTreeActions.ts @@ -10,7 +10,7 @@ import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; -import { CONTEXT_IN_CHAT_SESSION, CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +import { CONTEXT_IN_CHAT_SESSION, CONTEXT_HAS_DEFAULT_AGENT } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatResponseViewModel, isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; export function registerChatFileTreeActions() { @@ -24,7 +24,7 @@ export function registerChatFileTreeActions() { weight: KeybindingWeight.WorkbenchContrib, when: CONTEXT_IN_CHAT_SESSION, }, - precondition: CONTEXT_PROVIDER_EXISTS, + precondition: CONTEXT_HAS_DEFAULT_AGENT, f1: true, category: CHAT_CATEGORY, }); @@ -45,7 +45,7 @@ export function registerChatFileTreeActions() { weight: KeybindingWeight.WorkbenchContrib, when: CONTEXT_IN_CHAT_SESSION, }, - precondition: CONTEXT_PROVIDER_EXISTS, + precondition: CONTEXT_HAS_DEFAULT_AGENT, f1: true, category: CHAT_CATEGORY, }); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatImportExport.ts b/src/vs/workbench/contrib/chat/browser/actions/chatImportExport.ts index 2fbf411e0fc..d710f921901 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatImportExport.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatImportExport.ts @@ -14,7 +14,7 @@ import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatAct import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatEditor'; import { ChatEditorInput } from 'vs/workbench/contrib/chat/browser/chatEditorInput'; -import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +import { CONTEXT_HAS_DEFAULT_AGENT } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { isExportableSessionData } from 'vs/workbench/contrib/chat/common/chatModel'; import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -29,7 +29,7 @@ export function registerChatExportActions() { id: 'workbench.action.chat.export', category: CHAT_CATEGORY, title: localize2('chat.export.label', "Export Chat..."), - precondition: CONTEXT_PROVIDER_EXISTS, + precondition: CONTEXT_HAS_DEFAULT_AGENT, f1: true, }); } @@ -70,7 +70,7 @@ export function registerChatExportActions() { id: 'workbench.action.chat.import', title: localize2('chat.import.label', "Import Chat..."), category: CHAT_CATEGORY, - precondition: CONTEXT_PROVIDER_EXISTS, + precondition: CONTEXT_HAS_DEFAULT_AGENT, f1: true, }); } @@ -96,7 +96,7 @@ export function registerChatExportActions() { throw new Error('Invalid chat session data'); } - await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options: { target: { data }, pinned: true } }); + await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options: { target: { data }, pinned: true } as IChatEditorOptions }); } catch (err) { throw err; } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts index e00f7fa998e..873f39ca869 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts @@ -3,107 +3,68 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize, localize2 } from 'vs/nls'; -import { Action2, IAction2Options, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { localize2 } from 'vs/nls'; +import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { ViewAction } from 'vs/workbench/browser/parts/views/viewPane'; import { ActiveEditorContext } from 'vs/workbench/common/contextkeys'; -import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; -import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; -import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; +import { CHAT_CATEGORY, isChatViewTitleActionContext } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; +import { CHAT_VIEW_ID, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatEditor'; import { ChatEditorInput } from 'vs/workbench/contrib/chat/browser/chatEditorInput'; import { ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane'; -import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; -import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; -import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; +import { CONTEXT_HAS_DEFAULT_AGENT } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ACTIVE_GROUP, AUX_WINDOW_GROUP, IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; enum MoveToNewLocation { Editor = 'Editor', Window = 'Window' } -const getMoveToChatActionDescriptorForViewTitle = (viewId: string, providerId: string, moveTo: MoveToNewLocation): Readonly & { viewId: string } => ({ - id: `workbench.action.chat.${providerId}.openIn${moveTo}`, - title: { - value: moveTo === MoveToNewLocation.Editor ? localize('chat.openInEditor.label', "Open Chat in Editor") : localize('chat.openInNewWindow.label', "Open Chat in New Window"), - original: moveTo === MoveToNewLocation.Editor ? 'Open Chat in Editor' : 'Open Chat in New Window', - }, - category: CHAT_CATEGORY, - precondition: CONTEXT_PROVIDER_EXISTS, - f1: false, - viewId, - menu: { - id: MenuId.ViewTitle, - when: ContextKeyExpr.equals('view', viewId), - order: 0 - }, -}); - -export function getMoveToEditorAction(viewId: string, providerId: string) { - return getMoveToAction(viewId, providerId, MoveToNewLocation.Editor); -} - -export function getMoveToNewWindowAction(viewId: string, providerId: string) { - return getMoveToAction(viewId, providerId, MoveToNewLocation.Window); -} - -export function getMoveToAction(viewId: string, providerId: string, moveTo: MoveToNewLocation) { - return class MoveToAction extends ViewAction { - constructor() { - super(getMoveToChatActionDescriptorForViewTitle(viewId, providerId, moveTo)); - } - - async runInView(accessor: ServicesAccessor, view: ChatViewPane) { - const viewModel = view.widget.viewModel; - if (!viewModel) { - return; - } - - const editorService = accessor.get(IEditorService); - const sessionId = viewModel.sessionId; - const viewState = view.widget.getViewState(); - view.clear(); - - await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options: { target: { sessionId }, pinned: true, viewState: viewState } }, moveTo === MoveToNewLocation.Window ? AUX_WINDOW_GROUP : ACTIVE_GROUP); - } - }; -} - export function registerMoveActions() { registerAction2(class GlobalMoveToEditorAction extends Action2 { constructor() { super({ id: `workbench.action.chat.openInEditor`, - title: localize2('interactiveSession.openInEditor.label', "Open Chat in Editor"), + title: localize2('chat.openInEditor.label', "Open Chat in Editor"), category: CHAT_CATEGORY, - precondition: CONTEXT_PROVIDER_EXISTS, - f1: true + precondition: CONTEXT_HAS_DEFAULT_AGENT, + f1: true, + menu: { + id: MenuId.ViewTitle, + when: ContextKeyExpr.equals('view', CHAT_VIEW_ID), + order: 0 + }, }); } async run(accessor: ServicesAccessor, ...args: any[]) { - executeMoveToAction(accessor, MoveToNewLocation.Editor); + const context = args[0]; + executeMoveToAction(accessor, MoveToNewLocation.Editor, isChatViewTitleActionContext(context) ? context.chatView : undefined); } }); registerAction2(class GlobalMoveToNewWindowAction extends Action2 { - constructor() { super({ id: `workbench.action.chat.openInNewWindow`, - title: localize2('interactiveSession.openInNewWindow.label', "Open/move the panel chat to an undocked window."), + title: localize2('chat.openInNewWindow.label', "Open Chat in New Window"), category: CHAT_CATEGORY, - precondition: CONTEXT_PROVIDER_EXISTS, - f1: true + precondition: CONTEXT_HAS_DEFAULT_AGENT, + f1: true, + menu: { + id: MenuId.ViewTitle, + when: ContextKeyExpr.equals('view', CHAT_VIEW_ID), + order: 0 + }, }); } async run(accessor: ServicesAccessor, ...args: any[]) { - executeMoveToAction(accessor, MoveToNewLocation.Window); + const context = args[0]; + executeMoveToAction(accessor, MoveToNewLocation.Window, isChatViewTitleActionContext(context) ? context.chatView : undefined); } }); @@ -113,7 +74,7 @@ export function registerMoveActions() { id: `workbench.action.chat.openInSidebar`, title: localize2('interactiveSession.openInSidebar.label', "Open Chat in Side Bar"), category: CHAT_CATEGORY, - precondition: CONTEXT_PROVIDER_EXISTS, + precondition: CONTEXT_HAS_DEFAULT_AGENT, f1: true, menu: [{ id: MenuId.EditorTitle, @@ -129,17 +90,14 @@ export function registerMoveActions() { }); } -async function executeMoveToAction(accessor: ServicesAccessor, moveTo: MoveToNewLocation) { +async function executeMoveToAction(accessor: ServicesAccessor, moveTo: MoveToNewLocation, chatView?: ChatViewPane) { const widgetService = accessor.get(IChatWidgetService); const viewService = accessor.get(IViewsService); - const chatService = accessor.get(IChatService); const editorService = accessor.get(IEditorService); - const widget = widgetService.lastFocusedWidget; + const widget = chatView?.widget ?? widgetService.lastFocusedWidget; if (!widget || !('viewId' in widget.viewContext)) { - const providerId = chatService.getProviderInfos()[0].id; - - await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options: { target: { providerId }, pinned: true } }, moveTo === MoveToNewLocation.Window ? AUX_WINDOW_GROUP : ACTIVE_GROUP); + await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options: { pinned: true } }, moveTo === MoveToNewLocation.Window ? AUX_WINDOW_GROUP : ACTIVE_GROUP); return; } @@ -159,19 +117,14 @@ async function executeMoveToAction(accessor: ServicesAccessor, moveTo: MoveToNew async function moveToSidebar(accessor: ServicesAccessor): Promise { const viewsService = accessor.get(IViewsService); const editorService = accessor.get(IEditorService); - const chatContribService = accessor.get(IChatContributionService); const editorGroupService = accessor.get(IEditorGroupsService); const chatEditorInput = editorService.activeEditor; - if (chatEditorInput instanceof ChatEditorInput && chatEditorInput.sessionId && chatEditorInput.providerId) { + if (chatEditorInput instanceof ChatEditorInput && chatEditorInput.sessionId) { await editorService.closeEditor({ editor: chatEditorInput, groupId: editorGroupService.activeGroup.id }); - const viewId = chatContribService.getViewIdForProvider(chatEditorInput.providerId); - const view = await viewsService.openView(viewId) as ChatViewPane; + const view = await viewsService.openView(CHAT_VIEW_ID) as ChatViewPane; view.loadSession(chatEditorInput.sessionId); } else { - const chatService = accessor.get(IChatService); - const providerId = chatService.getProviderInfos()[0].id; - const viewId = chatContribService.getViewIdForProvider(providerId); - await viewsService.openView(viewId); + await viewsService.openView(CHAT_VIEW_ID); } } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts index d1863ef7d6c..39231cc62b2 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts @@ -12,13 +12,14 @@ import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/act import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; -import { IQuickChatService, IQuickChatOpenOptions } from 'vs/workbench/contrib/chat/browser/chat'; -import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +import { IQuickChatOpenOptions, IQuickChatService } from 'vs/workbench/contrib/chat/browser/chat'; +import { CONTEXT_HAS_DEFAULT_AGENT } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; export const ASK_QUICK_QUESTION_ACTION_ID = 'workbench.action.quickchat.toggle'; export function registerQuickChatActions() { registerAction2(QuickChatGlobalAction); + registerAction2(AskQuickChatAction); registerAction2(class OpenInChatViewAction extends Action2 { constructor() { @@ -94,6 +95,7 @@ export function registerQuickChatActions() { controller.focus(); } }); + } class QuickChatGlobalAction extends Action2 { @@ -101,7 +103,7 @@ class QuickChatGlobalAction extends Action2 { super({ id: ASK_QUICK_QUESTION_ACTION_ID, title: localize2('quickChat', 'Quick Chat'), - precondition: CONTEXT_PROVIDER_EXISTS, + precondition: CONTEXT_HAS_DEFAULT_AGENT, icon: Codicon.commentDiscussion, f1: false, category: CHAT_CATEGORY, @@ -153,35 +155,25 @@ class QuickChatGlobalAction extends Action2 { if (options?.query) { options.selection = new Selection(1, options.query.length + 1, 1, options.query.length + 1); } - quickChatService.toggle(undefined, options); + quickChatService.toggle(options); } } -/** - * Returns a provider specific action that will open the quick chat for that provider. - * This is used to include the provider label in the action title so it shows up in - * the command palette. - * @param id The id of the provider - * @param label The label of the provider - * @returns An action that will open the quick chat for this provider - */ -export function getQuickChatActionForProvider(id: string, label: string) { - return class AskQuickChatAction extends Action2 { - constructor() { - super({ - id: `workbench.action.openQuickChat.${id}`, - category: CHAT_CATEGORY, - title: localize2('interactiveSession.open', "Open Quick Chat ({0})", label), - f1: true - }); - } +class AskQuickChatAction extends Action2 { + constructor() { + super({ + id: `workbench.action.openQuickChat`, + category: CHAT_CATEGORY, + title: localize2('interactiveSession.open', "Open Quick Chat"), + f1: true + }); + } - override run(accessor: ServicesAccessor, query?: string): void { - const quickChatService = accessor.get(IQuickChatService); - quickChatService.toggle(id, query ? { - query, - selection: new Selection(1, query.length + 1, 1, query.length + 1) - } : undefined); - } - }; + override run(accessor: ServicesAccessor, query?: string): void { + const quickChatService = accessor.get(IQuickChatService); + quickChatService.toggle(query ? { + query, + selection: new Selection(1, query.length + 1, 1, query.length + 1) + } : undefined); + } } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts index 8204a71dea1..80800c41cf6 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts @@ -50,7 +50,6 @@ export function registerChatTitleActions() { const chatService = accessor.get(IChatService); chatService.notifyUserAction({ - providerId: item.providerId, agentId: item.agent?.id, sessionId: item.sessionId, requestId: item.requestId, @@ -90,7 +89,6 @@ export function registerChatTitleActions() { const chatService = accessor.get(IChatService); chatService.notifyUserAction({ - providerId: item.providerId, agentId: item.agent?.id, sessionId: item.sessionId, requestId: item.requestId, @@ -129,7 +127,6 @@ export function registerChatTitleActions() { const chatService = accessor.get(IChatService); chatService.notifyUserAction({ - providerId: item.providerId, agentId: item.agent?.id, sessionId: item.sessionId, requestId: item.requestId, diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 0aff29be677..8f5eb0da01e 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -19,9 +19,9 @@ import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/editor'; import { AccessibilityVerbositySettingId, AccessibleViewProviderId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; -import { alertFocusChange } from 'vs/workbench/contrib/accessibility/browser/accessibleViewContributions'; import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; +import { alertFocusChange } from 'vs/workbench/contrib/accessibility/browser/accessibleViewContributions'; import { registerChatActions } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; import { ACTION_ID_NEW_CHAT, registerNewChatActions } from 'vs/workbench/contrib/chat/browser/actions/chatClearActions'; import { registerChatCodeBlockActions, registerChatCodeCompareBlockActions } from 'vs/workbench/contrib/chat/browser/actions/chatCodeblockActions'; @@ -34,17 +34,16 @@ import { registerQuickChatActions } from 'vs/workbench/contrib/chat/browser/acti import { registerChatTitleActions } from 'vs/workbench/contrib/chat/browser/actions/chatTitleActions'; import { IChatAccessibilityService, IChatCodeBlockContextProviderService, IChatWidget, IChatWidgetService, IQuickChatService } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chatAccessibilityService'; -import { ChatContributionService } from 'vs/workbench/contrib/chat/browser/chatContributionServiceImpl'; import { ChatEditor, IChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatEditor'; import { ChatEditorInput, ChatEditorInputSerializer } from 'vs/workbench/contrib/chat/browser/chatEditorInput'; import { QuickChatService } from 'vs/workbench/contrib/chat/browser/chatQuick'; import { ChatVariablesService } from 'vs/workbench/contrib/chat/browser/chatVariables'; import { ChatWidgetService } from 'vs/workbench/contrib/chat/browser/chatWidget'; +import { ChatCodeBlockContextProviderService } from 'vs/workbench/contrib/chat/browser/codeBlockContextProviderService'; import 'vs/workbench/contrib/chat/browser/contrib/chatHistoryVariables'; import 'vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib'; import { ChatAgentLocation, ChatAgentService, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { CONTEXT_IN_CHAT_SESSION } from 'vs/workbench/contrib/chat/common/chatContextKeys'; -import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; import { ChatWelcomeMessageModel } from 'vs/workbench/contrib/chat/common/chatModel'; import { chatAgentLeader, chatSubcommandLeader, chatVariableLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; @@ -58,7 +57,7 @@ import { IVoiceChatService, VoiceChatService } from 'vs/workbench/contrib/chat/c import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import '../common/chatColors'; -import { ChatCodeBlockContextProviderService } from 'vs/workbench/contrib/chat/browser/codeBlockContextProviderService'; +import { ChatExtensionPointHandler } from 'vs/workbench/contrib/chat/browser/chatParticipantContributions'; // Register configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); @@ -314,6 +313,7 @@ registerWorkbenchContribution2(ChatResolverContribution.ID, ChatResolverContribu workbenchContributionsRegistry.registerWorkbenchContribution(ChatAccessibleViewContribution, LifecyclePhase.Eventually); workbenchContributionsRegistry.registerWorkbenchContribution(ChatSlashStaticSlashCommandsContribution, LifecyclePhase.Eventually); Registry.as(EditorExtensions.EditorFactory).registerEditorSerializer(ChatEditorInput.TypeID, ChatEditorInputSerializer); +registerWorkbenchContribution2(ChatExtensionPointHandler.ID, ChatExtensionPointHandler, WorkbenchPhase.BlockStartup); registerChatActions(); registerChatCopyActions(); @@ -328,7 +328,6 @@ registerMoveActions(); registerNewChatActions(); registerSingleton(IChatService, ChatService, InstantiationType.Delayed); -registerSingleton(IChatContributionService, ChatContributionService, InstantiationType.Delayed); registerSingleton(IChatWidgetService, ChatWidgetService, InstantiationType.Delayed); registerSingleton(IQuickChatService, QuickChatService, InstantiationType.Delayed); registerSingleton(IChatAccessibilityService, ChatAccessibilityService, InstantiationType.Delayed); diff --git a/src/vs/workbench/contrib/chat/browser/chat.ts b/src/vs/workbench/contrib/chat/browser/chat.ts index 98028d2d803..f97e377c7dd 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.ts @@ -11,11 +11,14 @@ import { Selection } from 'vs/editor/common/core/selection'; import { localize } from 'vs/nls'; import { MenuId } from 'vs/platform/actions/common/actions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane'; import { IChatWidgetContrib } from 'vs/workbench/contrib/chat/browser/chatWidget'; import { ICodeBlockActionContext } from 'vs/workbench/contrib/chat/browser/codeBlockPart'; import { ChatAgentLocation, IChatAgentCommand, IChatAgentData } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { CHAT_PROVIDER_ID } from 'vs/workbench/contrib/chat/common/chatParticipantContribTypes'; import { IChatRequestViewModel, IChatResponseViewModel, IChatViewModel, IChatWelcomeMessageViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; export const IChatWidgetService = createDecorator('chatWidgetService'); @@ -28,25 +31,24 @@ export interface IChatWidgetService { */ readonly lastFocusedWidget: IChatWidget | undefined; - /** - * Returns whether a view was successfully revealed. - */ - revealViewForProvider(providerId: string): Promise; - getWidgetByInputUri(uri: URI): IChatWidget | undefined; getWidgetBySessionId(sessionId: string): IChatWidget | undefined; } +export async function showChatView(viewsService: IViewsService): Promise { + return (await viewsService.openView(CHAT_VIEW_ID))?.widget; +} + export const IQuickChatService = createDecorator('quickChatService'); export interface IQuickChatService { readonly _serviceBrand: undefined; readonly onDidClose: Event; readonly enabled: boolean; readonly focused: boolean; - toggle(providerId?: string, options?: IQuickChatOpenOptions): void; + toggle(options?: IQuickChatOpenOptions): void; focus(): void; - open(providerId?: string, options?: IQuickChatOpenOptions): void; + open(options?: IQuickChatOpenOptions): void; close(): void; openInChatView(): void; } @@ -129,7 +131,6 @@ export interface IChatWidget { readonly viewContext: IChatWidgetViewContext; readonly viewModel: IChatViewModel | undefined; readonly inputEditor: ICodeEditor; - readonly providerId: string; readonly supportsFileReferences: boolean; readonly parsedInput: IParsedChatRequest; lastSelectedAgent: IChatAgentData | undefined; @@ -172,3 +173,5 @@ export interface IChatCodeBlockContextProviderService { } export const GeneratingPhrase = localize('generating', "Generating"); + +export const CHAT_VIEW_ID = `workbench.panel.chat.view.${CHAT_PROVIDER_ID}`; diff --git a/src/vs/workbench/contrib/chat/browser/chatEditor.ts b/src/vs/workbench/contrib/chat/browser/chatEditor.ts index 34f10a76088..875863c2e73 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditor.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditor.ts @@ -18,13 +18,14 @@ import { IEditorOpenContext } from 'vs/workbench/common/editor'; import { Memento } from 'vs/workbench/common/memento'; import { ChatEditorInput } from 'vs/workbench/contrib/chat/browser/chatEditorInput'; import { IChatViewState, ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget'; -import { IChatModel, ISerializableChatData } from 'vs/workbench/contrib/chat/common/chatModel'; +import { IChatModel, IExportableChatData, ISerializableChatData } from 'vs/workbench/contrib/chat/common/chatModel'; import { clearChatEditor } from 'vs/workbench/contrib/chat/browser/actions/chatClear'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { CHAT_PROVIDER_ID } from 'vs/workbench/contrib/chat/common/chatParticipantContribTypes'; export interface IChatEditorOptions extends IEditorOptions { - target: { sessionId: string } | { providerId: string } | { data: ISerializableChatData }; + target?: { sessionId: string } | { data: IExportableChatData | ISerializableChatData }; } export class ChatEditor extends EditorPane { @@ -101,7 +102,7 @@ export class ChatEditor extends EditorPane { } private updateModel(model: IChatModel, viewState?: IChatViewState): void { - this._memento = new Memento('interactive-session-editor-' + model.providerId, this.storageService); + this._memento = new Memento('interactive-session-editor-' + CHAT_PROVIDER_ID, this.storageService); this._viewState = viewState ?? this._memento.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE) as IChatViewState; this.widget.setModel(model, { ...this._viewState }); } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts index e6cc6c81ff0..28e7901e7c6 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts @@ -29,7 +29,6 @@ export class ChatEditorInput extends EditorInput { private readonly inputCount: number; public sessionId: string | undefined; - public providerId: string | undefined; private model: IChatModel | undefined; @@ -59,8 +58,9 @@ export class ChatEditorInput extends EditorInput { throw new Error('Invalid chat URI'); } - this.sessionId = 'sessionId' in options.target ? options.target.sessionId : undefined; - this.providerId = 'providerId' in options.target ? options.target.providerId : undefined; + this.sessionId = (options.target && 'sessionId' in options.target) ? + options.target.sessionId : + undefined; this.inputCount = ChatEditorInput.getNextCount(); ChatEditorInput.countsInUse.add(this.inputCount); this._register(toDisposable(() => ChatEditorInput.countsInUse.delete(this.inputCount))); @@ -93,8 +93,8 @@ export class ChatEditorInput extends EditorInput { override async resolve(): Promise { if (typeof this.sessionId === 'string') { this.model = this.chatService.getOrRestoreSession(this.sessionId); - } else if (typeof this.providerId === 'string') { - this.model = this.chatService.startSession(this.providerId, CancellationToken.None); + } else if (!this.options.target) { + this.model = this.chatService.startSession(CancellationToken.None); } else if ('data' in this.options.target) { this.model = this.chatService.loadSessionFromContent(this.options.target.data); } @@ -104,7 +104,6 @@ export class ChatEditorInput extends EditorInput { } this.sessionId = this.model.sessionId; - this.providerId = this.model.providerId; this._register(this.model.onDidChange(() => this._onDidChangeLabel.fire())); return this._register(new ChatEditorModel(this.model)); diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 90e2c48a6b1..a67a7826b04 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -125,7 +125,6 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge private inputEditorHasText: IContextKey; private chatCursorAtTop: IContextKey; private inputEditorHasFocus: IContextKey; - private providerId: string | undefined; private cachedDimensions: dom.Dimension | undefined; private cachedToolbarWidth: number | undefined; @@ -174,9 +173,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge return localize('chatInput', "Chat Input"); } - setState(providerId: string, inputValue: string | undefined): void { - this.providerId = providerId; - const history = this.historyService.getHistory(providerId); + setState(inputValue: string | undefined): void { + const history = this.historyService.getHistory(); this.history = new HistoryNavigator(history, 50); if (typeof inputValue === 'string') { @@ -495,7 +493,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge saveState(): void { const inputHistory = this.history.getHistory(); - this.historyService.saveHistory(this.providerId!, inputHistory); + this.historyService.saveHistory(inputHistory); } } diff --git a/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts b/src/vs/workbench/contrib/chat/browser/chatParticipantContributions.ts similarity index 59% rename from src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts rename to src/vs/workbench/contrib/chat/browser/chatParticipantContributions.ts index ea834161ce2..f51280b2cb1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/browser/chatParticipantContributions.ts @@ -5,9 +5,8 @@ import { isNonEmptyArray } from 'vs/base/common/arrays'; import { Codicon } from 'vs/base/common/codicons'; -import { DisposableMap, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { DisposableMap, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { localize, localize2 } from 'vs/nls'; -import { registerAction2 } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; @@ -15,55 +14,15 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; import { Registry } from 'vs/platform/registry/common/platform'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; -import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IViewContainersRegistry, IViewDescriptor, IViewsRegistry, ViewContainer, ViewContainerLocation, Extensions as ViewExtensions } from 'vs/workbench/common/views'; -import { getHistoryAction, getOpenChatEditorAction } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; -import { getNewChatAction } from 'vs/workbench/contrib/chat/browser/actions/chatClearActions'; -import { getMoveToEditorAction, getMoveToNewWindowAction } from 'vs/workbench/contrib/chat/browser/actions/chatMoveActions'; -import { getQuickChatActionForProvider } from 'vs/workbench/contrib/chat/browser/actions/chatQuickInputActions'; -import { CHAT_SIDEBAR_PANEL_ID, ChatViewPane, IChatViewOptions } from 'vs/workbench/contrib/chat/browser/chatViewPane'; +import { CHAT_VIEW_ID } from 'vs/workbench/contrib/chat/browser/chat'; +import { CHAT_SIDEBAR_PANEL_ID, ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane'; import { ChatAgentLocation, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { IChatContributionService, IChatProviderContribution, IRawChatParticipantContribution, IRawChatProviderContribution } from 'vs/workbench/contrib/chat/common/chatContributionService'; +import { IRawChatParticipantContribution } from 'vs/workbench/contrib/chat/common/chatParticipantContribTypes'; import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import * as extensionsRegistry from 'vs/workbench/services/extensions/common/extensionsRegistry'; -const chatExtensionPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint({ - extensionPoint: 'interactiveSession', - jsonSchema: { - description: localize('vscode.extension.contributes.interactiveSession', 'Contributes an Interactive Session provider'), - type: 'array', - items: { - additionalProperties: false, - type: 'object', - defaultSnippets: [{ body: { id: '', program: '', runtime: '' } }], - required: ['id', 'label'], - properties: { - id: { - description: localize('vscode.extension.contributes.interactiveSession.id', "Unique identifier for this Interactive Session provider."), - type: 'string' - }, - label: { - description: localize('vscode.extension.contributes.interactiveSession.label', "Display name for this Interactive Session provider."), - type: 'string' - }, - icon: { - description: localize('vscode.extension.contributes.interactiveSession.icon', "An icon for this Interactive Session provider."), - type: 'string' - }, - when: { - description: localize('vscode.extension.contributes.interactiveSession.when', "A condition which must be true to enable this Interactive Session provider."), - type: 'string' - }, - } - } - }, - activationEventsGenerator: (contributions: IRawChatProviderContribution[], result: { push(item: string): void }) => { - for (const contrib of contributions) { - result.push(`onInteractiveSession:${contrib.id}`); - } - }, -}); - const chatParticipantExtensionPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'chatParticipants', jsonSchema: { @@ -168,11 +127,9 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { private readonly disposables = new DisposableStore(); private _welcomeViewDescriptor?: IViewDescriptor; private _viewContainer: ViewContainer; - private _registrationDisposables = new Map(); private _participantRegistrationDisposables = new DisposableMap(); constructor( - @IChatContributionService private readonly _chatContributionService: IChatContributionService, @IChatAgentService private readonly _chatAgentService: IChatAgentService, @IProductService private readonly productService: IProductService, @IContextKeyService private readonly contextService: IContextKeyService, @@ -196,20 +153,18 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { const contextKeyExpr = ContextKeyExpr.equals(showWelcomeViewConfigKey, true); const viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); if (this.contextService.contextMatchesRules(contextKeyExpr)) { - const viewId = this._chatContributionService.getViewIdForProvider(this.productService.chatWelcomeView.welcomeViewId); - this._welcomeViewDescriptor = { - id: viewId, + id: CHAT_VIEW_ID, name: { original: this.productService.chatWelcomeView.welcomeViewTitle, value: this.productService.chatWelcomeView.welcomeViewTitle }, containerIcon: this._viewContainer.icon, - ctorDescriptor: new SyncDescriptor(ChatViewPane, [{ providerId: this.productService.chatWelcomeView.welcomeViewId }]), + ctorDescriptor: new SyncDescriptor(ChatViewPane), canToggleVisibility: false, canMoveView: true, order: 100 }; viewsRegistry.registerViews([this._welcomeViewDescriptor], this._viewContainer); - viewsRegistry.registerViewWelcomeContent(viewId, { + viewsRegistry.registerViewWelcomeContent(CHAT_VIEW_ID, { content: this.productService.chatWelcomeView.welcomeViewContent, }); } else if (this._welcomeViewDescriptor) { @@ -220,29 +175,6 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { } private handleAndRegisterChatExtensions(): void { - chatExtensionPoint.setHandler((extensions, delta) => { - for (const extension of delta.added) { - const extensionDisposable = new DisposableStore(); - for (const providerDescriptor of extension.value) { - this.registerChatProvider(providerDescriptor); - this._chatContributionService.registerChatProvider(providerDescriptor); - } - this._registrationDisposables.set(extension.description.identifier.value, extensionDisposable); - } - - for (const extension of delta.removed) { - const registration = this._registrationDisposables.get(extension.description.identifier.value); - if (registration) { - registration.dispose(); - this._registrationDisposables.delete(extension.description.identifier.value); - } - - for (const providerDescriptor of extension.value) { - this._chatContributionService.deregisterChatProvider(providerDescriptor.id); - } - } - }); - chatParticipantExtensionPoint.setHandler((extensions, delta) => { for (const extension of delta.added) { for (const providerDescriptor of extension.value) { @@ -261,25 +193,33 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { continue; } + const store = new DisposableStore(); + if (providerDescriptor.isDefault) { + store.add(this.registerDefaultParticipant(providerDescriptor)); + } + + store.add(this._chatAgentService.registerAgent( + providerDescriptor.id, + { + extensionId: extension.description.identifier, + id: providerDescriptor.id, + description: providerDescriptor.description, + metadata: { + isSticky: providerDescriptor.isSticky, + }, + name: providerDescriptor.name, + isDefault: providerDescriptor.isDefault, + defaultImplicitVariables: providerDescriptor.defaultImplicitVariables, + locations: isNonEmptyArray(providerDescriptor.locations) ? + providerDescriptor.locations.map(ChatAgentLocation.fromRaw) : + [ChatAgentLocation.Panel], + slashCommands: providerDescriptor.commands ?? [] + } satisfies IChatAgentData)); + this._participantRegistrationDisposables.set( getParticipantKey(extension.description.identifier, providerDescriptor.name), - this._chatAgentService.registerAgent( - providerDescriptor.id, - { - extensionId: extension.description.identifier, - id: providerDescriptor.id, - description: providerDescriptor.description, - metadata: { - isSticky: providerDescriptor.isSticky, - }, - name: providerDescriptor.name, - isDefault: providerDescriptor.isDefault, - defaultImplicitVariables: providerDescriptor.defaultImplicitVariables, - locations: isNonEmptyArray(providerDescriptor.locations) ? - providerDescriptor.locations.map(ChatAgentLocation.fromRaw) : - [ChatAgentLocation.Panel], - slashCommands: providerDescriptor.commands ?? [] - } satisfies IChatAgentData)); + store + ); } } @@ -309,72 +249,26 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { return viewContainer; } - private registerChatProvider(providerDescriptor: IRawChatProviderContribution): IDisposable { + private registerDefaultParticipant(defaultParticipantDescriptor: IRawChatParticipantContribution): IDisposable { // Register View - const viewId = this._chatContributionService.getViewIdForProvider(providerDescriptor.id); const viewDescriptor: IViewDescriptor[] = [{ - id: viewId, + id: CHAT_VIEW_ID, containerIcon: this._viewContainer.icon, containerTitle: this._viewContainer.title.value, singleViewPaneContainerTitle: this._viewContainer.title.value, - name: { value: providerDescriptor.label, original: providerDescriptor.label }, + name: { value: defaultParticipantDescriptor.name, original: defaultParticipantDescriptor.name }, canToggleVisibility: false, canMoveView: true, - ctorDescriptor: new SyncDescriptor(ChatViewPane, [{ providerId: providerDescriptor.id }]), - when: ContextKeyExpr.deserialize(providerDescriptor.when) + ctorDescriptor: new SyncDescriptor(ChatViewPane), }]; Registry.as(ViewExtensions.ViewsRegistry).registerViews(viewDescriptor, this._viewContainer); - // Per-provider actions - - // Actions in view title - const disposables = new DisposableStore(); - disposables.add(registerAction2(getHistoryAction(viewId, providerDescriptor.id))); - disposables.add(registerAction2(getNewChatAction(viewId, providerDescriptor.id))); - disposables.add(registerAction2(getMoveToEditorAction(viewId, providerDescriptor.id))); - disposables.add(registerAction2(getMoveToNewWindowAction(viewId, providerDescriptor.id))); - - // "Open Chat" Actions - disposables.add(registerAction2(getOpenChatEditorAction(providerDescriptor.id, providerDescriptor.label, providerDescriptor.when))); - disposables.add(registerAction2(getQuickChatActionForProvider(providerDescriptor.id, providerDescriptor.label))); - - return { - dispose: () => { - Registry.as(ViewExtensions.ViewsRegistry).deregisterViews(viewDescriptor, this._viewContainer); - Registry.as(ViewExtensions.ViewContainersRegistry).deregisterViewContainer(this._viewContainer); - disposables.dispose(); - } - }; + return toDisposable(() => { + Registry.as(ViewExtensions.ViewsRegistry).deregisterViews(viewDescriptor, this._viewContainer); + }); } } -registerWorkbenchContribution2(ChatExtensionPointHandler.ID, ChatExtensionPointHandler, WorkbenchPhase.BlockStartup); - function getParticipantKey(extensionId: ExtensionIdentifier, participantName: string): string { return `${extensionId.value}_${participantName}`; } - -export class ChatContributionService implements IChatContributionService { - declare _serviceBrand: undefined; - - private _registeredProviders = new Map(); - - constructor( - ) { } - - public getViewIdForProvider(providerId: string): string { - return ChatViewPane.ID + '.' + providerId; - } - - public registerChatProvider(provider: IChatProviderContribution): void { - this._registeredProviders.set(provider.id, provider); - } - - public deregisterChatProvider(providerId: string): void { - this._registeredProviders.delete(providerId); - } - - public get registeredProviders(): IChatProviderContribution[] { - return Array.from(this._registeredProviders.values()); - } -} diff --git a/src/vs/workbench/contrib/chat/browser/chatQuick.ts b/src/vs/workbench/contrib/chat/browser/chatQuick.ts index ffe1bb7ccba..44749751359 100644 --- a/src/vs/workbench/contrib/chat/browser/chatQuick.ts +++ b/src/vs/workbench/contrib/chat/browser/chatQuick.ts @@ -17,13 +17,13 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { IQuickInputService, IQuickWidget } from 'vs/platform/quickinput/common/quickInput'; import { editorBackground, inputBackground, quickInputBackground, quickInputForeground } from 'vs/platform/theme/common/colorRegistry'; -import { IChatWidgetService, IQuickChatService, IQuickChatOpenOptions } from 'vs/workbench/contrib/chat/browser/chat'; -import { IChatViewOptions } from 'vs/workbench/contrib/chat/browser/chatViewPane'; +import { IQuickChatOpenOptions, IQuickChatService, showChatView } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget'; import { ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; export class QuickChatService extends Disposable implements IQuickChatService { readonly _serviceBrand: undefined; @@ -45,7 +45,7 @@ export class QuickChatService extends Disposable implements IQuickChatService { } get enabled(): boolean { - return this.chatService.getProviderInfos().length > 0; + return !!this.chatService.isEnabled(ChatAgentLocation.Panel); } get focused(): boolean { @@ -56,13 +56,13 @@ export class QuickChatService extends Disposable implements IQuickChatService { return dom.isAncestorOfActiveElement(widget); } - toggle(providerId?: string, options?: IQuickChatOpenOptions): void { + toggle(options?: IQuickChatOpenOptions): void { // If the input is already shown, hide it. This provides a toggle behavior of the quick // pick. This should not happen when there is a query. if (this.focused && !options?.query) { this.close(); } else { - this.open(providerId, options); + this.open(options); // If this is a partial query, the value should be cleared when closed as otherwise it // would remain for the next time the quick chat is opened in any context. if (options?.isPartialQuery) { @@ -74,7 +74,7 @@ export class QuickChatService extends Disposable implements IQuickChatService { } } - open(providerId?: string, options?: IQuickChatOpenOptions): void { + open(options?: IQuickChatOpenOptions): void { if (this._input) { if (this._currentChat && options?.query) { this._currentChat.focus(); @@ -87,15 +87,6 @@ export class QuickChatService extends Disposable implements IQuickChatService { return this.focus(); } - // Check if any providers are available. If not, show nothing - // This shouldn't be needed because of the precondition, but just in case - const providerInfo = providerId - ? this.chatService.getProviderInfos().find(info => info.id === providerId) - : this.chatService.getProviderInfos()[0]; - if (!providerInfo) { - return; - } - const disposableStore = new DisposableStore(); this._input = this.quickInputService.createQuickWidget(); @@ -108,9 +99,7 @@ export class QuickChatService extends Disposable implements IQuickChatService { this._input.show(); if (!this._currentChat) { - this._currentChat = this.instantiationService.createInstance(QuickChat, { - providerId: providerInfo.id, - }); + this._currentChat = this.instantiationService.createInstance(QuickChat); // show needs to come after the quickpick is shown this._currentChat.render(this._container); @@ -160,12 +149,11 @@ class QuickChat extends Disposable { private _deferUpdatingDynamicLayout: boolean = false; constructor( - private readonly _options: IChatViewOptions, @IInstantiationService private readonly instantiationService: IInstantiationService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IChatService private readonly chatService: IChatService, - @IChatWidgetService private readonly _chatWidgetService: IChatWidgetService, - @ILayoutService private readonly layoutService: ILayoutService + @ILayoutService private readonly layoutService: ILayoutService, + @IViewsService private readonly viewsService: IViewsService, ) { super(); } @@ -288,7 +276,7 @@ class QuickChat extends Disposable { } async openChatView(): Promise { - const widget = await this._chatWidgetService.revealViewForProvider(this._options.providerId); + const widget = await showChatView(this.viewsService); if (!widget?.viewModel || !this.model) { return; } @@ -325,7 +313,7 @@ class QuickChat extends Disposable { } private updateModel(): void { - this.model ??= this.chatService.startSession(this._options.providerId, CancellationToken.None); + this.model ??= this.chatService.startSession(CancellationToken.None); if (!this.model) { throw new Error('Could not start chat session'); } diff --git a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts index 5da2b970613..06e971eaaf5 100644 --- a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts @@ -24,13 +24,11 @@ import { SIDE_BAR_FOREGROUND } from 'vs/workbench/common/theme'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IChatViewPane } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatViewState, ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget'; -import { ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { IChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; +import { ChatAgentLocation, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { CHAT_PROVIDER_ID } from 'vs/workbench/contrib/chat/common/chatParticipantContribTypes'; +import { ChatModelInitState, IChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; - -export interface IChatViewOptions { - readonly providerId: string; -} +import { IChatViewTitleActionContext } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; interface IViewPaneState extends IChatViewState { sessionId?: string; @@ -38,8 +36,6 @@ interface IViewPaneState extends IChatViewState { export const CHAT_SIDEBAR_PANEL_ID = 'workbench.panel.chatSidebar'; export class ChatViewPane extends ViewPane implements IChatViewPane { - static ID = 'workbench.panel.chat.view'; - private _widget!: ChatWidget; get widget(): ChatWidget { return this._widget; } @@ -50,7 +46,6 @@ export class ChatViewPane extends ViewPane implements IChatViewPane { private didUnregisterProvider = false; constructor( - private readonly chatViewOptions: IChatViewOptions, options: IViewPaneOptions, @IKeybindingService keybindingService: IKeybindingService, @IContextMenuService contextMenuService: IContextMenuService, @@ -64,46 +59,53 @@ export class ChatViewPane extends ViewPane implements IChatViewPane { @IHoverService hoverService: IHoverService, @IStorageService private readonly storageService: IStorageService, @IChatService private readonly chatService: IChatService, + @IChatAgentService private readonly chatAgentService: IChatAgentService, @ILogService private readonly logService: ILogService, ) { super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService, hoverService); // View state for the ViewPane is currently global per-provider basically, but some other strictly per-model state will require a separate memento. - this.memento = new Memento('interactive-session-view-' + this.chatViewOptions.providerId, this.storageService); + this.memento = new Memento('interactive-session-view-' + CHAT_PROVIDER_ID, this.storageService); this.viewState = this.memento.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE) as IViewPaneState; - this._register(this.chatService.onDidRegisterProvider(({ providerId }) => { - if (providerId === this.chatViewOptions.providerId && !this._widget?.viewModel) { - const sessionId = this.getSessionId(); - const model = sessionId ? this.chatService.getOrRestoreSession(sessionId) : undefined; + this._register(this.chatAgentService.onDidChangeAgents(() => { + if (this.chatAgentService.getDefaultAgent(ChatAgentLocation.Panel)) { + if (!this._widget?.viewModel) { + const sessionId = this.getSessionId(); + const model = sessionId ? this.chatService.getOrRestoreSession(sessionId) : undefined; - // The widget may be hidden at this point, because welcome views were allowed. Use setVisible to - // avoid doing a render while the widget is hidden. This is changing the condition in `shouldShowWelcome` - // so it should fire onDidChangeViewWelcomeState. - try { - this._widget.setVisible(false); - this.updateModel(model); - this.didProviderRegistrationFail = false; - this.didUnregisterProvider = false; - this._onDidChangeViewWelcomeState.fire(); - } finally { - this.widget.setVisible(true); + // The widget may be hidden at this point, because welcome views were allowed. Use setVisible to + // avoid doing a render while the widget is hidden. This is changing the condition in `shouldShowWelcome` + // so it should fire onDidChangeViewWelcomeState. + try { + this._widget.setVisible(false); + this.updateModel(model); + this.didProviderRegistrationFail = false; + this.didUnregisterProvider = false; + this._onDidChangeViewWelcomeState.fire(); + } finally { + this.widget.setVisible(true); + } } - } - })); - this._register(this.chatService.onDidUnregisterProvider(({ providerId }) => { - if (providerId === this.chatViewOptions.providerId) { + } else if (this._widget?.viewModel?.initState === ChatModelInitState.Initialized) { + // Model is initialized, and the default agent disappeared, so show welcome view this.didUnregisterProvider = true; this._onDidChangeViewWelcomeState.fire(); } })); } + override getActionsContext(): IChatViewTitleActionContext { + return { + chatView: this + }; + } + private updateModel(model?: IChatModel | undefined, viewState?: IViewPaneState): void { this.modelDisposables.clear(); model = model ?? (this.chatService.transferredSessionData?.sessionId ? this.chatService.getOrRestoreSession(this.chatService.transferredSessionData.sessionId) - : this.chatService.startSession(this.chatViewOptions.providerId, CancellationToken.None)); + : this.chatService.startSession(CancellationToken.None)); if (!model) { throw new Error('Could not start chat session'); } @@ -113,7 +115,7 @@ export class ChatViewPane extends ViewPane implements IChatViewPane { } override shouldShowWelcome(): boolean { - const noPersistedSessions = !this.chatService.hasSessions(this.chatViewOptions.providerId); + const noPersistedSessions = !this.chatService.hasSessions(); return this.didUnregisterProvider || !this._widget?.viewModel && (noPersistedSessions || this.didProviderRegistrationFail); } @@ -155,7 +157,7 @@ export class ChatViewPane extends ViewPane implements IChatViewPane { // Render the welcome view if this session gets disposed at any point, // including if the provider registration fails const disposeListener = sessionId ? this._register(this.chatService.onDidDisposeSession((e) => { - if (e.reason === 'initializationFailed' && e.providerId === this.chatViewOptions.providerId) { + if (e.reason === 'initializationFailed') { this.didProviderRegistrationFail = true; disposeListener?.dispose(); this._onDidChangeViewWelcomeState.fire(); @@ -174,7 +176,7 @@ export class ChatViewPane extends ViewPane implements IChatViewPane { this._widget.acceptInput(query); } - async clear(): Promise { + clear(): void { if (this.widget.viewModel) { this.chatService.clearSession(this.widget.viewModel.sessionId); } diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index de76221655a..30ae6217443 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -29,12 +29,9 @@ import { ChatTreeItem, IChatAccessibilityService, IChatCodeBlockInfo, IChatFileT import { ChatAccessibilityProvider } from 'vs/workbench/contrib/chat/browser/chatAccessibilityProvider'; import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart'; import { ChatListDelegate, ChatListItemRenderer, IChatRendererDelegate } from 'vs/workbench/contrib/chat/browser/chatListRenderer'; -import { IChatListItemRendererOptions } from './chat'; import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions'; -import { ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane'; import { ChatAgentLocation, IChatAgentCommand, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { CONTEXT_CHAT_INPUT_HAS_AGENT, CONTEXT_CHAT_LOCATION, CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_SESSION, CONTEXT_RESPONSE_FILTERED } 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 { ChatRequestAgentPart, IParsedChatRequest, chatAgentLeader, chatSubcommandLeader, extractAgentAndCommand } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; @@ -42,7 +39,7 @@ import { IChatFollowup, IChatService } from 'vs/workbench/contrib/chat/common/ch import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { ChatViewModel, IChatResponseViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { CodeBlockModelCollection } from 'vs/workbench/contrib/chat/common/codeBlockModelCollection'; -import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; +import { IChatListItemRendererOptions } from './chat'; const $ = dom.$; @@ -242,10 +239,6 @@ export class ChatWidget extends Disposable implements IChatWidget { return !!this.viewOptions.supportsFileReferences; } - get providerId(): string { - return this.viewModel?.providerId || ''; - } - get input(): ChatInputPart { return this.inputPart; } @@ -563,7 +556,6 @@ export class ChatWidget extends Disposable implements IChatWidget { } this.chatService.notifyUserAction({ - providerId: this.viewModel.providerId, sessionId: this.viewModel.sessionId, requestId: e.response.requestId, agentId: e.response.agent?.id, @@ -641,7 +633,7 @@ export class ChatWidget extends Disposable implements IChatWidget { this.viewModel = undefined; this.onDidChangeItems(); })); - this.inputPart.setState(model.providerId, viewState.inputValue); + this.inputPart.setState(viewState.inputValue); this.contribs.forEach(c => { if (c.setInputState && viewState.inputState?.[c.id]) { c.setInputState(viewState.inputState?.[c.id]); @@ -914,10 +906,7 @@ export class ChatWidgetService implements IChatWidgetService { return this._lastFocusedWidget; } - constructor( - @IViewsService private readonly viewsService: IViewsService, - @IChatContributionService private readonly chatContributionService: IChatContributionService, - ) { } + constructor() { } getWidgetByInputUri(uri: URI): ChatWidget | undefined { return this._widgets.find(w => isEqual(w.inputUri, uri)); @@ -927,13 +916,6 @@ export class ChatWidgetService implements IChatWidgetService { return this._widgets.find(w => w.viewModel?.sessionId === sessionId); } - async revealViewForProvider(providerId: string): Promise { - const viewId = this.chatContributionService.getViewIdForProvider(providerId); - const view = await this.viewsService.openView(viewId); - - return view?.widget; - } - private setLastFocusedWidget(widget: ChatWidget | undefined): void { if (widget === this._lastFocusedWidget) { return; diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index fb19f9f6abd..c9c5335c188 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -11,11 +11,12 @@ import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ThemeIcon } from 'vs/base/common/themables'; import { URI } from 'vs/base/common/uri'; import { ProviderResult } from 'vs/editor/common/languages'; -import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IRawChatCommandContribution, RawChatParticipantLocation } from 'vs/workbench/contrib/chat/common/chatContributionService'; +import { CONTEXT_HAS_DEFAULT_AGENT } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatProgressResponseContent, IChatRequestVariableData } from 'vs/workbench/contrib/chat/common/chatModel'; +import { IRawChatCommandContribution, RawChatParticipantLocation } from 'vs/workbench/contrib/chat/common/chatParticipantContribTypes'; import { IChatFollowup, IChatProgress, IChatResponseErrorDetails } from 'vs/workbench/contrib/chat/common/chatService'; //#region agent service, commands etc @@ -139,7 +140,16 @@ export interface IChatAgentService { getAgents(): IChatAgentData[]; getActivatedAgents(): Array; getAgentsByName(name: string): IChatAgentData[]; + + /** + * Get the default agent (only if activated) + */ getDefaultAgent(location: ChatAgentLocation): IChatAgent | undefined; + + /** + * Get the default agent data that has been contributed (may not be activated yet) + */ + getContributedDefaultAgent(location: ChatAgentLocation): IChatAgentData | undefined; getSecondaryAgent(): IChatAgentData | undefined; updateAgent(id: string, updateMetadata: IChatAgentMetadata): void; } @@ -155,9 +165,13 @@ export class ChatAgentService implements IChatAgentService { private readonly _onDidChangeAgents = new Emitter(); readonly onDidChangeAgents: Event = this._onDidChangeAgents.event; + private readonly _hasDefaultAgent: IContextKey; + constructor( @IContextKeyService private readonly contextKeyService: IContextKeyService - ) { } + ) { + this._hasDefaultAgent = CONTEXT_HAS_DEFAULT_AGENT.bindTo(this.contextKeyService); + } registerAgent(id: string, data: IChatAgentData): IDisposable { const existingAgent = this.getAgent(id); @@ -191,12 +205,20 @@ export class ChatAgentService implements IChatAgentService { throw new Error(`Agent already has implementation: ${JSON.stringify(id)}`); } + if (entry.data.isDefault) { + this._hasDefaultAgent.set(true); + } + entry.impl = agentImpl; this._onDidChangeAgents.fire(new MergedChatAgent(entry.data, agentImpl)); return toDisposable(() => { entry.impl = undefined; this._onDidChangeAgents.fire(undefined); + + if (entry.data.isDefault) { + this._hasDefaultAgent.set(false); + } }); } @@ -224,6 +246,10 @@ export class ChatAgentService implements IChatAgentService { return this.getActivatedAgents().find(a => !!a.isDefault && a.locations.includes(location)); } + getContributedDefaultAgent(location: ChatAgentLocation): IChatAgentData | undefined { + return this.getAgents().find(a => !!a.isDefault && a.locations.includes(location)); + } + getSecondaryAgent(): IChatAgentData | undefined { // TODO also static return Iterable.find(this._agents.values(), a => !!a.data.metadata.isSecondary)?.data; diff --git a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts index 417b72ff410..caa89c515f5 100644 --- a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts +++ b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts @@ -21,7 +21,8 @@ export const CONTEXT_CHAT_INPUT_HAS_FOCUS = new RawContextKey('chatInpu export const CONTEXT_IN_CHAT_INPUT = new RawContextKey('inChatInput', false, { type: 'boolean', description: localize('inInteractiveInput', "True when focus is in the chat input, false otherwise.") }); export const CONTEXT_IN_CHAT_SESSION = new RawContextKey('inChat', false, { type: 'boolean', description: localize('inChat', "True when focus is in the chat widget, false otherwise.") }); -export const CONTEXT_PROVIDER_EXISTS = new RawContextKey('hasChatProvider', false, { type: 'boolean', description: localize('hasChatProvider', "True when some chat provider has been registered.") }); +// TODO call it 'has default agent' +export const CONTEXT_HAS_DEFAULT_AGENT = new RawContextKey('hasChatProvider', false, { type: 'boolean', description: localize('hasChatProvider', "True when some chat provider has been registered.") }); export const CONTEXT_CHAT_INPUT_CURSOR_AT_TOP = new RawContextKey('chatCursorAtTop', false); export const CONTEXT_CHAT_INPUT_HAS_AGENT = new RawContextKey('chatInputHasAgent', false); export const CONTEXT_CHAT_LOCATION = new RawContextKey('chatLocation', undefined); diff --git a/src/vs/workbench/contrib/chat/common/chatContributionService.ts b/src/vs/workbench/contrib/chat/common/chatContributionService.ts deleted file mode 100644 index 7d7452b1957..00000000000 --- a/src/vs/workbench/contrib/chat/common/chatContributionService.ts +++ /dev/null @@ -1,56 +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 { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; - -export interface IChatProviderContribution { - id: string; - when?: string; -} - -export const IChatContributionService = createDecorator('IChatContributionService'); -export interface IChatContributionService { - _serviceBrand: undefined; - - registeredProviders: IChatProviderContribution[]; - registerChatProvider(provider: IChatProviderContribution): void; - deregisterChatProvider(providerId: string): void; - getViewIdForProvider(providerId: string): string; -} - -export interface IRawChatProviderContribution { - id: string; - label: string; - icon?: string; - when?: string; -} - -export interface IRawChatCommandContribution { - name: string; - description: string; - sampleRequest?: string; - isSticky?: boolean; - when?: string; - defaultImplicitVariables?: string[]; -} - -export type RawChatParticipantLocation = 'panel' | 'terminal' | 'notebook'; - -export interface IRawChatParticipantContribution { - id: string; - name: string; - description?: string; - isDefault?: boolean; - isSticky?: boolean; - commands?: IRawChatCommandContribution[]; - defaultImplicitVariables?: string[]; - locations?: RawChatParticipantLocation[]; -} - -export interface IChatParticipantContribution extends IRawChatParticipantContribution { - // Participant id is extensionId + name - extensionId: ExtensionIdentifier; -} diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 3f27837a5c9..1fe97569d5e 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -18,7 +18,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ILogService } from 'vs/platform/log/common/log'; import { ChatAgentLocation, IChatAgentCommand, IChatAgentData, IChatAgentHistoryEntry, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatRequestTextPart, IParsedChatRequest, getPromptText, reviveParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { IChat, IChatAgentMarkdownContentWithVulnerability, IChatCommandButton, IChatContent, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgress, IChatProgressMessage, IChatResponseProgressFileTreeData, IChatTextEdit, IChatTreeData, IChatUsedContext, InteractiveSessionVoteDirection, isIUsedContext } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatAgentMarkdownContentWithVulnerability, IChatCommandButton, IChatContent, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgress, IChatProgressMessage, IChatResponseProgressFileTreeData, IChatTextEdit, IChatTreeData, IChatUsedContext, InteractiveSessionVoteDirection, isIUsedContext } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; export interface IChatPromptVariableData { @@ -65,7 +65,6 @@ export interface IResponse { export interface IChatResponseModel { readonly onDidChange: Event; readonly id: string; - readonly providerId: string; readonly requestId: string; readonly username: string; readonly avatarIcon?: ThemeIcon | URI; @@ -260,10 +259,6 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel return this._result; } - public get providerId(): string { - return this.session.providerId; - } - public get username(): string { return this.session.responderUsername; } @@ -391,7 +386,6 @@ export interface IChatModel { readonly onDidDispose: Event; readonly onDidChange: Event; readonly sessionId: string; - readonly providerId: string; readonly initState: ChatModelInitState; readonly title: string; readonly welcomeMessage: IChatWelcomeMessageModel | undefined; @@ -426,7 +420,6 @@ export interface ISerializableChatRequestData { } export interface IExportableChatData { - providerId: string; welcomeMessage: (string | IChatFollowup[])[] | undefined; requests: ISerializableChatRequestData[]; requesterUsername: string; @@ -444,7 +437,6 @@ export interface ISerializableChatData extends IExportableChatData { export function isExportableSessionData(obj: unknown): obj is IExportableChatData { const data = obj as IExportableChatData; return typeof data === 'object' && - typeof data.providerId === 'string' && typeof data.requesterUsername === 'string'; } @@ -505,11 +497,6 @@ export class ChatModel extends Disposable implements IChatModel { private _initState: ChatModelInitState = ChatModelInitState.Created; private _isInitializedDeferred = new DeferredPromise(); - private _session: IChat | undefined; - get session(): IChat | undefined { - return this._session; - } - private _welcomeMessage: ChatWelcomeMessageModel | undefined; get welcomeMessage(): ChatWelcomeMessageModel | undefined { return this._welcomeMessage; @@ -580,7 +567,6 @@ export class ChatModel extends Disposable implements IChatModel { } constructor( - public readonly providerId: string, private readonly initialData: ISerializableChatData | IExportableChatData | undefined, @ILogService private readonly logService: ILogService, @IChatAgentService private readonly chatAgentService: IChatAgentService, @@ -672,19 +658,17 @@ export class ChatModel extends Disposable implements IChatModel { } deinitialize(): void { - this._session = undefined; this._initState = ChatModelInitState.Created; this._isInitializedDeferred = new DeferredPromise(); } - initialize(session: IChat, welcomeMessage: ChatWelcomeMessageModel | undefined): void { + initialize(welcomeMessage: ChatWelcomeMessageModel | undefined): void { if (this.initState !== ChatModelInitState.Initializing) { // Must call startInitialize before initialize, and only call it once throw new Error(`ChatModel is in the wrong state for initialize: ${ChatModelInitState[this.initState]}`); } this._initState = ChatModelInitState.Initialized; - this._session = session; if (!this._welcomeMessage) { // Could also have loaded the welcome message from persisted data this._welcomeMessage = welcomeMessage; @@ -713,10 +697,6 @@ export class ChatModel extends Disposable implements IChatModel { } addRequest(message: IParsedChatRequest, variableData: IChatRequestVariableData, chatAgent?: IChatAgentData, slashCommand?: IChatAgentCommand): ChatRequestModel { - if (!this._session) { - throw new Error('addRequest: No session'); - } - const request = new ChatRequestModel(this, message, variableData); request.response = new ChatResponseModel([], this, chatAgent, slashCommand, request.id); @@ -726,10 +706,6 @@ export class ChatModel extends Disposable implements IChatModel { } acceptResponseProgress(request: ChatRequestModel, progress: IChatProgress, quiet?: boolean): void { - if (!this._session) { - throw new Error('acceptResponseProgress: No session'); - } - if (!request.response) { request.response = new ChatResponseModel([], this, undefined, undefined, request.id); } @@ -772,10 +748,6 @@ export class ChatModel extends Disposable implements IChatModel { } setResponse(request: ChatRequestModel, result: IChatAgentResult): void { - if (!this._session) { - throw new Error('completeResponse: No session'); - } - if (!request.response) { request.response = new ChatResponseModel([], this, undefined, undefined, request.id); } @@ -851,7 +823,6 @@ export class ChatModel extends Disposable implements IChatModel { contentReferences: r.response?.contentReferences }; }), - providerId: this.providerId, }; } @@ -865,7 +836,6 @@ export class ChatModel extends Disposable implements IChatModel { } override dispose() { - this._session?.dispose?.(); this._requests.forEach(r => r.response?.dispose()); this._onDidDispose.fire(); diff --git a/src/vs/workbench/contrib/chat/common/chatParticipantContribTypes.ts b/src/vs/workbench/contrib/chat/common/chatParticipantContribTypes.ts new file mode 100644 index 00000000000..3a190bf172b --- /dev/null +++ b/src/vs/workbench/contrib/chat/common/chatParticipantContribTypes.ts @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export interface IRawChatCommandContribution { + name: string; + description: string; + sampleRequest?: string; + isSticky?: boolean; + when?: string; + defaultImplicitVariables?: string[]; +} + +export type RawChatParticipantLocation = 'panel' | 'terminal' | 'notebook'; + +export interface IRawChatParticipantContribution { + id: string; + name: string; + description?: string; + isDefault?: boolean; + isSticky?: boolean; + commands?: IRawChatCommandContribution[]; + defaultImplicitVariables?: string[]; + locations?: RawChatParticipantLocation[]; +} + +/** + * Hardcoding the previous id of the Copilot Chat provider to avoid breaking view locations, persisted data, etc. + * DON'T use this for any new data, only for old persisted data. + * @deprecated + */ +export const CHAT_PROVIDER_ID = 'copilot'; diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 819c41f3045..fa41b8b7153 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -6,25 +6,18 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; import { IMarkdownString } from 'vs/base/common/htmlContent'; -import { IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IRange, Range } from 'vs/editor/common/core/range'; -import { Command, Location, ProviderResult, TextEdit } from 'vs/editor/common/languages'; +import { Command, Location, TextEdit } from 'vs/editor/common/languages'; import { FileType } from 'vs/platform/files/common/files'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ChatAgentLocation, IChatAgentCommand, IChatAgentData, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { ChatModel, IChatModel, IChatRequestVariableData, ISerializableChatData } from 'vs/workbench/contrib/chat/common/chatModel'; +import { ChatModel, IChatModel, IChatRequestVariableData, IExportableChatData, ISerializableChatData } from 'vs/workbench/contrib/chat/common/chatModel'; import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IChatParserContext } from 'vs/workbench/contrib/chat/common/chatRequestParser'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; -export interface IChat { - id: number; // TODO Maybe remove this and move to a subclass that only the provider knows about - dispose?(): void; -} - export interface IChatRequest { - session: IChat; message: string; variables: Record; } @@ -159,11 +152,6 @@ export type IChatProgress = | IChatCommandButton | IChatTextEdit; -export interface IChatProvider { - readonly id: string; - prepareSession(token: CancellationToken): ProviderResult; -} - export interface IChatFollowup { kind: 'reply'; message: string; @@ -231,7 +219,6 @@ export type ChatUserAction = IChatVoteAction | IChatCopyAction | IChatInsertActi export interface IChatUserActionEvent { action: ChatUserAction; - providerId: string; agentId: string | undefined; sessionId: string; requestId: string; @@ -282,16 +269,12 @@ export interface IChatService { _serviceBrand: undefined; transferredSessionData: IChatTransferredSessionData | undefined; - onDidRegisterProvider: Event<{ providerId: string }>; - onDidUnregisterProvider: Event<{ providerId: string }>; - registerProvider(provider: IChatProvider): IDisposable; - hasSessions(providerId: string): boolean; - getProviderInfos(): IChatProviderInfo[]; - startSession(providerId: string, token: CancellationToken): ChatModel | undefined; + isEnabled(location: ChatAgentLocation): boolean; + hasSessions(): boolean; + startSession(token: CancellationToken): ChatModel | undefined; getSession(sessionId: string): IChatModel | undefined; - getSessionId(sessionProviderId: number): string | undefined; getOrRestoreSession(sessionId: string): IChatModel | undefined; - loadSessionFromContent(data: ISerializableChatData): IChatModel | undefined; + loadSessionFromContent(data: IExportableChatData | ISerializableChatData): IChatModel | undefined; /** * Returns whether the request was accepted. @@ -307,7 +290,7 @@ export interface IChatService { onDidPerformUserAction: Event; notifyUserAction(event: IChatUserActionEvent): void; - onDidDisposeSession: Event<{ sessionId: string; providerId: string; reason: 'initializationFailed' | 'cleared' }>; + onDidDisposeSession: Event<{ sessionId: string; reason: 'initializationFailed' | 'cleared' }>; transferChatSession(transferredSessionData: IChatTransferredSessionData, toWorkspace: URI): void; } diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 1667fd45bb9..d5fafd28838 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -8,13 +8,12 @@ import { ErrorNoTelemetry } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { Iterable } from 'vs/base/common/iterator'; -import { Disposable, DisposableMap, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableMap } 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'; import { localize } from 'vs/nls'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { Progress } from 'vs/platform/progress/common/progress'; @@ -22,11 +21,10 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { ChatAgentLocation, IChatAgent, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; -import { ChatModel, ChatModelInitState, ChatRequestModel, ChatWelcomeMessageModel, IChatModel, IChatRequestVariableData, IChatRequestVariableEntry, ISerializableChatData, ISerializableChatsData, getHistoryEntriesFromModel, updateRanges } from 'vs/workbench/contrib/chat/common/chatModel'; +import { ChatModel, ChatRequestModel, ChatWelcomeMessageModel, IChatModel, IChatRequestVariableData, IChatRequestVariableEntry, IExportableChatData, ISerializableChatData, ISerializableChatsData, getHistoryEntriesFromModel, updateRanges } from 'vs/workbench/contrib/chat/common/chatModel'; import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, IParsedChatRequest, getPromptText } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatRequestParser, IChatParserContext } from 'vs/workbench/contrib/chat/common/chatRequestParser'; -import { ChatCopyKind, IChat, IChatCompleteResponse, IChatDetail, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatSendRequestData, IChatService, IChatTransferredSessionData, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { ChatCopyKind, IChatCompleteResponse, IChatDetail, IChatFollowup, IChatProgress, IChatSendRequestData, IChatService, IChatTransferredSessionData, IChatUserActionEvent, 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 { ChatMessageRole, IChatMessage } from 'vs/workbench/contrib/chat/common/languageModels'; @@ -44,7 +42,6 @@ interface IChatTransfer { const SESSION_TRANSFER_EXPIRATION_IN_MILLISECONDS = 1000 * 60; type ChatProviderInvokedEvent = { - providerId: string; timeToFirstProgress: number | undefined; totalTime: number | undefined; result: 'success' | 'error' | 'errorWithOutput' | 'cancelled' | 'filtered'; @@ -55,7 +52,6 @@ type ChatProviderInvokedEvent = { }; type ChatProviderInvokedClassification = { - providerId: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The identifier of the provider that was invoked.' }; timeToFirstProgress: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The time in milliseconds from invoking the provider to getting the first data.' }; totalTime: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The total time it took to run the provider\'s `provideResponseWithProgress`.' }; result: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether invoking the ChatProvider resulted in an error.' }; @@ -68,60 +64,50 @@ type ChatProviderInvokedClassification = { }; type ChatVoteEvent = { - providerId: string; direction: 'up' | 'down'; }; type ChatVoteClassification = { - providerId: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The identifier of the provider that this response came from.' }; direction: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the user voted up or down.' }; owner: 'roblourens'; comment: 'Provides insight into the performance of Chat providers.'; }; type ChatCopyEvent = { - providerId: string; copyKind: 'action' | 'toolbar'; }; type ChatCopyClassification = { - providerId: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The identifier of the provider that this codeblock response came from.' }; copyKind: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'How the copy was initiated.' }; owner: 'roblourens'; comment: 'Provides insight into the usage of Chat features.'; }; type ChatInsertEvent = { - providerId: string; newFile: boolean; }; type ChatInsertClassification = { - providerId: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The identifier of the provider that this codeblock response came from.' }; newFile: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the code was inserted into a new untitled file.' }; owner: 'roblourens'; comment: 'Provides insight into the usage of Chat features.'; }; type ChatCommandEvent = { - providerId: string; commandId: string; }; type ChatCommandClassification = { - providerId: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The identifier of the provider that this codeblock response came from.' }; commandId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The id of the command that was executed.' }; owner: 'roblourens'; comment: 'Provides insight into the usage of Chat features.'; }; type ChatTerminalEvent = { - providerId: string; languageId: string; }; type ChatTerminalClassification = { - providerId: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The identifier of the provider that this codeblock response came from.' }; languageId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The language of the code that was run in the terminal.' }; owner: 'roblourens'; comment: 'Provides insight into the usage of Chat features.'; @@ -132,12 +118,10 @@ const maxPersistedSessions = 25; export class ChatService extends Disposable implements IChatService { declare _serviceBrand: undefined; - private readonly _providers = new Map(); - private readonly _sessionModels = this._register(new DisposableMap()); private readonly _pendingRequests = this._register(new DisposableMap()); private _persistedSessions: ISerializableChatsData; - private readonly _hasProvider: IContextKey; + private _transferredSessionData: IChatTransferredSessionData | undefined; public get transferredSessionData(): IChatTransferredSessionData | undefined { @@ -147,15 +131,9 @@ export class ChatService extends Disposable implements IChatService { private readonly _onDidPerformUserAction = this._register(new Emitter()); public readonly onDidPerformUserAction: Event = this._onDidPerformUserAction.event; - private readonly _onDidDisposeSession = this._register(new Emitter<{ sessionId: string; providerId: string; reason: 'initializationFailed' | 'cleared' }>()); + private readonly _onDidDisposeSession = this._register(new Emitter<{ sessionId: string; reason: 'initializationFailed' | 'cleared' }>()); public readonly onDidDisposeSession = this._onDidDisposeSession.event; - private readonly _onDidRegisterProvider = this._register(new Emitter<{ providerId: string }>()); - public readonly onDidRegisterProvider = this._onDidRegisterProvider.event; - - private readonly _onDidUnregisterProvider = this._register(new Emitter<{ providerId: string }>()); - public readonly onDidUnregisterProvider = this._onDidUnregisterProvider.event; - private readonly _sessionFollowupCancelTokens = this._register(new DisposableMap()); constructor( @@ -164,7 +142,6 @@ export class ChatService extends Disposable implements IChatService { @IExtensionService private readonly extensionService: IExtensionService, @IInstantiationService private readonly instantiationService: IInstantiationService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @IChatSlashCommandService private readonly chatSlashCommandService: IChatSlashCommandService, @IChatVariablesService private readonly chatVariablesService: IChatVariablesService, @@ -172,8 +149,6 @@ export class ChatService extends Disposable implements IChatService { ) { super(); - this._hasProvider = CONTEXT_PROVIDER_EXISTS.bindTo(this.contextKeyService); - const sessionData = storageService.get(serializedChatKey, StorageScope.WORKSPACE, ''); if (sessionData) { this._persistedSessions = this.deserializeChats(sessionData); @@ -196,6 +171,10 @@ export class ChatService extends Disposable implements IChatService { this._register(storageService.onWillSaveState(() => this.saveState())); } + isEnabled(location: ChatAgentLocation): boolean { + return this.chatAgentService.getContributedDefaultAgent(location) !== undefined; + } + private saveState(): void { let allSessions: (ChatModel | ISerializableChatData)[] = Array.from(this._sessionModels.values()) .filter(session => session.getRequests().length > 0); @@ -221,29 +200,24 @@ export class ChatService extends Disposable implements IChatService { notifyUserAction(action: IChatUserActionEvent): void { if (action.action.kind === 'vote') { this.telemetryService.publicLog2('interactiveSessionVote', { - providerId: action.providerId, direction: action.action.direction === InteractiveSessionVoteDirection.Up ? 'up' : 'down' }); } else if (action.action.kind === 'copy') { this.telemetryService.publicLog2('interactiveSessionCopy', { - providerId: action.providerId, copyKind: action.action.copyKind === ChatCopyKind.Action ? 'action' : 'toolbar' }); } else if (action.action.kind === 'insert') { this.telemetryService.publicLog2('interactiveSessionInsert', { - providerId: action.providerId, newFile: !!action.action.newFile }); } else if (action.action.kind === 'command') { const command = CommandsRegistry.getCommand(action.action.commandButton.command.id); const commandId = command ? action.action.commandButton.command.id : 'INVALID'; this.telemetryService.publicLog2('interactiveSessionCommand', { - providerId: action.providerId, commandId }); } else if (action.action.kind === 'runInTerminal') { this.telemetryService.publicLog2('interactiveSessionRunInTerminal', { - providerId: action.providerId, languageId: action.action.languageId ?? '' }); } @@ -251,8 +225,12 @@ export class ChatService extends Disposable implements IChatService { this._onDidPerformUserAction.fire(action); } - private trace(method: string, message: string): void { - this.logService.trace(`ChatService#${method}: ${message}`); + private trace(method: string, message?: string): void { + if (message) { + this.logService.trace(`ChatService#${method}: ${message}`); + } else { + this.logService.trace(`ChatService#${method}`); + } } private error(method: string, message: string): void { @@ -341,53 +319,42 @@ export class ChatService extends Disposable implements IChatService { this.saveState(); } - startSession(providerId: string, token: CancellationToken): ChatModel { - this.trace('startSession', `providerId=${providerId}`); - return this._startSession(providerId, undefined, token); + startSession(token: CancellationToken): ChatModel { + this.trace('startSession'); + return this._startSession(undefined, token); } - private _startSession(providerId: string, someSessionHistory: ISerializableChatData | undefined, token: CancellationToken): ChatModel { - this.trace('_startSession', `providerId=${providerId}`); - const model = this.instantiationService.createInstance(ChatModel, providerId, someSessionHistory); + private _startSession(someSessionHistory: IExportableChatData | ISerializableChatData | undefined, token: CancellationToken): ChatModel { + const model = this.instantiationService.createInstance(ChatModel, someSessionHistory); this._sessionModels.set(model.sessionId, model); this.initializeSession(model, token); return model; } - private reinitializeModel(model: ChatModel): void { - this.trace('reinitializeModel', `Start reinit`); - this.initializeSession(model, CancellationToken.None); - } + // TODO, when default agent shows up? + // private reinitializeModel(model: ChatModel): void { + // this.trace('reinitializeModel', `Start reinit`); + // this.initializeSession(model, CancellationToken.None); + // } private async initializeSession(model: ChatModel, token: CancellationToken): Promise { try { this.trace('initializeSession', `Initialize session ${model.sessionId}`); model.startInitialize(); - await this.extensionService.activateByEvent(`onInteractiveSession:${model.providerId}`); - const provider = this._providers.get(model.providerId); - if (!provider) { - throw new ErrorNoTelemetry(`Unknown provider: ${model.providerId}`); + await this.extensionService.whenInstalledExtensionsRegistered(); + const defaultAgentData = this.chatAgentService.getContributedDefaultAgent(ChatAgentLocation.Panel); + if (!defaultAgentData) { + throw new ErrorNoTelemetry('No default agent contributed'); } - let session: IChat | undefined; - try { - session = await provider.prepareSession(token) ?? undefined; - } catch (err) { - this.trace('initializeSession', `Provider initializeSession threw: ${err}`); - } - - if (!session) { - throw new Error('Provider returned no session'); - } - - this.trace('startSession', `Provider returned session`); + await this.extensionService.activateByEvent(`onChatParticipant:${defaultAgentData.id}`); const defaultAgent = this.chatAgentService.getDefaultAgent(ChatAgentLocation.Panel); if (!defaultAgent) { - throw new ErrorNoTelemetry('No default agent'); + // Should have been registered during activation above! + throw new ErrorNoTelemetry('No default agent registered'); } - const welcomeMessage = model.welcomeMessage ? undefined : await defaultAgent.provideWelcomeMessage?.(token) ?? undefined; const welcomeModel = welcomeMessage && this.instantiationService.createInstance( ChatWelcomeMessageModel, @@ -395,12 +362,12 @@ export class ChatService extends Disposable implements IChatService { await defaultAgent.provideSampleQuestions?.(token) ?? [] ); - model.initialize(session, welcomeModel); + model.initialize(welcomeModel); } catch (err) { this.trace('startSession', `initializeSession failed: ${err}`); model.setInitializationError(err); this._sessionModels.deleteAndDispose(model.sessionId); - this._onDidDisposeSession.fire({ sessionId: model.sessionId, providerId: model.providerId, reason: 'initializationFailed' }); + this._onDidDisposeSession.fire({ sessionId: model.sessionId, reason: 'initializationFailed' }); } } @@ -408,10 +375,6 @@ export class ChatService extends Disposable implements IChatService { return this._sessionModels.get(sessionId); } - getSessionId(sessionProviderId: number): string | undefined { - return Iterable.find(this._sessionModels.values(), model => model.session?.id === sessionProviderId)?.sessionId; - } - getOrRestoreSession(sessionId: string): ChatModel | undefined { this.trace('getOrRestoreSession', `sessionId: ${sessionId}`); const model = this._sessionModels.get(sessionId); @@ -428,11 +391,11 @@ export class ChatService extends Disposable implements IChatService { this._transferredSessionData = undefined; } - return this._startSession(sessionData.providerId, sessionData, CancellationToken.None); + return this._startSession(sessionData, CancellationToken.None); } - loadSessionFromContent(data: ISerializableChatData): IChatModel | undefined { - return this._startSession(data.providerId, data, CancellationToken.None); + loadSessionFromContent(data: IExportableChatData | ISerializableChatData): IChatModel | undefined { + return this._startSession(data, CancellationToken.None); } async sendRequest(sessionId: string, request: string, implicitVariablesEnabled?: boolean, location: ChatAgentLocation = ChatAgentLocation.Panel, parserContext?: IChatParserContext): Promise { @@ -448,10 +411,6 @@ export class ChatService extends Disposable implements IChatService { } await model.waitForInitialization(); - const provider = this._providers.get(model.providerId); - if (!provider) { - throw new Error(`Unknown provider: ${model.providerId}`); - } if (this._pendingRequests.has(sessionId)) { this.trace('sendRequest', `Session ${sessionId} already has a pending request`); @@ -466,7 +425,7 @@ 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, parsedRequest, implicitVariablesEnabled ?? false, defaultAgent, location), + responseCompletePromise: this._sendRequestAsync(model, sessionId, parsedRequest, implicitVariablesEnabled ?? false, defaultAgent, location), agent, slashCommand: agentSlashCommandPart?.command, }; @@ -480,7 +439,7 @@ export class ChatService extends Disposable implements IChatService { return newTokenSource.token; } - private async _sendRequestAsync(model: ChatModel, sessionId: string, provider: IChatProvider, parsedRequest: IParsedChatRequest, implicitVariablesEnabled: boolean, defaultAgent: IChatAgent, location: ChatAgentLocation): Promise { + private async _sendRequestAsync(model: ChatModel, sessionId: string, parsedRequest: IParsedChatRequest, implicitVariablesEnabled: boolean, defaultAgent: IChatAgent, location: ChatAgentLocation): Promise { const followupsCancelToken = this.refreshFollowupsCancellationToken(sessionId); let request: ChatRequestModel; const agentPart = 'kind' in parsedRequest ? undefined : parsedRequest.parts.find((r): r is ChatRequestAgentPart => r instanceof ChatRequestAgentPart); @@ -513,7 +472,6 @@ export class ChatService extends Disposable implements IChatService { const listener = token.onCancellationRequested(() => { this.trace('sendRequest', `Request for session ${model.sessionId} was cancelled`); this.telemetryService.publicLog2('interactiveSessionProviderInvoked', { - providerId: provider.id, timeToFirstProgress: undefined, // Normally timings happen inside the EH around the actual provider. For cancellation we can measure how long the user waited before cancelling totalTime: stopWatch.elapsed(), @@ -600,7 +558,6 @@ export class ChatService extends Disposable implements IChatService { rawResult.errorDetails ? 'error' : 'success'; this.telemetryService.publicLog2('interactiveSessionProviderInvoked', { - providerId: provider.id, timeToFirstProgress: rawResult.timings?.firstProgress, totalTime: rawResult.timings?.totalElapsed, result, @@ -638,18 +595,10 @@ export class ChatService extends Disposable implements IChatService { } await model.waitForInitialization(); - const provider = this._providers.get(model.providerId); - if (!provider) { - throw new Error(`Unknown provider: ${model.providerId}`); - } model.removeRequest(requestId); } - getProviders(): string[] { - return Array.from(this._providers.keys()); - } - async addCompleteRequest(sessionId: string, message: IParsedChatRequest | string, variableData: IChatRequestVariableData | undefined, response: IChatCompleteResponse): Promise { this.trace('addCompleteRequest', `message: ${message}`); @@ -695,47 +644,11 @@ export class ChatService extends Disposable implements IChatService { this._sessionModels.deleteAndDispose(sessionId); this._pendingRequests.get(sessionId)?.cancel(); this._pendingRequests.deleteAndDispose(sessionId); - this._onDidDisposeSession.fire({ sessionId, providerId: model.providerId, reason: 'cleared' }); + this._onDidDisposeSession.fire({ sessionId, reason: 'cleared' }); } - registerProvider(provider: IChatProvider): IDisposable { - this.trace('registerProvider', `Adding new chat provider`); - - if (this._providers.has(provider.id)) { - throw new Error(`Provider ${provider.id} already registered`); - } - - this._providers.set(provider.id, provider); - this._hasProvider.set(true); - this._onDidRegisterProvider.fire({ providerId: provider.id }); - - Array.from(this._sessionModels.values()) - .filter(model => model.providerId === provider.id) - // The provider may have been registered in the process of initializing this model. Only grab models that were deinitialized when the provider was unregistered - .filter(model => model.initState === ChatModelInitState.Created) - .forEach(model => this.reinitializeModel(model)); - - return toDisposable(() => { - this.trace('registerProvider', `Disposing chat provider`); - this._providers.delete(provider.id); - this._hasProvider.set(this._providers.size > 0); - Array.from(this._sessionModels.values()) - .filter(model => model.providerId === provider.id) - .forEach(model => model.deinitialize()); - this._onDidUnregisterProvider.fire({ providerId: provider.id }); - }); - } - - public hasSessions(providerId: string): boolean { - return !!Object.values(this._persistedSessions).find((session) => session.providerId === providerId); - } - - getProviderInfos(): IChatProviderInfo[] { - return Array.from(this._providers.values()).map(provider => { - return { - id: provider.id, - }; - }); + public hasSessions(): boolean { + return !!Object.values(this._persistedSessions); } transferChatSession(transferredSessionData: IChatTransferredSessionData, toWorkspace: URI): void { diff --git a/src/vs/workbench/contrib/chat/common/chatViewModel.ts b/src/vs/workbench/contrib/chat/common/chatViewModel.ts index b03b90f16cf..f2b5f9a3774 100644 --- a/src/vs/workbench/contrib/chat/common/chatViewModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatViewModel.ts @@ -47,7 +47,6 @@ export interface IChatSessionInitEvent { export interface IChatViewModel { readonly model: IChatModel; readonly initState: ChatModelInitState; - readonly providerId: string; readonly sessionId: string; readonly onDidDisposeModel: Event; readonly onDidChange: Event; @@ -110,7 +109,6 @@ export interface IChatResponseViewModel { readonly sessionId: string; /** This ID updates every time the underlying data changes */ readonly dataId: string; - readonly providerId: string; /** The ID of the associated IChatRequestViewModel */ readonly requestId: string; readonly username: string; @@ -175,10 +173,6 @@ export class ChatViewModel extends Disposable implements IChatViewModel { return this._model.requestInProgress; } - get providerId() { - return this._model.providerId; - } - get initState() { return this._model.initState; } @@ -363,10 +357,6 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi return this._model.id + `_${this._modelChangeCount}` + `_${ChatModelInitState[this._model.session.initState]}`; } - get providerId() { - return this._model.providerId; - } - get sessionId() { return this._model.session.sessionId; } diff --git a/src/vs/workbench/contrib/chat/common/chatWidgetHistoryService.ts b/src/vs/workbench/contrib/chat/common/chatWidgetHistoryService.ts index 03948260a67..f9431569db7 100644 --- a/src/vs/workbench/contrib/chat/common/chatWidgetHistoryService.ts +++ b/src/vs/workbench/contrib/chat/common/chatWidgetHistoryService.ts @@ -7,6 +7,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { Memento } from 'vs/workbench/common/memento'; +import { CHAT_PROVIDER_ID } from 'vs/workbench/contrib/chat/common/chatParticipantContribTypes'; export interface IChatHistoryEntry { text: string; @@ -20,8 +21,8 @@ export interface IChatWidgetHistoryService { readonly onDidClearHistory: Event; clearHistory(): void; - getHistory(providerId: string): IChatHistoryEntry[]; - saveHistory(providerId: string, history: IChatHistoryEntry[]): void; + getHistory(): IChatHistoryEntry[]; + saveHistory(history: IChatHistoryEntry[]): void; } interface IChatHistory { @@ -50,15 +51,15 @@ export class ChatWidgetHistoryService implements IChatWidgetHistoryService { this.viewState = loadedState; } - getHistory(providerId: string): IChatHistoryEntry[] { - return this.viewState.history?.[providerId] ?? []; + getHistory(): IChatHistoryEntry[] { + return this.viewState.history?.[CHAT_PROVIDER_ID] ?? []; } - saveHistory(providerId: string, history: IChatHistoryEntry[]): void { + saveHistory(history: IChatHistoryEntry[]): void { if (!this.viewState.history) { this.viewState.history = {}; } - this.viewState.history[providerId] = history; + this.viewState.history[CHAT_PROVIDER_ID] = history; this.memento.saveMemento(); } diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index 0e2bd19e8d5..bd0ab0bdd89 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -3,57 +3,56 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./media/voiceChatActions'; -import { Event } from 'vs/base/common/event'; -import { firstOrDefault } from 'vs/base/common/arrays'; +import { RunOnceScheduler, disposableTimeout } from 'vs/base/common/async'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { Codicon } from 'vs/base/common/codicons'; +import { Color } from 'vs/base/common/color'; +import { Event } from 'vs/base/common/event'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { ThemeIcon } from 'vs/base/common/themables'; +import { assertIsDefined, isNumber } from 'vs/base/common/types'; +import 'vs/css!./media/voiceChatActions'; +import { getCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { localize, localize2 } from 'vs/nls'; import { Action2, IAction2Options, MenuId } from 'vs/platform/actions/common/actions'; +import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { ContextKeyExpr, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { spinningLoading } from 'vs/platform/theme/common/iconRegistry'; -import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; -import { IChatWidget, IChatWidgetService, IQuickChatService } from 'vs/workbench/contrib/chat/browser/chat'; -import { IChatService, KEYWORD_ACTIVIATION_SETTING_ID } from 'vs/workbench/contrib/chat/common/chatService'; -import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_INPUT, CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; -import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; -import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; -import { ActiveEditorContext } from 'vs/workbench/common/contextkeys'; -import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; -import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; -import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; -import { HasSpeechProvider, ISpeechService, KeywordRecognitionStatus, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; -import { RunOnceScheduler, disposableTimeout } from 'vs/base/common/async'; -import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { ACTIVITY_BAR_BADGE_BACKGROUND } from 'vs/workbench/common/theme'; -import { ColorScheme } from 'vs/platform/theme/common/theme'; -import { Color } from 'vs/base/common/color'; -import { contrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { assertIsDefined, isNumber } from 'vs/base/common/types'; -import { AccessibilityVoiceSettingId, SpeechTimeoutDefault, accessibilityConfigurationNodeBase } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; -import { IChatExecuteActionContext } from 'vs/workbench/contrib/chat/browser/actions/chatExecuteActions'; -import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; -import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/browser/statusbar'; -import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { getCodeEditor } from 'vs/editor/browser/editorBrowser'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; -import { IVoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChat'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { ThemeIcon } from 'vs/base/common/themables'; -import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ProgressLocation } from 'vs/platform/progress/common/progress'; -import { TerminalChatController, TerminalChatContextKeys } from 'vs/workbench/contrib/terminal/browser/terminalContribExports'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { contrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry'; +import { spinningLoading } from 'vs/platform/theme/common/iconRegistry'; +import { ColorScheme } from 'vs/platform/theme/common/theme'; +import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { ActiveEditorContext } from 'vs/workbench/common/contextkeys'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { ACTIVITY_BAR_BADGE_BACKGROUND } from 'vs/workbench/common/theme'; +import { AccessibilityVoiceSettingId, SpeechTimeoutDefault, accessibilityConfigurationNodeBase } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; +import { IChatExecuteActionContext } from 'vs/workbench/contrib/chat/browser/actions/chatExecuteActions'; +import { CHAT_VIEW_ID, IChatWidget, IChatWidgetService, IQuickChatService, showChatView } from 'vs/workbench/contrib/chat/browser/chat'; +import { ChatAgentLocation, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_INPUT, CONTEXT_HAS_DEFAULT_AGENT } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +import { IChatService, KEYWORD_ACTIVIATION_SETTING_ID } from 'vs/workbench/contrib/chat/common/chatService'; +import { IVoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChat'; +import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; +import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; +import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { NOTEBOOK_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; +import { HasSpeechProvider, ISpeechService, KeywordRecognitionStatus, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; +import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { TerminalChatContextKeys, TerminalChatController } from 'vs/workbench/contrib/terminal/browser/terminalContribExports'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; +import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/browser/statusbar'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; const CONTEXT_VOICE_CHAT_GETTING_READY = new RawContextKey('voiceChatGettingReady', false, { type: 'boolean', description: localize('voiceChatGettingReady', "True when getting ready for receiving voice input from the microphone for voice chat.") }); const CONTEXT_VOICE_CHAT_IN_PROGRESS = new RawContextKey('voiceChatInProgress', false, { type: 'boolean', description: localize('voiceChatInProgress', "True when voice recording from microphone is in progress for voice chat.") }); @@ -64,7 +63,7 @@ const CONTEXT_TERMINAL_VOICE_CHAT_IN_PROGRESS = new RawContextKey('term const CONTEXT_VOICE_CHAT_IN_VIEW_IN_PROGRESS = new RawContextKey('voiceChatInViewInProgress', false, { type: 'boolean', description: localize('voiceChatInViewInProgress', "True when voice recording from microphone is in progress in the chat view.") }); const CONTEXT_VOICE_CHAT_IN_EDITOR_IN_PROGRESS = new RawContextKey('voiceChatInEditorInProgress', false, { type: 'boolean', description: localize('voiceChatInEditorInProgress', "True when voice recording from microphone is in progress in the chat editor.") }); -const CanVoiceChat = ContextKeyExpr.and(CONTEXT_PROVIDER_EXISTS, HasSpeechProvider); +const CanVoiceChat = ContextKeyExpr.and(CONTEXT_HAS_DEFAULT_AGENT, HasSpeechProvider); const FocusInChatInput = assertIsDefined(ContextKeyExpr.or(CTX_INLINE_CHAT_FOCUSED, CONTEXT_IN_CHAT_INPUT)); type VoiceChatSessionContext = 'inline' | 'terminal' | 'quick' | 'view' | 'editor'; @@ -96,7 +95,6 @@ class VoiceChatSessionControllerFactory { static async create(accessor: ServicesAccessor, context: 'inline' | 'terminal' | 'quick' | 'view' | 'focused'): Promise { const chatWidgetService = accessor.get(IChatWidgetService); const viewsService = accessor.get(IViewsService); - const chatContributionService = accessor.get(IChatContributionService); const quickChatService = accessor.get(IQuickChatService); const layoutService = accessor.get(IWorkbenchLayoutService); const editorService = accessor.get(IEditorService); @@ -126,11 +124,11 @@ class VoiceChatSessionControllerFactory { layoutService.hasFocus(Parts.PANEL_PART) || layoutService.hasFocus(Parts.AUXILIARYBAR_PART) ) { - return VoiceChatSessionControllerFactory.doCreateForChatView(chatInput, viewsService, chatContributionService); + return VoiceChatSessionControllerFactory.doCreateForChatView(chatInput, viewsService); } if (layoutService.hasFocus(Parts.EDITOR_PART)) { - return VoiceChatSessionControllerFactory.doCreateForChatEditor(chatInput, viewsService, chatContributionService); + return VoiceChatSessionControllerFactory.doCreateForChatEditor(chatInput, viewsService); } return VoiceChatSessionControllerFactory.doCreateForQuickChat(chatInput, quickChatService); @@ -150,7 +148,7 @@ class VoiceChatSessionControllerFactory { if (context === 'view' || context === 'focused' /* fallback in case 'focused' was not successful */) { const chatView = await VoiceChatSessionControllerFactory.revealChatView(accessor); if (chatView) { - return VoiceChatSessionControllerFactory.doCreateForChatView(chatView, viewsService, chatContributionService); + return VoiceChatSessionControllerFactory.doCreateForChatView(chatView, viewsService); } } @@ -190,31 +188,29 @@ class VoiceChatSessionControllerFactory { } static async revealChatView(accessor: ServicesAccessor): Promise { - const chatWidgetService = accessor.get(IChatWidgetService); const chatService = accessor.get(IChatService); - - const provider = firstOrDefault(chatService.getProviderInfos()); - if (provider) { - return chatWidgetService.revealViewForProvider(provider.id); + const viewsService = accessor.get(IViewsService); + if (chatService.isEnabled(ChatAgentLocation.Panel)) { + return showChatView(viewsService); } return undefined; } - private static doCreateForChatView(chatView: IChatWidget, viewsService: IViewsService, chatContributionService: IChatContributionService): IVoiceChatSessionController { - return VoiceChatSessionControllerFactory.doCreateForChatViewOrEditor('view', chatView, viewsService, chatContributionService); + private static doCreateForChatView(chatView: IChatWidget, viewsService: IViewsService): IVoiceChatSessionController { + return VoiceChatSessionControllerFactory.doCreateForChatViewOrEditor('view', chatView, viewsService); } - private static doCreateForChatEditor(chatView: IChatWidget, viewsService: IViewsService, chatContributionService: IChatContributionService): IVoiceChatSessionController { - return VoiceChatSessionControllerFactory.doCreateForChatViewOrEditor('editor', chatView, viewsService, chatContributionService); + private static doCreateForChatEditor(chatView: IChatWidget, viewsService: IViewsService): IVoiceChatSessionController { + return VoiceChatSessionControllerFactory.doCreateForChatViewOrEditor('editor', chatView, viewsService); } - private static doCreateForChatViewOrEditor(context: 'view' | 'editor', chatView: IChatWidget, viewsService: IViewsService, chatContributionService: IChatContributionService): IVoiceChatSessionController { + private static doCreateForChatViewOrEditor(context: 'view' | 'editor', chatView: IChatWidget, viewsService: IViewsService): IVoiceChatSessionController { return { context, onDidAcceptInput: chatView.onDidAcceptInput, // TODO@bpasero cancellation needs to work better for chat editors that are not view bound - onDidCancelInput: Event.filter(viewsService.onDidChangeViewVisibility, e => e.id === chatContributionService.getViewIdForProvider(chatView.providerId)), + onDidCancelInput: Event.filter(viewsService.onDidChangeViewVisibility, e => e.id === CHAT_VIEW_ID), focusInput: () => chatView.focusInput(), acceptInput: () => chatView.acceptInput(), updateInput: text => chatView.setInput(text), @@ -882,7 +878,7 @@ export class KeywordActivationContribution extends Disposable implements IWorkbe @IInstantiationService instantiationService: IInstantiationService, @IEditorService private readonly editorService: IEditorService, @IHostService private readonly hostService: IHostService, - @IChatService private readonly chatService: IChatService + @IChatAgentService private readonly chatAgentService: IChatAgentService, ) { super(); @@ -897,7 +893,12 @@ export class KeywordActivationContribution extends Disposable implements IWorkbe this.handleKeywordActivation(); })); - this._register(this.chatService.onDidRegisterProvider(() => this.updateConfiguration())); + const onDidAddDefaultAgent = this._register(this.chatAgentService.onDidChangeAgents(() => { + if (this.chatAgentService.getDefaultAgent(ChatAgentLocation.Panel)) { + this.updateConfiguration(); + onDidAddDefaultAgent.dispose(); + } + })); this._register(this.speechService.onDidStartSpeechToTextSession(() => this.handleKeywordActivation())); this._register(this.speechService.onDidEndSpeechToTextSession(() => this.handleKeywordActivation())); @@ -910,7 +911,7 @@ export class KeywordActivationContribution extends Disposable implements IWorkbe } private updateConfiguration(): void { - if (!this.speechService.hasSpeechProvider || this.chatService.getProviderInfos().length === 0) { + if (!this.speechService.hasSpeechProvider || !this.chatAgentService.getDefaultAgent(ChatAgentLocation.Panel)) { return; // these settings require a speech and chat provider } diff --git a/src/vs/workbench/contrib/chat/test/browser/chatVariables.test.ts b/src/vs/workbench/contrib/chat/test/browser/chatVariables.test.ts index 6d6856156ee..5da221f7ad1 100644 --- a/src/vs/workbench/contrib/chat/test/browser/chatVariables.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/chatVariables.test.ts @@ -6,7 +6,9 @@ import * as assert from 'assert'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ChatVariablesService } from 'vs/workbench/contrib/chat/browser/chatVariables'; @@ -33,6 +35,7 @@ suite('ChatVariables', function () { instantiationService.stub(IExtensionService, new TestExtensionService()); instantiationService.stub(IChatVariablesService, service); instantiationService.stub(IChatService, new MockChatService()); + instantiationService.stub(IContextKeyService, new MockContextKeyService()); instantiationService.stub(IChatAgentService, instantiationService.createInstance(ChatAgentService)); }); diff --git a/src/vs/workbench/contrib/chat/test/browser/mockChatWidget.ts b/src/vs/workbench/contrib/chat/test/browser/mockChatWidget.ts index 7e23620a06e..472877f3e4b 100644 --- a/src/vs/workbench/contrib/chat/test/browser/mockChatWidget.ts +++ b/src/vs/workbench/contrib/chat/test/browser/mockChatWidget.ts @@ -14,13 +14,6 @@ export class MockChatWidgetService implements IChatWidgetService { */ readonly lastFocusedWidget: IChatWidget | undefined; - /** - * Returns whether a view was successfully revealed. - */ - async revealViewForProvider(providerId: string): Promise { - return undefined; - } - getWidgetByInputUri(uri: URI): IChatWidget | undefined { return undefined; } diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize.0.snap index b9b4b15aa1f..2339e724231 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize.0.snap @@ -91,6 +91,5 @@ }, contentReferences: [ ] } - ], - providerId: "testProvider" + ] } \ No newline at end of file diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_serialize.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_serialize.0.snap index 75c5fa71f40..643917dc4c2 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_serialize.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_serialize.0.snap @@ -4,6 +4,5 @@ responderUsername: "test", responderAvatarIconUri: undefined, welcomeMessage: undefined, - requests: [ ], - providerId: "testProvider" + requests: [ ] } \ No newline at end of file diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_serialize.1.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_serialize.1.snap index bad39b3eafc..7293313ad9f 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_serialize.1.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_serialize.1.snap @@ -97,6 +97,5 @@ }, contentReferences: [ ] } - ], - providerId: "testProvider" + ] } \ No newline at end of file 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 deleted file mode 100644 index cfed0a5d0ca..00000000000 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap +++ /dev/null @@ -1,92 +0,0 @@ -{ - requesterUsername: "test", - requesterAvatarIconUri: undefined, - responderUsername: "test", - responderAvatarIconUri: undefined, - welcomeMessage: undefined, - requests: [ - { - message: { - text: "@ChatProviderWithUsedContext test request", - parts: [ - { - kind: "agent", - range: { - start: 0, - endExclusive: 28 - }, - editorRange: { - startLineNumber: 1, - startColumn: 1, - endLineNumber: 1, - endColumn: 29 - }, - agent: { - id: "ChatProviderWithUsedContext", - metadata: { description: undefined } - } - }, - { - range: { - start: 28, - endExclusive: 41 - }, - editorRange: { - startLineNumber: 1, - startColumn: 29, - endLineNumber: 1, - endColumn: 42 - }, - text: " test request", - kind: "text" - } - ] - }, - variableData: { variables: [ ] }, - response: [ ], - result: { metadata: { metadataKey: "value" } }, - followups: undefined, - isCanceled: false, - vote: undefined, - agent: { - id: "ChatProviderWithUsedContext", - extensionId: { - value: "nullExtensionDescription", - _lower: "nullextensiondescription" - }, - metadata: { description: undefined }, - slashCommands: [ ], - locations: [ "panel" ], - isDefault: undefined - }, - slashCommand: undefined, - usedContext: { - documents: [ - { - uri: { - scheme: "file", - authority: "", - path: "/test/path/to/file", - query: "", - fragment: "", - _formatted: null, - _fsPath: null - }, - version: 3, - ranges: [ - { - startLineNumber: 1, - startColumn: 1, - endLineNumber: 2, - endColumn: 2 - } - ] - } - ], - kind: "usedContext" - }, - contentReferences: [ ] - } - ], - 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 deleted file mode 100644 index 75c5fa71f40..00000000000 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.0.snap +++ /dev/null @@ -1,9 +0,0 @@ -{ - requesterUsername: "test", - requesterAvatarIconUri: undefined, - responderUsername: "test", - responderAvatarIconUri: undefined, - welcomeMessage: undefined, - requests: [ ], - 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 deleted file mode 100644 index cb9c94b5d2d..00000000000 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap +++ /dev/null @@ -1,100 +0,0 @@ -{ - requesterUsername: "test", - requesterAvatarIconUri: undefined, - responderUsername: "test", - responderAvatarIconUri: undefined, - welcomeMessage: undefined, - requests: [ - { - message: { - parts: [ - { - kind: "agent", - range: { - start: 0, - endExclusive: 28 - }, - editorRange: { - startLineNumber: 1, - startColumn: 1, - endLineNumber: 1, - endColumn: 29 - }, - agent: { - id: "ChatProviderWithUsedContext", - metadata: { - description: undefined, - requester: { name: "test" }, - fullName: "test" - } - } - }, - { - range: { - start: 28, - endExclusive: 41 - }, - editorRange: { - startLineNumber: 1, - startColumn: 29, - endLineNumber: 1, - endColumn: 42 - }, - text: " test request", - kind: "text" - } - ], - text: "@ChatProviderWithUsedContext test request" - }, - variableData: { variables: [ ] }, - response: [ ], - result: { metadata: { metadataKey: "value" } }, - followups: undefined, - isCanceled: false, - vote: undefined, - agent: { - id: "ChatProviderWithUsedContext", - extensionId: { - value: "nullExtensionDescription", - _lower: "nullextensiondescription" - }, - metadata: { - description: undefined, - requester: { name: "test" }, - fullName: "test" - }, - slashCommands: [ ], - locations: [ "panel" ], - isDefault: undefined - }, - slashCommand: undefined, - usedContext: { - documents: [ - { - uri: { - scheme: "file", - authority: "", - path: "/test/path/to/file", - query: "", - fragment: "", - _formatted: null, - _fsPath: null - }, - version: 3, - ranges: [ - { - startLineNumber: 1, - startColumn: 1, - endLineNumber: 2, - endColumn: 2 - } - ] - } - ], - kind: "usedContext" - }, - contentReferences: [ ] - } - ], - 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 3d63e2ed9d3..d365d1f5100 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts @@ -11,7 +11,9 @@ import { assertSnapshot } from 'vs/base/test/common/snapshot'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { OffsetRange } from 'vs/editor/common/core/offsetRange'; import { Range } from 'vs/editor/common/core/range'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; 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'; @@ -30,11 +32,12 @@ suite('ChatModel', () => { instantiationService.stub(IStorageService, testDisposables.add(new TestStorageService())); instantiationService.stub(ILogService, new NullLogService()); instantiationService.stub(IExtensionService, new TestExtensionService()); + instantiationService.stub(IContextKeyService, new MockContextKeyService()); instantiationService.stub(IChatAgentService, instantiationService.createInstance(ChatAgentService)); }); test('Waits for initialization', async () => { - const model = testDisposables.add(instantiationService.createInstance(ChatModel, 'provider', undefined)); + const model = testDisposables.add(instantiationService.createInstance(ChatModel, undefined)); let hasInitialized = false; model.waitForInitialization().then(() => { @@ -45,13 +48,13 @@ suite('ChatModel', () => { assert.strictEqual(hasInitialized, false); model.startInitialize(); - model.initialize({} as any, undefined); + model.initialize(undefined); await timeout(0); assert.strictEqual(hasInitialized, true); }); test('must call startInitialize before initialize', async () => { - const model = testDisposables.add(instantiationService.createInstance(ChatModel, 'provider', undefined)); + const model = testDisposables.add(instantiationService.createInstance(ChatModel, undefined)); let hasInitialized = false; model.waitForInitialization().then(() => { @@ -61,12 +64,12 @@ suite('ChatModel', () => { await timeout(0); assert.strictEqual(hasInitialized, false); - assert.throws(() => model.initialize({} as any, undefined)); + assert.throws(() => model.initialize(undefined)); assert.strictEqual(hasInitialized, false); }); test('deinitialize/reinitialize', async () => { - const model = testDisposables.add(instantiationService.createInstance(ChatModel, 'provider', undefined)); + const model = testDisposables.add(instantiationService.createInstance(ChatModel, undefined)); let hasInitialized = false; model.waitForInitialization().then(() => { @@ -74,7 +77,7 @@ suite('ChatModel', () => { }); model.startInitialize(); - model.initialize({} as any, undefined); + model.initialize(undefined); await timeout(0); assert.strictEqual(hasInitialized, true); @@ -85,31 +88,31 @@ suite('ChatModel', () => { }); model.startInitialize(); - model.initialize({} as any, undefined); + model.initialize(undefined); await timeout(0); assert.strictEqual(hasInitialized2, true); }); test('cannot initialize twice', async () => { - const model = testDisposables.add(instantiationService.createInstance(ChatModel, 'provider', undefined)); + const model = testDisposables.add(instantiationService.createInstance(ChatModel, undefined)); model.startInitialize(); - model.initialize({} as any, undefined); - assert.throws(() => model.initialize({} as any, undefined)); + model.initialize(undefined); + assert.throws(() => model.initialize(undefined)); }); test('Initialization fails when model is disposed', async () => { - const model = testDisposables.add(instantiationService.createInstance(ChatModel, 'provider', undefined)); + const model = testDisposables.add(instantiationService.createInstance(ChatModel, undefined)); model.dispose(); - assert.throws(() => model.initialize({} as any, undefined)); + assert.throws(() => model.initialize(undefined)); }); test('removeRequest', async () => { - const model = testDisposables.add(instantiationService.createInstance(ChatModel, 'provider', undefined)); + const model = testDisposables.add(instantiationService.createInstance(ChatModel, undefined)); model.startInitialize(); - model.initialize({} as any, undefined); + model.initialize(undefined); const text = 'hello'; model.addRequest({ text, parts: [new ChatRequestTextPart(new OffsetRange(0, text.length), new Range(1, text.length, 1, text.length), text)] }, { variables: [] }); const requests = model.getRequests(); 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 c28817bb090..09f5ee0af0d 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts @@ -6,7 +6,9 @@ import { MockObject, mockObject } from 'vs/base/test/common/mock'; import { assertSnapshot } from 'vs/base/test/common/snapshot'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ChatAgentLocation, ChatAgentService, IChatAgentCommand, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; @@ -31,6 +33,7 @@ suite('ChatRequestParser', () => { instantiationService.stub(ILogService, new NullLogService()); instantiationService.stub(IExtensionService, new TestExtensionService()); instantiationService.stub(IChatService, new MockChatService()); + instantiationService.stub(IContextKeyService, new MockContextKeyService()); instantiationService.stub(IChatAgentService, instantiationService.createInstance(ChatAgentService)); varService = mockObject()({}); 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 3125cf09d88..ca674681991 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts @@ -5,12 +5,10 @@ import * as assert from 'assert'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { assertSnapshot } from 'vs/base/test/common/snapshot'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Range } from 'vs/editor/common/core/range'; -import { ProviderResult } from 'vs/editor/common/languages'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; @@ -21,9 +19,8 @@ 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 { ChatAgentLocation, ChatAgentService, IChatAgent, IChatAgentImplementation, 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, IChatFollowup, IChatProgress, IChatProvider, IChatRequest, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatFollowup, IChatService } 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'; @@ -33,26 +30,6 @@ import { IExtensionService, nullExtensionDescription } from 'vs/workbench/servic import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { TestContextService, TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; -class SimpleTestProvider extends Disposable implements IChatProvider { - private static sessionId = 0; - - readonly displayName = 'Test'; - - constructor(readonly id: string) { - super(); - } - - async prepareSession(): Promise { - return { - id: SimpleTestProvider.sessionId++, - }; - } - - async provideReply(request: IChatRequest, progress: (progress: IChatProgress) => void): Promise<{ session: IChat; followups: never[] }> { - return { session: request.session, followups: [] }; - } -} - const chatAgentWithUsedContextId = 'ChatProviderWithUsedContext'; const chatAgentWithUsedContext: IChatAgent = { id: chatAgentWithUsedContextId, @@ -100,7 +77,6 @@ suite('ChatService', () => { instantiationService.stub(IExtensionService, new TestExtensionService()); instantiationService.stub(IContextKeyService, new MockContextKeyService()); instantiationService.stub(IViewsService, new TestExtensionService()); - instantiationService.stub(IChatContributionService, new TestExtensionService()); instantiationService.stub(IWorkspaceContextService, new TestContextService()); instantiationService.stub(IChatSlashCommandService, testDisposables.add(instantiationService.createInstance(ChatSlashCommandService))); instantiationService.stub(IChatService, new MockChatService()); @@ -121,23 +97,16 @@ suite('ChatService', () => { test('retrieveSession', async () => { const testService = testDisposables.add(instantiationService.createInstance(ChatService)); - const provider1 = testDisposables.add(new SimpleTestProvider('provider1')); - const provider2 = testDisposables.add(new SimpleTestProvider('provider2')); - testDisposables.add(testService.registerProvider(provider1)); - testDisposables.add(testService.registerProvider(provider2)); - - const session1 = testDisposables.add(testService.startSession('provider1', CancellationToken.None)); + const session1 = testDisposables.add(testService.startSession(CancellationToken.None)); await session1.waitForInitialization(); session1.addRequest({ parts: [], text: 'request 1' }, { variables: [] }); - const session2 = testDisposables.add(testService.startSession('provider2', CancellationToken.None)); + const session2 = testDisposables.add(testService.startSession(CancellationToken.None)); await session2.waitForInitialization(); session2.addRequest({ parts: [], text: 'request 2' }, { variables: [] }); storageService.flush(); const testService2 = testDisposables.add(instantiationService.createInstance(ChatService)); - testDisposables.add(testService2.registerProvider(provider1)); - testDisposables.add(testService2.registerProvider(provider2)); const retrieved1 = testDisposables.add(testService2.getOrRestoreSession(session1.sessionId)!); await retrieved1.waitForInitialization(); const retrieved2 = testDisposables.add(testService2.getOrRestoreSession(session2.sessionId)!); @@ -146,57 +115,10 @@ suite('ChatService', () => { assert.deepStrictEqual(retrieved2.getRequests()[0]?.message.text, 'request 2'); }); - test('Handles failed session startup', async () => { - function getFailProvider(providerId: string) { - return new class implements IChatProvider { - readonly id = providerId; - readonly displayName = 'Test'; - - lastInitialState = undefined; - - prepareSession(initialState: any): ProviderResult { - throw new Error('Failed to start session'); - } - - async provideReply(request: IChatRequest) { - return { session: request.session, followups: [] }; - } - }; - } - - const testService = testDisposables.add(instantiationService.createInstance(ChatService)); - const provider1 = getFailProvider('provider1'); - testDisposables.add(testService.registerProvider(provider1)); - - const session1 = testDisposables.add(testService.startSession('provider1', CancellationToken.None)); - await assert.rejects(() => session1.waitForInitialization()); - }); - - test('Can\'t register same provider id twice', async () => { - const testService = testDisposables.add(instantiationService.createInstance(ChatService)); - const id = 'testProvider'; - testDisposables.add(testService.registerProvider({ - id, - prepareSession: function (token: CancellationToken): ProviderResult { - throw new Error('Function not implemented.'); - } - })); - - assert.throws(() => { - testDisposables.add(testService.registerProvider({ - id, - prepareSession: function (token: CancellationToken): ProviderResult { - throw new Error('Function not implemented.'); - } - })); - }, 'Expected to throw for dupe provider'); - }); - test('addCompleteRequest', async () => { const testService = testDisposables.add(instantiationService.createInstance(ChatService)); - testDisposables.add(testService.registerProvider(testDisposables.add(new SimpleTestProvider('testProvider')))); - const model = testDisposables.add(testService.startSession('testProvider', CancellationToken.None)); + const model = testDisposables.add(testService.startSession(CancellationToken.None)); assert.strictEqual(model.getRequests().length, 0); await testService.addCompleteRequest(model.sessionId, 'test request', undefined, { message: 'test response' }); @@ -209,9 +131,8 @@ suite('ChatService', () => { testDisposables.add(chatAgentService.registerAgentImplementation(chatAgentWithUsedContextId, chatAgentWithUsedContext)); chatAgentService.updateAgent(chatAgentWithUsedContextId, { requester: { name: 'test' }, fullName: 'test' }); const testService = testDisposables.add(instantiationService.createInstance(ChatService)); - testDisposables.add(testService.registerProvider(testDisposables.add(new SimpleTestProvider('testProvider')))); - const model = testDisposables.add(testService.startSession('testProvider', CancellationToken.None)); + const model = testDisposables.add(testService.startSession(CancellationToken.None)); assert.strictEqual(model.getRequests().length, 0); await assertSnapshot(model.toExport()); @@ -232,9 +153,8 @@ suite('ChatService', () => { // 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')))); - const chatModel1 = testDisposables.add(testService.startSession('testProvider', CancellationToken.None)); + const chatModel1 = testDisposables.add(testService.startSession(CancellationToken.None)); assert.strictEqual(chatModel1.getRequests().length, 0); const response = await testService.sendRequest(chatModel1.sessionId, `@${chatAgentWithUsedContextId} test request`); @@ -248,7 +168,6 @@ suite('ChatService', () => { // try deserializing the state into a new service const testService2 = testDisposables.add(instantiationService.createInstance(ChatService)); - testDisposables.add(testService2.registerProvider(testDisposables.add(new SimpleTestProvider('testProvider')))); const chatModel2 = testService2.loadSessionFromContent(serializedChatData); assert(chatModel2); diff --git a/src/vs/workbench/contrib/chat/test/common/mockChatContributionService.ts b/src/vs/workbench/contrib/chat/test/common/mockChatContributionService.ts deleted file mode 100644 index 27a687cb24d..00000000000 --- a/src/vs/workbench/contrib/chat/test/common/mockChatContributionService.ts +++ /dev/null @@ -1,31 +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 { IChatContributionService, IChatParticipantContribution, IChatProviderContribution } from 'vs/workbench/contrib/chat/common/chatContributionService'; - -export class MockChatContributionService implements IChatContributionService { - _serviceBrand: undefined; - - constructor( - ) { } - - registeredProviders: IChatProviderContribution[] = []; - registerChatParticipant(participant: IChatParticipantContribution): void { - throw new Error('Method not implemented.'); - } - deregisterChatParticipant(participant: IChatParticipantContribution): void { - throw new Error('Method not implemented.'); - } - - registerChatProvider(provider: IChatProviderContribution): void { - throw new Error('Method not implemented.'); - } - deregisterChatProvider(providerId: string): void { - throw new Error('Method not implemented.'); - } - getViewIdForProvider(providerId: string): string { - throw new Error('Method not implemented.'); - } -} diff --git a/src/vs/workbench/contrib/chat/test/common/mockChatService.ts b/src/vs/workbench/contrib/chat/test/common/mockChatService.ts index d961bbb74c5..5b1f572a23e 100644 --- a/src/vs/workbench/contrib/chat/test/common/mockChatService.ts +++ b/src/vs/workbench/contrib/chat/test/common/mockChatService.ts @@ -5,43 +5,37 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; -import { IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; +import { ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatModel, IChatModel, IChatRequestVariableData, ISerializableChatData } from 'vs/workbench/contrib/chat/common/chatModel'; import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { IChatCompleteResponse, IChatDetail, IChatProvider, IChatProviderInfo, IChatSendRequestData, IChatService, IChatTransferredSessionData, IChatUserActionEvent } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatCompleteResponse, IChatDetail, IChatProviderInfo, IChatSendRequestData, IChatService, IChatTransferredSessionData, IChatUserActionEvent } from 'vs/workbench/contrib/chat/common/chatService'; export class MockChatService implements IChatService { _serviceBrand: undefined; transferredSessionData: IChatTransferredSessionData | undefined; - hasSessions(providerId: string): boolean { + isEnabled(location: ChatAgentLocation): boolean { + throw new Error('Method not implemented.'); + } + hasSessions(): boolean { throw new Error('Method not implemented.'); } getProviderInfos(): IChatProviderInfo[] { throw new Error('Method not implemented.'); } - startSession(providerId: string, token: CancellationToken): ChatModel | undefined { + startSession(token: CancellationToken): ChatModel | undefined { throw new Error('Method not implemented.'); } getSession(sessionId: string): IChatModel | undefined { return {} as IChatModel; } - getSessionId(sessionProviderId: number): string | undefined { - throw new Error('Method not implemented.'); - } getOrRestoreSession(sessionId: string): IChatModel | undefined { throw new Error('Method not implemented.'); } loadSessionFromContent(data: ISerializableChatData): IChatModel | undefined { throw new Error('Method not implemented.'); } - onDidRegisterProvider: Event<{ providerId: string }> = undefined!; - onDidUnregisterProvider: Event<{ providerId: string }> = undefined!; - registerProvider(provider: IChatProvider): IDisposable { - throw new Error('Method not implemented.'); - } - /** * Returns whether the request was accepted. */ @@ -74,7 +68,7 @@ export class MockChatService implements IChatService { notifyUserAction(event: IChatUserActionEvent): void { throw new Error('Method not implemented.'); } - onDidDisposeSession: Event<{ sessionId: string; providerId: string; reason: 'initializationFailed' | 'cleared' }> = undefined!; + onDidDisposeSession: Event<{ sessionId: string; reason: 'initializationFailed' | 'cleared' }> = undefined!; transferChatSession(transferredSessionData: IChatTransferredSessionData, toWorkspace: URI): void { throw new Error('Method not implemented.'); diff --git a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts index 97e11f717ea..da6d6b2b02e 100644 --- a/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/voiceChat.test.ts @@ -57,6 +57,7 @@ suite('VoiceChat', () => { getActivatedAgents(): IChatAgent[] { return agents; } getAgents(): IChatAgent[] { return agents; } getDefaultAgent(): IChatAgent | undefined { throw new Error(); } + getContributedDefaultAgent(): IChatAgentData | undefined { throw new Error(); } getSecondaryAgent(): IChatAgent | undefined { throw new Error(); } registerAgent(id: string, data: IChatAgentData): IDisposable { throw new Error('Method not implemented.'); } getAgent(id: string): IChatAgentData | undefined { throw new Error('Method not implemented.'); } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatContentWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatContentWidget.ts index ac06027fce3..bd6defdb6a0 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatContentWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatContentWidget.ts @@ -50,7 +50,7 @@ export class InlineChatContentWidget implements IContentWidget { @IContextKeyService contextKeyService: IContextKeyService, ) { - this._defaultChatModel = this._store.add(instaService.createInstance(ChatModel, `inlineChatDefaultModel/editorContentWidgetPlaceholder`, undefined)); + this._defaultChatModel = this._store.add(instaService.createInstance(ChatModel, undefined)); const scopedInstaService = instaService.createChild( new ServiceCollection([ diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 8ad246733d4..7532a1e137b 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -29,7 +29,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; -import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; +import { IChatWidgetService, showChatView } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatAgentLocation, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { chatAgentLeader, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; @@ -51,6 +51,7 @@ import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeat import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart'; import { OffsetRange } from 'vs/editor/common/core/offsetRange'; import { isEqual } from 'vs/base/common/resources'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; export const enum State { CREATE_SESSION = 'CREATE_SESSION', @@ -1229,8 +1230,7 @@ async function showMessageResponse(accessor: ServicesAccessor, query: string, re return; } - const chatWidgetService = accessor.get(IChatWidgetService); - const widget = await chatWidgetService.revealViewForProvider(agent.name); + const widget = await showChatView(accessor.get(IViewsService)); if (widget && widget.viewModel) { chatService.addCompleteRequest(widget.viewModel.sessionId, query, undefined, { message: response }); widget.focusLastMessage(); @@ -1238,13 +1238,12 @@ async function showMessageResponse(accessor: ServicesAccessor, query: string, re } async function sendRequest(accessor: ServicesAccessor, query: string) { - const widgetService = accessor.get(IChatWidgetService); const chatAgentService = accessor.get(IChatAgentService); const agent = chatAgentService.getActivatedAgents().find(agent => agent.locations.includes(ChatAgentLocation.Panel) && agent.isDefault); if (!agent) { return; } - const widget = await widgetService.revealViewForProvider(agent.name); + const widget = await showChatView(accessor.get(IViewsService)); if (!widget) { return; } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts index 097dff843f2..b03cb399e32 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts @@ -2,41 +2,40 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { URI } from 'vs/base/common/uri'; -import { Emitter, Event } from 'vs/base/common/event'; -import { EditMode, IInlineChatSession, IInlineChatService, IInlineChatSessionProvider, InlineChatResponseFeedbackKind, IInlineChatProgressItem, IInlineChatResponse, IInlineChatRequest, InlineChatResponseType, IInlineChatBulkEditResponse } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { Range } from 'vs/editor/common/core/range'; -import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IModelService } from 'vs/editor/common/services/model'; -import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { DisposableMap, DisposableStore, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; -import { ILogService } from 'vs/platform/log/common/log'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { Iterable } from 'vs/base/common/iterator'; -import { raceCancellation } from 'vs/base/common/async'; -import { Recording, IInlineChatSessionService, ISessionKeyComputer, IInlineChatSessionEvent, IInlineChatSessionEndEvent } from './inlineChatSessionService'; -import { EmptyResponse, ErrorResponse, HunkData, ReplyResponse, Session, SessionExchange, SessionWholeRange, StashedSession, TelemetryData, TelemetryDataClassification } from './inlineChatSession'; -import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; -import { ITextModel, IValidEditOperation } from 'vs/editor/common/model'; -import { Schemas } from 'vs/base/common/network'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { generateUuid } from 'vs/base/common/uuid'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; -import { DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor'; -import { IChatFollowup, IChatProgress, IChatService, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; -import { ChatAgentLocation, IChatAgentCommand, IChatAgentData, IChatAgentHistoryEntry, IChatAgentImplementation, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { Progress } from 'vs/platform/progress/common/progress'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { coalesceInPlace, isNonEmptyArray } from 'vs/base/common/arrays'; -import { MarkdownString } from 'vs/base/common/htmlContent'; -import { TextEdit } from 'vs/editor/common/languages'; -import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; +import { raceCancellation } from 'vs/base/common/async'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { Codicon } from 'vs/base/common/codicons'; import { CancellationError } from 'vs/base/common/errors'; +import { Emitter, Event } from 'vs/base/common/event'; +import { MarkdownString } from 'vs/base/common/htmlContent'; +import { Iterable } from 'vs/base/common/iterator'; +import { DisposableStore, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { LRUCache } from 'vs/base/common/map'; +import { Schemas } from 'vs/base/common/network'; +import { URI } from 'vs/base/common/uri'; +import { generateUuid } from 'vs/base/common/uuid'; +import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { Range } from 'vs/editor/common/core/range'; +import { TextEdit } from 'vs/editor/common/languages'; +import { ITextModel, IValidEditOperation } from 'vs/editor/common/model'; +import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; +import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; +import { IModelService } from 'vs/editor/common/services/model'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ILogService } from 'vs/platform/log/common/log'; +import { Progress } from 'vs/platform/progress/common/progress'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor'; +import { ChatAgentLocation, IChatAgentCommand, IChatAgentData, IChatAgentHistoryEntry, IChatAgentImplementation, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatFollowup, IChatProgress, IChatService, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { EditMode, IInlineChatBulkEditResponse, IInlineChatProgressItem, IInlineChatRequest, IInlineChatResponse, IInlineChatService, IInlineChatSession, InlineChatResponseFeedbackKind, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; +import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; +import { EmptyResponse, ErrorResponse, HunkData, ReplyResponse, Session, SessionExchange, SessionWholeRange, StashedSession, TelemetryData, TelemetryDataClassification } from './inlineChatSession'; +import { IInlineChatSessionEndEvent, IInlineChatSessionEvent, IInlineChatSessionService, ISessionKeyComputer, Recording } from './inlineChatSessionService'; class BridgeAgent implements IChatAgentImplementation { @@ -299,34 +298,6 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { this._store.add(this._chatAgentService.onDidChangeAgents(() => addOrRemoveBridgeAgent())); const brigdeAgent = this._store.add(new MutableDisposable()); addOrRemoveBridgeAgent(); - - // MARK: register fake chat provider - - const mapping = this._store.add(new DisposableMap()); - const registerFakeChatProvider = (provider: IInlineChatSessionProvider) => { - const d = this._chatService.registerProvider({ - id: this._asChatProviderBrigdeName(provider), - prepareSession() { - return { - id: Math.random() - }; - } - }); - mapping.set(provider, d); - }; - - this._store.add(_inlineChatService.onDidChangeProviders(e => { - if (e.added) { - registerFakeChatProvider(e.added); - } - if (e.removed) { - mapping.deleteAndDispose(e.removed); - } - })); - - for (const provider of _inlineChatService.getAllProvider()) { - registerFakeChatProvider(provider); - } } dispose() { @@ -335,10 +306,6 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { this._sessions.clear(); } - private _asChatProviderBrigdeName(provider: IInlineChatSessionProvider) { - return `inlinechat:${provider.label}:${ExtensionIdentifier.toKey(provider.extensionId)}`; - } - async createSession(editor: IActiveCodeEditor, options: { editMode: EditMode; wholeRange?: Range }, token: CancellationToken): Promise { const provider = Iterable.first(this._inlineChatService.getAllProvider()); @@ -347,7 +314,7 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { return undefined; } - const chatModel = this._chatService.startSession(this._asChatProviderBrigdeName(provider), token); + const chatModel = this._chatService.startSession(token); if (!chatModel) { this._logService.trace('[IE] NO chatModel found'); return undefined; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index bfd7400f776..3f0d84f5e98 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -93,7 +93,6 @@ export interface IInlineChatWidgetConstructionOptions { export interface IInlineChatMessage { message: IMarkdownString; requestId: string; - providerId: string; } export interface IInlineChatMessageAppender { @@ -302,9 +301,9 @@ export class InlineChatWidget { // LEGACY - default chat model // this is only here for as long as we offer updateChatMessage - this._defaultChatModel = this._store.add(this._instantiationService.createInstance(ChatModel, `inlineChatDefaultModel/${location}`, undefined)); + this._defaultChatModel = this._store.add(this._instantiationService.createInstance(ChatModel, undefined)); this._defaultChatModel.startInitialize(); - this._defaultChatModel.initialize({ id: 1 }, undefined); + this._defaultChatModel.initialize(undefined); this.setChatModel(this._defaultChatModel); } diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index f5576bacb7f..b1cdcf53221 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -43,7 +43,6 @@ import { IInlineChatSavingService } from '../../browser/inlineChatSavingService' import { IInlineChatSessionService } from '../../browser/inlineChatSessionService'; import { InlineChatSessionServiceImpl } from '../../browser/inlineChatSessionServiceImpl'; import { TestWorkerService } from './testWorkerService'; -import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; import { IExtensionService, nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { ChatService } from 'vs/workbench/contrib/chat/common/chatServiceImpl'; @@ -127,7 +126,6 @@ suite('InteractiveChatController', function () { [IExtensionService, new TestExtensionService()], [IContextKeyService, new MockContextKeyService()], [IViewsService, new TestExtensionService()], - [IChatContributionService, new TestExtensionService()], [IWorkspaceContextService, new TestContextService()], [IChatWidgetHistoryService, new SyncDescriptor(ChatWidgetHistoryService)], [IChatWidgetService, new SyncDescriptor(ChatWidgetService)], diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts index d74482948f1..e2dc5ae92db 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts @@ -46,7 +46,6 @@ 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 { ChatWidgetService } from 'vs/workbench/contrib/chat/browser/chatWidget'; -import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { ChatService } from 'vs/workbench/contrib/chat/common/chatServiceImpl'; import { IChatSlashCommandService, ChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; @@ -56,7 +55,6 @@ import { MockChatVariablesService } from 'vs/workbench/contrib/chat/test/common/ import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { TestExtensionService, TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; import { IChatAgentService, ChatAgentService, ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { MockChatContributionService } from 'vs/workbench/contrib/chat/test/common/mockChatContributionService'; suite('InlineChatSession', function () { @@ -80,14 +78,12 @@ suite('InlineChatSession', function () { [IExtensionService, new TestExtensionService()], [IContextKeyService, new MockContextKeyService()], [IViewsService, new TestExtensionService()], - [IChatContributionService, new TestExtensionService()], [IWorkspaceContextService, new TestContextService()], [IChatWidgetHistoryService, new SyncDescriptor(ChatWidgetHistoryService)], [IChatWidgetService, new SyncDescriptor(ChatWidgetService)], [IChatSlashCommandService, new SyncDescriptor(ChatSlashCommandService)], [IChatService, new SyncDescriptor(ChatService)], [IEditorWorkerService, new SyncDescriptor(TestWorkerService)], - [IChatContributionService, new MockChatContributionService()], [IChatAgentService, new SyncDescriptor(ChatAgentService)], [IInlineChatService, new SyncDescriptor(InlineChatServiceImpl)], [IContextKeyService, contextKeyService], diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts index 9add14eccb0..dd3bca068fe 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController.ts @@ -614,7 +614,6 @@ export class NotebookChatController extends Disposable implements INotebookEdito if (!progressiveChatResponse) { const message = { message: new MarkdownString(data.markdownFragment, { supportThemeIcons: true, supportHtml: true, isTrusted: false }), - providerId: this._activeSession!.provider.label, requestId: request.requestId, }; progressiveChatResponse = this._widget?.inlineChatWidget.updateChatMessage(message, true); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index d6cdd0c866a..6adb846d414 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -12,7 +12,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; -import { GeneratingPhrase, IChatAccessibilityService, IChatCodeBlockContextProviderService, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; +import { GeneratingPhrase, IChatAccessibilityService, IChatCodeBlockContextProviderService, showChatView } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatAgentLocation, IChatAgentRequest, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatUserAction, IChatProgress, IChatService, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; @@ -24,6 +24,7 @@ import { TerminalChatWidget } from 'vs/workbench/contrib/terminalContrib/chat/br import { MarkdownString } from 'vs/base/common/htmlContent'; import { ChatModel, ChatRequestModel, IChatRequestVariableData, getHistoryEntriesFromModel } from 'vs/workbench/contrib/chat/common/chatModel'; import { TerminalChatContextKeys } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; const enum Message { NONE = 0, @@ -95,9 +96,9 @@ export class TerminalChatController extends Disposable implements ITerminalContr @IChatAgentService private readonly _chatAgentService: IChatAgentService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService, - @IChatWidgetService private readonly _chatWidgetService: IChatWidgetService, @IChatService private readonly _chatService: IChatService, @IChatCodeBlockContextProviderService private readonly _chatCodeBlockContextProviderService: IChatCodeBlockContextProviderService, + @IViewsService private readonly _viewsService: IViewsService, ) { super(); @@ -134,12 +135,10 @@ export class TerminalChatController extends Disposable implements ITerminalContr // a default chat model (unless configured) and feedback is reported against that one. This // code forwards the feedback to an actual registered provider this._register(this._chatService.onDidPerformUserAction(e => { - if (e.providerId === this._chatWidget?.rawValue?.inlineChatWidget.getChatModel().providerId) { - if (e.action.kind === 'bug') { - this.acceptFeedback(undefined); - } else if (e.action.kind === 'vote') { - this.acceptFeedback(e.action.direction === InteractiveSessionVoteDirection.Up); - } + if (e.action.kind === 'bug') { + this.acceptFeedback(undefined); + } else if (e.action.kind === 'vote') { + this.acceptFeedback(e.action.direction === InteractiveSessionVoteDirection.Up); } })); } @@ -179,9 +178,8 @@ export class TerminalChatController extends Disposable implements ITerminalContr } acceptFeedback(helpful?: boolean): void { - const providerId = this._chatService.getProviderInfos()?.[0]?.id; const model = this._model.value; - if (!providerId || !this._currentRequest || !model) { + if (!this._currentRequest || !model) { return; } let action: ChatUserAction; @@ -195,7 +193,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr for (const request of model.getRequests()) { if (request.response?.response.value || request.response?.result) { this._chatService.notifyUserAction({ - providerId, sessionId: request.session.sessionId, requestId: request.id, agentId: request.response?.agent?.id, @@ -253,12 +250,8 @@ export class TerminalChatController extends Disposable implements ITerminalContr } async acceptInput(): Promise { - const providerInfo = this._chatService.getProviderInfos()?.[0]; - if (!providerInfo) { - return; - } if (!this._model.value) { - this._model.value = this._chatService.startSession(providerInfo.id, CancellationToken.None); + this._model.value = this._chatService.startSession(CancellationToken.None); if (!this._model.value) { throw new Error('Could not start chat session'); } @@ -328,7 +321,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr if (this._currentRequest) { this._chatAccessibilityService.acceptResponse(responseContent, accessibilityRequestId); const containsCode = responseContent.includes('```'); - this._chatWidget?.value.inlineChatWidget.updateChatMessage({ message: new MarkdownString(responseContent), requestId: this._currentRequest.id, providerId: 'terminal' }, false, containsCode); + this._chatWidget?.value.inlineChatWidget.updateChatMessage({ message: new MarkdownString(responseContent), requestId: this._currentRequest.id }, false, containsCode); const firstCodeBlock = await this.chatWidget?.inlineChatWidget.getCodeBlockInfo(0); const secondCodeBlock = await this.chatWidget?.inlineChatWidget.getCodeBlockInfo(1); this._responseContainsCodeBlockContextKey.set(!!firstCodeBlock); @@ -377,11 +370,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr } async viewInChat(): Promise { - const providerInfo = this._chatService.getProviderInfos()?.[0]; - if (!providerInfo) { - return; - } - const widget = await this._chatWidgetService.revealViewForProvider(providerInfo.id); + const widget = await showChatView(this._viewsService); const request = this._currentRequest; if (!widget || !request?.response) { return; diff --git a/src/vscode-dts/vscode.proposed.chatTab.d.ts b/src/vscode-dts/vscode.proposed.chatTab.d.ts index 7eaf4c63f52..5721dced191 100644 --- a/src/vscode-dts/vscode.proposed.chatTab.d.ts +++ b/src/vscode-dts/vscode.proposed.chatTab.d.ts @@ -8,11 +8,7 @@ declare module 'vscode' { * The tab represents an interactive window. */ export class TabInputChat { - /** - * The uri of the history notebook in the interactive window. - */ - readonly providerId: string; - constructor(providerId: string); + constructor(); } export interface Tab { diff --git a/src/vscode-dts/vscode.proposed.interactive.d.ts b/src/vscode-dts/vscode.proposed.interactive.d.ts index 26fb4c2f62f..0a03e7687ca 100644 --- a/src/vscode-dts/vscode.proposed.interactive.d.ts +++ b/src/vscode-dts/vscode.proposed.interactive.d.ts @@ -128,6 +128,9 @@ declare module 'vscode' { // current version of the proposal. export const _version: 1 | number; + /** + * @deprecated + */ export function registerInteractiveSessionProvider(id: string, provider: InteractiveSessionProvider): Disposable; export function registerInteractiveEditorSessionProvider(provider: InteractiveEditorSessionProvider, metadata?: InteractiveEditorSessionProviderMetadata): Disposable;