Kill "interactive session provider" DEAD (#209700)

* Reduce chat provider usage

* Clear out IChat props

* Delete interactive session provider API

* Totally remove static chat provider registration

* Clean up onDidRegisterProvider events

* Remove provider ID references from model

* Eliminate many 'providerId' references

* Simplify view registration

* Clean up participant-specific actions

* Delete getProviderInfos

* Rename context key

* Fix tests

* Fix action label

* Fix view title

* Fix test

* Fix test

* Fix integration test

* Remove more providerId

* Add API stub to make this compatible with the existing Copilot Chat
This commit is contained in:
Rob Lourens 2024-04-08 01:06:43 -03:00 committed by GitHub
parent 8af2f4e3bf
commit ed6c6d3f28
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
64 changed files with 626 additions and 1530 deletions

View file

@ -5,7 +5,7 @@
import * as assert from 'assert'; import * as assert from 'assert';
import 'mocha'; 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'; import { DeferredPromise, assertNoRpc, closeAllEditors, disposeAll } from '../utils';
suite('chat', () => { suite('chat', () => {
@ -31,14 +31,6 @@ suite('chat', () => {
function setupParticipant(): Event<{ request: ChatRequest; context: ChatContext }> { function setupParticipant(): Event<{ request: ChatRequest; context: ChatContext }> {
const emitter = new EventEmitter<{ request: ChatRequest; context: ChatContext }>(); const emitter = new EventEmitter<{ request: ChatRequest; context: ChatContext }>();
disposables.push(emitter); disposables.push(emitter);
disposables.push(interactive.registerInteractiveSessionProvider('provider', {
prepareSession: (_token: CancellationToken): ProviderResult<InteractiveSession> => {
return {
requester: { name: 'test' },
responder: { name: 'test' },
};
},
}));
const participant = chat.createChatParticipant('api-test.participant', (request, context, _progress, _token) => { const participant = chat.createChatParticipant('api-test.participant', (request, context, _progress, _token) => {
emitter.fire({ request, context }); emitter.fire({ request, context });
@ -52,19 +44,29 @@ suite('chat', () => {
const onRequest = setupParticipant(); const onRequest = setupParticipant();
commands.executeCommand('workbench.action.chat.open', { query: '@participant /hello friend' }); commands.executeCommand('workbench.action.chat.open', { query: '@participant /hello friend' });
const deferred = new DeferredPromise<void>();
let i = 0; let i = 0;
disposables.push(onRequest(request => { disposables.push(onRequest(request => {
try {
if (i === 0) { if (i === 0) {
assert.deepStrictEqual(request.request.command, 'hello'); assert.deepStrictEqual(request.request.command, 'hello');
assert.strictEqual(request.request.prompt, 'friend'); assert.strictEqual(request.request.prompt, 'friend');
i++; i++;
setTimeout(() => {
commands.executeCommand('workbench.action.chat.open', { query: '@participant /hello friend' }); commands.executeCommand('workbench.action.chat.open', { query: '@participant /hello friend' });
}, 0);
} else { } else {
assert.strictEqual(request.context.history.length, 1); assert.strictEqual(request.context.history.length, 2);
assert.strictEqual(request.context.history[0].participant, 'api-test.participant'); assert.strictEqual(request.context.history[0].participant, 'api-test.participant');
assert.strictEqual(request.context.history[0].command, 'hello'); assert.strictEqual(request.context.history[0].command, 'hello');
deferred.complete();
}
} catch (e) {
deferred.error(e);
} }
})); }));
await deferred.p;
}); });
test('participant and variable', async () => { test('participant and variable', async () => {
@ -82,15 +84,6 @@ suite('chat', () => {
}); });
test('result metadata is returned to the followup provider', async () => { test('result metadata is returned to the followup provider', async () => {
disposables.push(interactive.registerInteractiveSessionProvider('provider', {
prepareSession: (_token: CancellationToken): ProviderResult<InteractiveSession> => {
return {
requester: { name: 'test' },
responder: { name: 'test' },
};
},
}));
const deferred = new DeferredPromise<ChatResult>(); const deferred = new DeferredPromise<ChatResult>();
const participant = chat.createChatParticipant('api-test.participant', (_request, _context, _progress, _token) => { const participant = chat.createChatParticipant('api-test.participant', (_request, _context, _progress, _token) => {
return { metadata: { key: 'value' } }; return { metadata: { key: 'value' } };

View file

@ -76,7 +76,6 @@ import './mainThreadNotebookRenderers';
import './mainThreadNotebookSaveParticipant'; import './mainThreadNotebookSaveParticipant';
import './mainThreadInteractive'; import './mainThreadInteractive';
import './mainThreadInlineChat'; import './mainThreadInlineChat';
import './mainThreadChat';
import './mainThreadTask'; import './mainThreadTask';
import './mainThreadLabelService'; import './mainThreadLabelService';
import './mainThreadTunnelService'; import './mainThreadTunnelService';

View file

@ -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<number>());
private readonly _stateEmitters = new Map<number, Emitter<any>>();
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<void> {
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<any>();
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<void> {
this._stateEmitters.get(sessionId)?.fire(state);
}
async $unregisterChatProvider(handle: number): Promise<void> {
this._providerRegistrations.deleteAndDispose(handle);
}
}

View file

@ -7,6 +7,7 @@ import { CancellationToken } from 'vs/base/common/cancellation';
import { Disposable, DisposableMap, IDisposable } from 'vs/base/common/lifecycle'; import { Disposable, DisposableMap, IDisposable } from 'vs/base/common/lifecycle';
import { revive } from 'vs/base/common/marshalling'; import { revive } from 'vs/base/common/marshalling';
import { escapeRegExpCharacters } from 'vs/base/common/strings'; import { escapeRegExpCharacters } from 'vs/base/common/strings';
import { URI, UriComponents } from 'vs/base/common/uri';
import { Position } from 'vs/editor/common/core/position'; import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range'; import { Range } from 'vs/editor/common/core/range';
import { getWordAtText } from 'vs/editor/common/core/wordHelper'; 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 { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
import { ExtHostChatAgentsShape2, ExtHostContext, IChatProgressDto, IExtensionChatAgentMetadata, MainContext, MainThreadChatAgentsShape2 } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostChatAgentsShape2, ExtHostContext, IChatProgressDto, IExtensionChatAgentMetadata, MainContext, MainThreadChatAgentsShape2 } from 'vs/workbench/api/common/extHost.protocol';
import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat';
import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart'; 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, @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService,
@IChatWidgetService private readonly _chatWidgetService: IChatWidgetService, @IChatWidgetService private readonly _chatWidgetService: IChatWidgetService,
@IInstantiationService private readonly _instantiationService: IInstantiationService, @IInstantiationService private readonly _instantiationService: IInstantiationService,
@ILogService private readonly _logService: ILogService,
) { ) {
super(); super();
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatAgents2); this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatAgents2);
@ -75,6 +78,18 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA
this._agents.deleteAndDispose(handle); 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 { $registerAgent(handle: number, extension: ExtensionIdentifier, id: string, metadata: IExtensionChatAgentMetadata, dynamicProps: { name: string; description: string } | undefined): void {
const staticAgentRegistration = this._chatAgentService.getAgent(id); const staticAgentRegistration = this._chatAgentService.getAgent(id);
if (!staticAgentRegistration && !dynamicProps) { if (!staticAgentRegistration && !dynamicProps) {

View file

@ -196,7 +196,6 @@ export class MainThreadEditorTabs implements MainThreadEditorTabsShape {
if (editor instanceof ChatEditorInput) { if (editor instanceof ChatEditorInput) {
return { return {
kind: TabInputKind.ChatEditorInput, kind: TabInputKind.ChatEditorInput,
providerId: editor.providerId ?? 'unknown',
}; };
} }

View file

@ -6,7 +6,7 @@
import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { CancellationTokenSource } from 'vs/base/common/cancellation';
import * as errors from 'vs/base/common/errors'; import * as errors from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event'; 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 { Schemas, matchesScheme } from 'vs/base/common/network';
import Severity from 'vs/base/common/severity'; import Severity from 'vs/base/common/severity';
import { URI } from 'vs/base/common/uri'; 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 { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService';
import { IExtHostAuthentication } from 'vs/workbench/api/common/extHostAuthentication'; import { IExtHostAuthentication } from 'vs/workbench/api/common/extHostAuthentication';
import { ExtHostBulkEdits } from 'vs/workbench/api/common/extHostBulkEdits'; 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 { ExtHostChatAgents2 } from 'vs/workbench/api/common/extHostChatAgents2';
import { IExtHostLanguageModels } from 'vs/workbench/api/common/extHostLanguageModels';
import { ExtHostChatVariables } from 'vs/workbench/api/common/extHostChatVariables'; import { ExtHostChatVariables } from 'vs/workbench/api/common/extHostChatVariables';
import { ExtHostClipboard } from 'vs/workbench/api/common/extHostClipboard'; import { ExtHostClipboard } from 'vs/workbench/api/common/extHostClipboard';
import { ExtHostEditorInsets } from 'vs/workbench/api/common/extHostCodeInsets'; 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 { ExtHostInteractive } from 'vs/workbench/api/common/extHostInteractive';
import { ExtHostLabelService } from 'vs/workbench/api/common/extHostLabelService'; import { ExtHostLabelService } from 'vs/workbench/api/common/extHostLabelService';
import { ExtHostLanguageFeatures } from 'vs/workbench/api/common/extHostLanguageFeatures'; 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 { ExtHostLanguages } from 'vs/workbench/api/common/extHostLanguages';
import { IExtHostLocalizationService } from 'vs/workbench/api/common/extHostLocalizationService'; import { IExtHostLocalizationService } from 'vs/workbench/api/common/extHostLocalizationService';
import { IExtHostManagedSockets } from 'vs/workbench/api/common/extHostManagedSockets'; 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 { IExtHostTask } from 'vs/workbench/api/common/extHostTask';
import { ExtHostTelemetryLogger, IExtHostTelemetry, isNewAppInstall } from 'vs/workbench/api/common/extHostTelemetry'; import { ExtHostTelemetryLogger, IExtHostTelemetry, isNewAppInstall } from 'vs/workbench/api/common/extHostTelemetry';
import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService'; 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 { ExtHostTesting } from 'vs/workbench/api/common/extHostTesting';
import { ExtHostEditors } from 'vs/workbench/api/common/extHostTextEditors'; import { ExtHostEditors } from 'vs/workbench/api/common/extHostTextEditors';
import { ExtHostTheming } from 'vs/workbench/api/common/extHostTheming'; 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 { ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyIdentifier';
import { TextSearchCompleteMessageType } from 'vs/workbench/services/search/common/searchExtTypes'; import { TextSearchCompleteMessageType } from 'vs/workbench/services/search/common/searchExtTypes';
import type * as vscode from 'vscode'; import type * as vscode from 'vscode';
import { IExtHostTerminalShellIntegration } from 'vs/workbench/api/common/extHostTerminalShellIntegration';
export interface IExtensionRegistries { export interface IExtensionRegistries {
mine: ExtensionDescriptionRegistry; 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 extHostInteractiveEditor = rpcProtocol.set(ExtHostContext.ExtHostInlineChat, new ExtHostInteractiveEditor(rpcProtocol, extHostCommands, extHostDocuments, extHostLogService));
const extHostChatAgents2 = rpcProtocol.set(ExtHostContext.ExtHostChatAgents2, new ExtHostChatAgents2(rpcProtocol, extHostLogService, extHostCommands)); const extHostChatAgents2 = rpcProtocol.set(ExtHostContext.ExtHostChatAgents2, new ExtHostChatAgents2(rpcProtocol, extHostLogService, extHostCommands));
const extHostChatVariables = rpcProtocol.set(ExtHostContext.ExtHostChatVariables, new ExtHostChatVariables(rpcProtocol)); 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 extHostAiRelatedInformation = rpcProtocol.set(ExtHostContext.ExtHostAiRelatedInformation, new ExtHostRelatedInformation(rpcProtocol));
const extHostAiEmbeddingVector = rpcProtocol.set(ExtHostContext.ExtHostAiEmbeddingVector, new ExtHostAiEmbeddingVector(rpcProtocol)); const extHostAiEmbeddingVector = rpcProtocol.set(ExtHostContext.ExtHostAiEmbeddingVector, new ExtHostAiEmbeddingVector(rpcProtocol));
const extHostStatusBar = rpcProtocol.set(ExtHostContext.ExtHostStatusBar, new ExtHostStatusBar(rpcProtocol, extHostCommands.converter)); 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); return extHostInteractiveEditor.registerProvider(extension, provider, metadata);
}, },
registerInteractiveSessionProvider(id: string, provider: vscode.InteractiveSessionProvider) { registerInteractiveSessionProvider(id: string, provider: vscode.InteractiveSessionProvider) {
// temp stub
checkProposedApiEnabled(extension, 'interactive'); checkProposedApiEnabled(extension, 'interactive');
return extHostChat.registerChatProvider(extension, id, provider); return Disposable.None;
}, },
transferActiveChat(toWorkspace: vscode.Uri) { transferActiveChat(toWorkspace: vscode.Uri) {
checkProposedApiEnabled(extension, 'interactive'); checkProposedApiEnabled(extension, 'interactive');
return extHostChat.transferActiveChat(toWorkspace); return extHostChatAgents2.transferActiveChat(toWorkspace);
} }
}; };

View file

@ -769,7 +769,6 @@ export interface InteractiveEditorInputDto {
export interface ChatEditorInputDto { export interface ChatEditorInputDto {
kind: TabInputKind.ChatEditorInput; kind: TabInputKind.ChatEditorInput;
providerId: string;
} }
export interface MultiDiffEditorInputDto { export interface MultiDiffEditorInputDto {
@ -1219,6 +1218,8 @@ export interface MainThreadChatAgentsShape2 extends IDisposable {
$updateAgent(handle: number, metadataUpdate: IExtensionChatAgentMetadata): void; $updateAgent(handle: number, metadataUpdate: IExtensionChatAgentMetadata): void;
$unregisterAgent(handle: number): void; $unregisterAgent(handle: number): void;
$handleProgressChunk(requestId: string, chunk: IChatProgressDto): Promise<number | void>; $handleProgressChunk(requestId: string, chunk: IChatProgressDto): Promise<number | void>;
$transferActiveChatSession(toWorkspace: UriComponents): void;
} }
export interface IChatAgentCompletionItem { export interface IChatAgentCompletionItem {
@ -1287,7 +1288,6 @@ export interface MainThreadUrlsShape extends IDisposable {
} }
export interface IChatDto { export interface IChatDto {
id: number;
} }
export interface IChatRequestDto { export interface IChatRequestDto {
@ -1318,18 +1318,6 @@ export type IDocumentContextDto = {
export type IChatProgressDto = export type IChatProgressDto =
| Dto<IChatProgress>; | Dto<IChatProgress>;
export interface MainThreadChatShape extends IDisposable {
$registerChatProvider(handle: number, id: string): Promise<void>;
$acceptChatState(sessionId: number, state: any): Promise<void>;
$unregisterChatProvider(handle: number): Promise<void>;
$transferActiveChatSession(toWorkspace: UriComponents): void;
}
export interface ExtHostChatShape {
$prepareChat(handle: number, token: CancellationToken): Promise<IChatDto | undefined>;
$releaseSession(sessionId: number): void;
}
export interface ExtHostUrlsShape { export interface ExtHostUrlsShape {
$handleExternalUri(handle: number, uri: UriComponents): Promise<void>; $handleExternalUri(handle: number, uri: UriComponents): Promise<void>;
} }
@ -2839,7 +2827,6 @@ export const MainContext = {
MainThreadNotebookKernels: createProxyIdentifier<MainThreadNotebookKernelsShape>('MainThreadNotebookKernels'), MainThreadNotebookKernels: createProxyIdentifier<MainThreadNotebookKernelsShape>('MainThreadNotebookKernels'),
MainThreadNotebookRenderers: createProxyIdentifier<MainThreadNotebookRenderersShape>('MainThreadNotebookRenderers'), MainThreadNotebookRenderers: createProxyIdentifier<MainThreadNotebookRenderersShape>('MainThreadNotebookRenderers'),
MainThreadInteractive: createProxyIdentifier<MainThreadInteractiveShape>('MainThreadInteractive'), MainThreadInteractive: createProxyIdentifier<MainThreadInteractiveShape>('MainThreadInteractive'),
MainThreadChat: createProxyIdentifier<MainThreadChatShape>('MainThreadChat'),
MainThreadInlineChat: createProxyIdentifier<MainThreadInlineChatShape>('MainThreadInlineChatShape'), MainThreadInlineChat: createProxyIdentifier<MainThreadInlineChatShape>('MainThreadInlineChatShape'),
MainThreadTheming: createProxyIdentifier<MainThreadThemingShape>('MainThreadTheming'), MainThreadTheming: createProxyIdentifier<MainThreadThemingShape>('MainThreadTheming'),
MainThreadTunnelService: createProxyIdentifier<MainThreadTunnelServiceShape>('MainThreadTunnelService'), MainThreadTunnelService: createProxyIdentifier<MainThreadTunnelServiceShape>('MainThreadTunnelService'),
@ -2904,7 +2891,6 @@ export const ExtHostContext = {
ExtHostNotebookDocumentSaveParticipant: createProxyIdentifier<ExtHostNotebookDocumentSaveParticipantShape>('ExtHostNotebookDocumentSaveParticipant'), ExtHostNotebookDocumentSaveParticipant: createProxyIdentifier<ExtHostNotebookDocumentSaveParticipantShape>('ExtHostNotebookDocumentSaveParticipant'),
ExtHostInteractive: createProxyIdentifier<ExtHostInteractiveShape>('ExtHostInteractive'), ExtHostInteractive: createProxyIdentifier<ExtHostInteractiveShape>('ExtHostInteractive'),
ExtHostInlineChat: createProxyIdentifier<ExtHostInlineChatShape>('ExtHostInlineChatShape'), ExtHostInlineChat: createProxyIdentifier<ExtHostInlineChatShape>('ExtHostInlineChatShape'),
ExtHostChat: createProxyIdentifier<ExtHostChatShape>('ExtHostChat'),
ExtHostChatAgents2: createProxyIdentifier<ExtHostChatAgentsShape2>('ExtHostChatAgents'), ExtHostChatAgents2: createProxyIdentifier<ExtHostChatAgentsShape2>('ExtHostChatAgents'),
ExtHostChatVariables: createProxyIdentifier<ExtHostChatVariablesShape>('ExtHostChatVariables'), ExtHostChatVariables: createProxyIdentifier<ExtHostChatVariablesShape>('ExtHostChatVariables'),
ExtHostChatProvider: createProxyIdentifier<ExtHostLanguageModelsShape>('ExtHostChatProvider'), ExtHostChatProvider: createProxyIdentifier<ExtHostLanguageModelsShape>('ExtHostChatProvider'),

View file

@ -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<T> {
private static _pool = 0;
readonly handle: number = ChatProviderWrapper._pool++;
constructor(
readonly extension: Readonly<IRelaxedExtensionDescription>,
readonly provider: T,
) { }
}
export class ExtHostChat implements ExtHostChatShape {
private static _nextId = 0;
private readonly _chatProvider = new Map<number, ChatProviderWrapper<vscode.InteractiveSessionProvider>>();
private readonly _chatSessions = new Map<number, vscode.InteractiveSession>();
private readonly _proxy: MainThreadChatShape;
constructor(
mainContext: IMainContext,
) {
this._proxy = mainContext.getProxy(MainContext.MainThreadChat);
}
//#region interactive session
registerChatProvider(extension: Readonly<IRelaxedExtensionDescription>, 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<IChatDto | undefined> {
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
}

View file

@ -213,6 +213,10 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 {
this._proxy = mainContext.getProxy(MainContext.MainThreadChatAgents2); 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 { createChatAgent(extension: IExtensionDescription, id: string, handler: vscode.ChatExtendedRequestHandler): vscode.ChatParticipant {
const handle = ExtHostChatAgents2._idPool++; const handle = ExtHostChatAgents2._idPool++;
const agent = new ExtHostChatAgent(extension, id, this._proxy, handle, handler); const agent = new ExtHostChatAgent(extension, id, this._proxy, handle, handler);

View file

@ -99,7 +99,7 @@ class ExtHostEditorTab {
case TabInputKind.InteractiveEditorInput: case TabInputKind.InteractiveEditorInput:
return new InteractiveWindowInput(URI.revive(this._dto.input.uri), URI.revive(this._dto.input.inputBoxUri)); return new InteractiveWindowInput(URI.revive(this._dto.input.uri), URI.revive(this._dto.input.inputBoxUri));
case TabInputKind.ChatEditorInput: case TabInputKind.ChatEditorInput:
return new ChatEditorTabInput(this._dto.input.providerId); return new ChatEditorTabInput();
case TabInputKind.MultiDiffEditorInput: case TabInputKind.MultiDiffEditorInput:
return new TextMultiDiffTabInput(this._dto.input.diffEditors.map(diff => new TextDiffTabInput(URI.revive(diff.original), URI.revive(diff.modified)))); return new TextMultiDiffTabInput(this._dto.input.diffEditors.map(diff => new TextDiffTabInput(URI.revive(diff.original), URI.revive(diff.modified))));
default: default:

View file

@ -4207,7 +4207,7 @@ export class InteractiveWindowInput {
} }
export class ChatEditorTabInput { export class ChatEditorTabInput {
constructor(readonly providerId: string) { } constructor() { }
} }
export class TextMultiDiffTabInput { export class TextMultiDiffTabInput {

View file

@ -18,7 +18,7 @@ import { AccessibleViewProviderId, AccessibilityVerbositySettingId } from 'vs/wo
import { descriptionForCommand } from 'vs/workbench/contrib/accessibility/browser/accessibleViewContributions'; import { descriptionForCommand } from 'vs/workbench/contrib/accessibility/browser/accessibleViewContributions';
import { IAccessibleViewService, IAccessibleContentProvider, IAccessibleViewOptions, AccessibleViewType } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { IAccessibleViewService, IAccessibleContentProvider, IAccessibleViewOptions, AccessibleViewType } from 'vs/workbench/contrib/accessibility/browser/accessibleView';
import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; 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 { CommentAccessibilityHelpNLS } from 'vs/workbench/contrib/comments/browser/commentsAccessibility';
import { CommentCommandId } from 'vs/workbench/contrib/comments/common/commentCommandIds'; import { CommentCommandId } from 'vs/workbench/contrib/comments/common/commentCommandIds';
import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/commentContextKeys'; 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 { 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[] = []; const commentCommandInfo: string[] = [];
commentCommandInfo.push(descriptionForCommand('workbench.action.quickchat.toggle', AccessibilityHelpNLS.quickChat, AccessibilityHelpNLS.quickChatNoKb, keybindingService)); commentCommandInfo.push(descriptionForCommand('workbench.action.quickchat.toggle', AccessibilityHelpNLS.quickChat, AccessibilityHelpNLS.quickChatNoKb, keybindingService));
commentCommandInfo.push(descriptionForCommand('inlineChat.start', AccessibilityHelpNLS.startInlineChat, AccessibilityHelpNLS.startInlineChatNoKb, keybindingService)); commentCommandInfo.push(descriptionForCommand('inlineChat.start', AccessibilityHelpNLS.startInlineChat, AccessibilityHelpNLS.startInlineChatNoKb, keybindingService));

View file

@ -11,7 +11,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorAction2, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { EditorAction2, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { localize, localize2 } from 'vs/nls'; 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 { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IsLinuxContext, IsWindowsContext } from 'vs/platform/contextkey/common/contextkeys'; import { IsLinuxContext, IsWindowsContext } from 'vs/platform/contextkey/common/contextkeys';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; 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 { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions';
import { runAccessibilityHelpAction } from 'vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp'; 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 { IChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatEditor';
import { ChatEditorInput } from 'vs/workbench/contrib/chat/browser/chatEditorInput'; import { ChatEditorInput } from 'vs/workbench/contrib/chat/browser/chatEditorInput';
import { ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane'; import { ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane';
import { ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents'; 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 { 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 { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService';
import { IChatDetail, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatDetail, IChatService } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatWidgetHistoryService } from 'vs/workbench/contrib/chat/common/chatWidgetHistoryService'; import { IChatWidgetHistoryService } from 'vs/workbench/contrib/chat/common/chatWidgetHistoryService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; 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_CATEGORY = localize2('chat.category', 'Chat');
export const CHAT_OPEN_ACTION_ID = 'workbench.action.chat.open'; export const CHAT_OPEN_ACTION_ID = 'workbench.action.chat.open';
@ -53,7 +60,7 @@ class OpenChatGlobalAction extends Action2 {
super({ super({
id: CHAT_OPEN_ACTION_ID, id: CHAT_OPEN_ACTION_ID,
title: localize2('openChat', "Open Chat"), title: localize2('openChat', "Open Chat"),
precondition: CONTEXT_PROVIDER_EXISTS, precondition: CONTEXT_HAS_DEFAULT_AGENT,
icon: Codicon.commentDiscussion, icon: Codicon.commentDiscussion,
f1: false, f1: false,
category: CHAT_CATEGORY, category: CHAT_CATEGORY,
@ -70,13 +77,7 @@ class OpenChatGlobalAction extends Action2 {
override async run(accessor: ServicesAccessor, opts?: string | IChatViewOpenOptions): Promise<void> { override async run(accessor: ServicesAccessor, opts?: string | IChatViewOpenOptions): Promise<void> {
opts = typeof opts === 'string' ? { query: opts } : opts; opts = typeof opts === 'string' ? { query: opts } : opts;
const chatService = accessor.get(IChatService); const chatWidget = await showChatView(accessor.get(IViewsService));
const chatWidgetService = accessor.get(IChatWidgetService);
const providers = chatService.getProviderInfos();
if (!providers.length) {
return;
}
const chatWidget = await chatWidgetService.revealViewForProvider(providers[0].id);
if (!chatWidget) { if (!chatWidget) {
return; return;
} }
@ -92,15 +93,82 @@ class OpenChatGlobalAction extends Action2 {
} }
} }
class ChatHistoryAction extends ViewAction<ChatViewPane> {
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 => (<IQuickPickItem & { chat: IChatDetail }>{
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() { export function registerChatActions() {
registerAction2(OpenChatGlobalAction); registerAction2(OpenChatGlobalAction);
registerAction2(ChatHistoryAction);
registerAction2(OpenChatEditorAction);
registerAction2(class ClearChatInputHistoryAction extends Action2 { registerAction2(class ClearChatInputHistoryAction extends Action2 {
constructor() { constructor() {
super({ super({
id: 'workbench.action.chat.clearInputHistory', id: 'workbench.action.chat.clearInputHistory',
title: localize2('interactiveSession.clearHistory.label', "Clear Input History"), title: localize2('interactiveSession.clearHistory.label', "Clear Input History"),
precondition: CONTEXT_PROVIDER_EXISTS, precondition: CONTEXT_HAS_DEFAULT_AGENT,
category: CHAT_CATEGORY, category: CHAT_CATEGORY,
f1: true, f1: true,
}); });
@ -116,7 +184,7 @@ export function registerChatActions() {
super({ super({
id: 'workbench.action.chat.clearHistory', id: 'workbench.action.chat.clearHistory',
title: localize2('chat.clear.label', "Clear All Workspace Chats"), title: localize2('chat.clear.label', "Clear All Workspace Chats"),
precondition: CONTEXT_PROVIDER_EXISTS, precondition: CONTEXT_HAS_DEFAULT_AGENT,
category: CHAT_CATEGORY, category: CHAT_CATEGORY,
f1: true, 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: <IChatEditorOptions>{ target: { providerId: id }, pinned: true } });
}
};
}
const getHistoryChatActionDescriptorForViewTitle = (viewId: string, providerId: string): Readonly<IAction2Options> & { 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<ChatViewPane> {
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 => (<IQuickPickItem & { chat: IChatDetail }>{
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);
}
}
}
};
}

View file

@ -14,10 +14,10 @@ export async function clearChatEditor(accessor: ServicesAccessor): Promise<void>
const editorGroupsService = accessor.get(IEditorGroupsService); const editorGroupsService = accessor.get(IEditorGroupsService);
const chatEditorInput = editorService.activeEditor; const chatEditorInput = editorService.activeEditor;
if (chatEditorInput instanceof ChatEditorInput && chatEditorInput.providerId) { if (chatEditorInput instanceof ChatEditorInput) {
await editorService.replaceEditors([{ await editorService.replaceEditors([{
editor: chatEditorInput, editor: chatEditorInput,
replacement: { resource: ChatEditorInput.getNewEditorUri(), options: <IChatEditorOptions>{ target: { providerId: chatEditorInput.providerId, pinned: true } } } replacement: { resource: ChatEditorInput.getNewEditorUri(), options: { pinned: true } satisfies IChatEditorOptions }
}], editorGroupsService.activeGroup); }], editorGroupsService.activeGroup);
} }
} }

View file

@ -7,23 +7,20 @@ import { Codicon } from 'vs/base/common/codicons';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { localize2 } from 'vs/nls'; 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 { 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 { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; 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 { 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 { 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 { ChatEditorInput } from 'vs/workbench/contrib/chat/browser/chatEditorInput';
import { ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane'; import { CONTEXT_IN_CHAT_SESSION, CONTEXT_HAS_DEFAULT_AGENT } from 'vs/workbench/contrib/chat/common/chatContextKeys';
import { CONTEXT_IN_CHAT_SESSION, CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys';
export const ACTION_ID_NEW_CHAT = `workbench.action.chat.newChat`; export const ACTION_ID_NEW_CHAT = `workbench.action.chat.newChat`;
export function registerNewChatActions() { export function registerNewChatActions() {
registerAction2(class NewChatEditorAction extends Action2 { registerAction2(class NewChatEditorAction extends Action2 {
constructor() { constructor() {
super({ super({
@ -31,7 +28,7 @@ export function registerNewChatActions() {
title: localize2('chat.newChat.label', "New Chat"), title: localize2('chat.newChat.label', "New Chat"),
icon: Codicon.plus, icon: Codicon.plus,
f1: false, f1: false,
precondition: CONTEXT_PROVIDER_EXISTS, precondition: CONTEXT_HAS_DEFAULT_AGENT,
menu: [{ menu: [{
id: MenuId.EditorTitle, id: MenuId.EditorTitle,
group: 'navigation', group: 'navigation',
@ -46,7 +43,6 @@ export function registerNewChatActions() {
} }
}); });
registerAction2(class GlobalClearChatAction extends Action2 { registerAction2(class GlobalClearChatAction extends Action2 {
constructor() { constructor() {
super({ super({
@ -54,7 +50,7 @@ export function registerNewChatActions() {
title: localize2('chat.newChat.label', "New Chat"), title: localize2('chat.newChat.label', "New Chat"),
category: CHAT_CATEGORY, category: CHAT_CATEGORY,
icon: Codicon.plus, icon: Codicon.plus,
precondition: CONTEXT_PROVIDER_EXISTS, precondition: CONTEXT_HAS_DEFAULT_AGENT,
f1: true, f1: true,
keybinding: { keybinding: {
weight: KeybindingWeight.WorkbenchContrib, weight: KeybindingWeight.WorkbenchContrib,
@ -64,58 +60,42 @@ export function registerNewChatActions() {
}, },
when: CONTEXT_IN_CHAT_SESSION when: CONTEXT_IN_CHAT_SESSION
}, },
menu: { menu: [{
id: MenuId.ChatContext, id: MenuId.ChatContext,
group: 'z_clear' group: 'z_clear'
} },
{
id: MenuId.ViewTitle,
when: ContextKeyExpr.equals('view', CHAT_VIEW_ID),
group: 'navigation',
order: -1
}]
}); });
} }
run(accessor: ServicesAccessor, ...args: any[]) { run(accessor: ServicesAccessor, ...args: any[]) {
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 widgetService = accessor.get(IChatWidgetService);
const widget = widgetService.lastFocusedWidget; const widget = widgetService.lastFocusedWidget;
if (!widget) { if (!widget) {
return; return;
} }
announceChatCleared(accessor); announceChatCleared(accessor);
widget.clear(); widget.clear();
widget.focusInput(); widget.focusInput();
} }
}
}); });
} }
const getNewChatActionDescriptorForViewTitle = (viewId: string, providerId: string): Readonly<IAction2Options> & { 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<ChatViewPane> {
constructor() {
super(getNewChatActionDescriptorForViewTitle(viewId, providerId));
}
async runInView(accessor: ServicesAccessor, view: ChatViewPane) {
announceChatCleared(accessor);
await view.clear();
view.widget.focusInput();
}
};
}
function announceChatCleared(accessor: ServicesAccessor): void { function announceChatCleared(accessor: ServicesAccessor): void {
accessor.get(IAccessibilitySignalService).playSignal(AccessibilitySignal.clear); accessor.get(IAccessibilitySignalService).playSignal(AccessibilitySignal.clear);
} }

View file

@ -29,7 +29,7 @@ import { accessibleViewInCodeBlock } from 'vs/workbench/contrib/accessibility/br
import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions';
import { IChatWidgetService, IChatCodeBlockContextProviderService } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatWidgetService, IChatCodeBlockContextProviderService } from 'vs/workbench/contrib/chat/browser/chat';
import { ICodeBlockActionContext, ICodeCompareBlockActionContext } from 'vs/workbench/contrib/chat/browser/codeBlockPart'; 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 { ChatCopyKind, IChatService, IDocumentContext } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatResponseViewModel, isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { IChatResponseViewModel, isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel';
import { insertCell } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; import { insertCell } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations';
@ -109,7 +109,6 @@ export function registerChatCodeBlockActions() {
if (isResponseVM(context.element)) { if (isResponseVM(context.element)) {
const chatService = accessor.get(IChatService); const chatService = accessor.get(IChatService);
chatService.notifyUserAction({ chatService.notifyUserAction({
providerId: context.element.providerId,
agentId: context.element.agent?.id, agentId: context.element.agent?.id,
sessionId: context.element.sessionId, sessionId: context.element.sessionId,
requestId: context.element.requestId, requestId: context.element.requestId,
@ -155,7 +154,6 @@ export function registerChatCodeBlockActions() {
const element = context.element as IChatResponseViewModel | undefined; const element = context.element as IChatResponseViewModel | undefined;
if (element) { if (element) {
chatService.notifyUserAction({ chatService.notifyUserAction({
providerId: element.providerId,
agentId: element.agent?.id, agentId: element.agent?.id,
sessionId: element.sessionId, sessionId: element.sessionId,
requestId: element.requestId, requestId: element.requestId,
@ -185,7 +183,7 @@ export function registerChatCodeBlockActions() {
super({ super({
id: 'workbench.action.chat.insertCodeBlock', id: 'workbench.action.chat.insertCodeBlock',
title: localize2('interactive.insertCodeBlock.label', "Insert at Cursor"), title: localize2('interactive.insertCodeBlock.label', "Insert at Cursor"),
precondition: CONTEXT_PROVIDER_EXISTS, precondition: CONTEXT_HAS_DEFAULT_AGENT,
f1: true, f1: true,
category: CHAT_CATEGORY, category: CHAT_CATEGORY,
icon: Codicon.insert, icon: Codicon.insert,
@ -331,7 +329,6 @@ export function registerChatCodeBlockActions() {
if (isResponseVM(context.element)) { if (isResponseVM(context.element)) {
const chatService = accessor.get(IChatService); const chatService = accessor.get(IChatService);
chatService.notifyUserAction({ chatService.notifyUserAction({
providerId: context.element.providerId,
agentId: context.element.agent?.id, agentId: context.element.agent?.id,
sessionId: context.element.sessionId, sessionId: context.element.sessionId,
requestId: context.element.requestId, requestId: context.element.requestId,
@ -352,7 +349,7 @@ export function registerChatCodeBlockActions() {
super({ super({
id: 'workbench.action.chat.insertIntoNewFile', id: 'workbench.action.chat.insertIntoNewFile',
title: localize2('interactive.insertIntoNewFile.label', "Insert into New File"), title: localize2('interactive.insertIntoNewFile.label', "Insert into New File"),
precondition: CONTEXT_PROVIDER_EXISTS, precondition: CONTEXT_HAS_DEFAULT_AGENT,
f1: true, f1: true,
category: CHAT_CATEGORY, category: CHAT_CATEGORY,
icon: Codicon.newFile, icon: Codicon.newFile,
@ -377,7 +374,6 @@ export function registerChatCodeBlockActions() {
if (isResponseVM(context.element)) { if (isResponseVM(context.element)) {
chatService.notifyUserAction({ chatService.notifyUserAction({
providerId: context.element.providerId,
agentId: context.element.agent?.id, agentId: context.element.agent?.id,
sessionId: context.element.sessionId, sessionId: context.element.sessionId,
requestId: context.element.requestId, requestId: context.element.requestId,
@ -407,7 +403,7 @@ export function registerChatCodeBlockActions() {
super({ super({
id: 'workbench.action.chat.runInTerminal', id: 'workbench.action.chat.runInTerminal',
title: localize2('interactive.runInTerminal.label', "Insert into Terminal"), title: localize2('interactive.runInTerminal.label', "Insert into Terminal"),
precondition: CONTEXT_PROVIDER_EXISTS, precondition: CONTEXT_HAS_DEFAULT_AGENT,
f1: true, f1: true,
category: CHAT_CATEGORY, category: CHAT_CATEGORY,
icon: Codicon.terminal, icon: Codicon.terminal,
@ -470,7 +466,6 @@ export function registerChatCodeBlockActions() {
if (isResponseVM(context.element)) { if (isResponseVM(context.element)) {
chatService.notifyUserAction({ chatService.notifyUserAction({
providerId: context.element.providerId,
agentId: context.element.agent?.id, agentId: context.element.agent?.id,
sessionId: context.element.sessionId, sessionId: context.element.sessionId,
requestId: context.element.requestId, requestId: context.element.requestId,
@ -526,7 +521,7 @@ export function registerChatCodeBlockActions() {
weight: KeybindingWeight.WorkbenchContrib, weight: KeybindingWeight.WorkbenchContrib,
when: CONTEXT_IN_CHAT_SESSION, when: CONTEXT_IN_CHAT_SESSION,
}, },
precondition: CONTEXT_PROVIDER_EXISTS, precondition: CONTEXT_HAS_DEFAULT_AGENT,
f1: true, f1: true,
category: CHAT_CATEGORY, category: CHAT_CATEGORY,
}); });
@ -548,7 +543,7 @@ export function registerChatCodeBlockActions() {
weight: KeybindingWeight.WorkbenchContrib, weight: KeybindingWeight.WorkbenchContrib,
when: CONTEXT_IN_CHAT_SESSION, when: CONTEXT_IN_CHAT_SESSION,
}, },
precondition: CONTEXT_PROVIDER_EXISTS, precondition: CONTEXT_HAS_DEFAULT_AGENT,
f1: true, f1: true,
category: CHAT_CATEGORY, category: CHAT_CATEGORY,
}); });

View file

@ -10,7 +10,7 @@ import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions';
import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; 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'; import { IChatResponseViewModel, isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel';
export function registerChatFileTreeActions() { export function registerChatFileTreeActions() {
@ -24,7 +24,7 @@ export function registerChatFileTreeActions() {
weight: KeybindingWeight.WorkbenchContrib, weight: KeybindingWeight.WorkbenchContrib,
when: CONTEXT_IN_CHAT_SESSION, when: CONTEXT_IN_CHAT_SESSION,
}, },
precondition: CONTEXT_PROVIDER_EXISTS, precondition: CONTEXT_HAS_DEFAULT_AGENT,
f1: true, f1: true,
category: CHAT_CATEGORY, category: CHAT_CATEGORY,
}); });
@ -45,7 +45,7 @@ export function registerChatFileTreeActions() {
weight: KeybindingWeight.WorkbenchContrib, weight: KeybindingWeight.WorkbenchContrib,
when: CONTEXT_IN_CHAT_SESSION, when: CONTEXT_IN_CHAT_SESSION,
}, },
precondition: CONTEXT_PROVIDER_EXISTS, precondition: CONTEXT_HAS_DEFAULT_AGENT,
f1: true, f1: true,
category: CHAT_CATEGORY, category: CHAT_CATEGORY,
}); });

View file

@ -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 { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat';
import { IChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatEditor'; import { IChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatEditor';
import { ChatEditorInput } from 'vs/workbench/contrib/chat/browser/chatEditorInput'; 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 { isExportableSessionData } from 'vs/workbench/contrib/chat/common/chatModel';
import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatService } from 'vs/workbench/contrib/chat/common/chatService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
@ -29,7 +29,7 @@ export function registerChatExportActions() {
id: 'workbench.action.chat.export', id: 'workbench.action.chat.export',
category: CHAT_CATEGORY, category: CHAT_CATEGORY,
title: localize2('chat.export.label', "Export Chat..."), title: localize2('chat.export.label', "Export Chat..."),
precondition: CONTEXT_PROVIDER_EXISTS, precondition: CONTEXT_HAS_DEFAULT_AGENT,
f1: true, f1: true,
}); });
} }
@ -70,7 +70,7 @@ export function registerChatExportActions() {
id: 'workbench.action.chat.import', id: 'workbench.action.chat.import',
title: localize2('chat.import.label', "Import Chat..."), title: localize2('chat.import.label', "Import Chat..."),
category: CHAT_CATEGORY, category: CHAT_CATEGORY,
precondition: CONTEXT_PROVIDER_EXISTS, precondition: CONTEXT_HAS_DEFAULT_AGENT,
f1: true, f1: true,
}); });
} }
@ -96,7 +96,7 @@ export function registerChatExportActions() {
throw new Error('Invalid chat session data'); throw new Error('Invalid chat session data');
} }
await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options: <IChatEditorOptions>{ target: { data }, pinned: true } }); await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options: { target: { data }, pinned: true } as IChatEditorOptions });
} catch (err) { } catch (err) {
throw err; throw err;
} }

View file

@ -3,107 +3,68 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { localize, localize2 } from 'vs/nls'; import { 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 { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; 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 { ActiveEditorContext } from 'vs/workbench/common/contextkeys';
import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { CHAT_CATEGORY, isChatViewTitleActionContext } from 'vs/workbench/contrib/chat/browser/actions/chatActions';
import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; import { CHAT_VIEW_ID, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat';
import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat';
import { IChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatEditor'; import { IChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatEditor';
import { ChatEditorInput } from 'vs/workbench/contrib/chat/browser/chatEditorInput'; import { ChatEditorInput } from 'vs/workbench/contrib/chat/browser/chatEditorInput';
import { ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane'; import { ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane';
import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { CONTEXT_HAS_DEFAULT_AGENT } 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 { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { ACTIVE_GROUP, AUX_WINDOW_GROUP, IEditorService } from 'vs/workbench/services/editor/common/editorService'; 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 { enum MoveToNewLocation {
Editor = 'Editor', Editor = 'Editor',
Window = 'Window' Window = 'Window'
} }
const getMoveToChatActionDescriptorForViewTitle = (viewId: string, providerId: string, moveTo: MoveToNewLocation): Readonly<IAction2Options> & { 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<ChatViewPane> {
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: <IChatEditorOptions>{ target: { sessionId }, pinned: true, viewState: viewState } }, moveTo === MoveToNewLocation.Window ? AUX_WINDOW_GROUP : ACTIVE_GROUP);
}
};
}
export function registerMoveActions() { export function registerMoveActions() {
registerAction2(class GlobalMoveToEditorAction extends Action2 { registerAction2(class GlobalMoveToEditorAction extends Action2 {
constructor() { constructor() {
super({ super({
id: `workbench.action.chat.openInEditor`, 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, category: CHAT_CATEGORY,
precondition: CONTEXT_PROVIDER_EXISTS, precondition: CONTEXT_HAS_DEFAULT_AGENT,
f1: true f1: true,
menu: {
id: MenuId.ViewTitle,
when: ContextKeyExpr.equals('view', CHAT_VIEW_ID),
order: 0
},
}); });
} }
async run(accessor: ServicesAccessor, ...args: any[]) { 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 { registerAction2(class GlobalMoveToNewWindowAction extends Action2 {
constructor() { constructor() {
super({ super({
id: `workbench.action.chat.openInNewWindow`, 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, category: CHAT_CATEGORY,
precondition: CONTEXT_PROVIDER_EXISTS, precondition: CONTEXT_HAS_DEFAULT_AGENT,
f1: true f1: true,
menu: {
id: MenuId.ViewTitle,
when: ContextKeyExpr.equals('view', CHAT_VIEW_ID),
order: 0
},
}); });
} }
async run(accessor: ServicesAccessor, ...args: any[]) { 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`, id: `workbench.action.chat.openInSidebar`,
title: localize2('interactiveSession.openInSidebar.label', "Open Chat in Side Bar"), title: localize2('interactiveSession.openInSidebar.label', "Open Chat in Side Bar"),
category: CHAT_CATEGORY, category: CHAT_CATEGORY,
precondition: CONTEXT_PROVIDER_EXISTS, precondition: CONTEXT_HAS_DEFAULT_AGENT,
f1: true, f1: true,
menu: [{ menu: [{
id: MenuId.EditorTitle, 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 widgetService = accessor.get(IChatWidgetService);
const viewService = accessor.get(IViewsService); const viewService = accessor.get(IViewsService);
const chatService = accessor.get(IChatService);
const editorService = accessor.get(IEditorService); const editorService = accessor.get(IEditorService);
const widget = widgetService.lastFocusedWidget; const widget = chatView?.widget ?? widgetService.lastFocusedWidget;
if (!widget || !('viewId' in widget.viewContext)) { if (!widget || !('viewId' in widget.viewContext)) {
const providerId = chatService.getProviderInfos()[0].id; await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options: <IChatEditorOptions>{ pinned: true } }, moveTo === MoveToNewLocation.Window ? AUX_WINDOW_GROUP : ACTIVE_GROUP);
await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options: <IChatEditorOptions>{ target: { providerId }, pinned: true } }, moveTo === MoveToNewLocation.Window ? AUX_WINDOW_GROUP : ACTIVE_GROUP);
return; return;
} }
@ -159,19 +117,14 @@ async function executeMoveToAction(accessor: ServicesAccessor, moveTo: MoveToNew
async function moveToSidebar(accessor: ServicesAccessor): Promise<void> { async function moveToSidebar(accessor: ServicesAccessor): Promise<void> {
const viewsService = accessor.get(IViewsService); const viewsService = accessor.get(IViewsService);
const editorService = accessor.get(IEditorService); const editorService = accessor.get(IEditorService);
const chatContribService = accessor.get(IChatContributionService);
const editorGroupService = accessor.get(IEditorGroupsService); const editorGroupService = accessor.get(IEditorGroupsService);
const chatEditorInput = editorService.activeEditor; 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 }); await editorService.closeEditor({ editor: chatEditorInput, groupId: editorGroupService.activeGroup.id });
const viewId = chatContribService.getViewIdForProvider(chatEditorInput.providerId); const view = await viewsService.openView(CHAT_VIEW_ID) as ChatViewPane;
const view = await viewsService.openView(viewId) as ChatViewPane;
view.loadSession(chatEditorInput.sessionId); view.loadSession(chatEditorInput.sessionId);
} else { } else {
const chatService = accessor.get(IChatService); await viewsService.openView(CHAT_VIEW_ID);
const providerId = chatService.getProviderInfos()[0].id;
const viewId = chatContribService.getViewIdForProvider(providerId);
await viewsService.openView(viewId);
} }
} }

View file

@ -12,13 +12,14 @@ import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/act
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions';
import { IQuickChatService, IQuickChatOpenOptions } from 'vs/workbench/contrib/chat/browser/chat'; import { IQuickChatOpenOptions, IQuickChatService } from 'vs/workbench/contrib/chat/browser/chat';
import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { CONTEXT_HAS_DEFAULT_AGENT } from 'vs/workbench/contrib/chat/common/chatContextKeys';
import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController';
export const ASK_QUICK_QUESTION_ACTION_ID = 'workbench.action.quickchat.toggle'; export const ASK_QUICK_QUESTION_ACTION_ID = 'workbench.action.quickchat.toggle';
export function registerQuickChatActions() { export function registerQuickChatActions() {
registerAction2(QuickChatGlobalAction); registerAction2(QuickChatGlobalAction);
registerAction2(AskQuickChatAction);
registerAction2(class OpenInChatViewAction extends Action2 { registerAction2(class OpenInChatViewAction extends Action2 {
constructor() { constructor() {
@ -94,6 +95,7 @@ export function registerQuickChatActions() {
controller.focus(); controller.focus();
} }
}); });
} }
class QuickChatGlobalAction extends Action2 { class QuickChatGlobalAction extends Action2 {
@ -101,7 +103,7 @@ class QuickChatGlobalAction extends Action2 {
super({ super({
id: ASK_QUICK_QUESTION_ACTION_ID, id: ASK_QUICK_QUESTION_ACTION_ID,
title: localize2('quickChat', 'Quick Chat'), title: localize2('quickChat', 'Quick Chat'),
precondition: CONTEXT_PROVIDER_EXISTS, precondition: CONTEXT_HAS_DEFAULT_AGENT,
icon: Codicon.commentDiscussion, icon: Codicon.commentDiscussion,
f1: false, f1: false,
category: CHAT_CATEGORY, category: CHAT_CATEGORY,
@ -153,35 +155,25 @@ class QuickChatGlobalAction extends Action2 {
if (options?.query) { if (options?.query) {
options.selection = new Selection(1, options.query.length + 1, 1, options.query.length + 1); options.selection = new Selection(1, options.query.length + 1, 1, options.query.length + 1);
} }
quickChatService.toggle(undefined, options); quickChatService.toggle(options);
} }
} }
/** class AskQuickChatAction extends Action2 {
* 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() { constructor() {
super({ super({
id: `workbench.action.openQuickChat.${id}`, id: `workbench.action.openQuickChat`,
category: CHAT_CATEGORY, category: CHAT_CATEGORY,
title: localize2('interactiveSession.open', "Open Quick Chat ({0})", label), title: localize2('interactiveSession.open', "Open Quick Chat"),
f1: true f1: true
}); });
} }
override run(accessor: ServicesAccessor, query?: string): void { override run(accessor: ServicesAccessor, query?: string): void {
const quickChatService = accessor.get(IQuickChatService); const quickChatService = accessor.get(IQuickChatService);
quickChatService.toggle(id, query ? { quickChatService.toggle(query ? {
query, query,
selection: new Selection(1, query.length + 1, 1, query.length + 1) selection: new Selection(1, query.length + 1, 1, query.length + 1)
} : undefined); } : undefined);
} }
};
} }

View file

@ -50,7 +50,6 @@ export function registerChatTitleActions() {
const chatService = accessor.get(IChatService); const chatService = accessor.get(IChatService);
chatService.notifyUserAction({ chatService.notifyUserAction({
providerId: item.providerId,
agentId: item.agent?.id, agentId: item.agent?.id,
sessionId: item.sessionId, sessionId: item.sessionId,
requestId: item.requestId, requestId: item.requestId,
@ -90,7 +89,6 @@ export function registerChatTitleActions() {
const chatService = accessor.get(IChatService); const chatService = accessor.get(IChatService);
chatService.notifyUserAction({ chatService.notifyUserAction({
providerId: item.providerId,
agentId: item.agent?.id, agentId: item.agent?.id,
sessionId: item.sessionId, sessionId: item.sessionId,
requestId: item.requestId, requestId: item.requestId,
@ -129,7 +127,6 @@ export function registerChatTitleActions() {
const chatService = accessor.get(IChatService); const chatService = accessor.get(IChatService);
chatService.notifyUserAction({ chatService.notifyUserAction({
providerId: item.providerId,
agentId: item.agent?.id, agentId: item.agent?.id,
sessionId: item.sessionId, sessionId: item.sessionId,
requestId: item.requestId, requestId: item.requestId,

View file

@ -19,9 +19,9 @@ import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions';
import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/editor'; import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/editor';
import { AccessibilityVerbositySettingId, AccessibleViewProviderId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; 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 { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView';
import { AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; 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 { registerChatActions } from 'vs/workbench/contrib/chat/browser/actions/chatActions';
import { ACTION_ID_NEW_CHAT, registerNewChatActions } from 'vs/workbench/contrib/chat/browser/actions/chatClearActions'; 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'; 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 { registerChatTitleActions } from 'vs/workbench/contrib/chat/browser/actions/chatTitleActions';
import { IChatAccessibilityService, IChatCodeBlockContextProviderService, IChatWidget, IChatWidgetService, IQuickChatService } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatAccessibilityService, IChatCodeBlockContextProviderService, IChatWidget, IChatWidgetService, IQuickChatService } from 'vs/workbench/contrib/chat/browser/chat';
import { ChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chatAccessibilityService'; 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 { ChatEditor, IChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatEditor';
import { ChatEditorInput, ChatEditorInputSerializer } from 'vs/workbench/contrib/chat/browser/chatEditorInput'; import { ChatEditorInput, ChatEditorInputSerializer } from 'vs/workbench/contrib/chat/browser/chatEditorInput';
import { QuickChatService } from 'vs/workbench/contrib/chat/browser/chatQuick'; import { QuickChatService } from 'vs/workbench/contrib/chat/browser/chatQuick';
import { ChatVariablesService } from 'vs/workbench/contrib/chat/browser/chatVariables'; import { ChatVariablesService } from 'vs/workbench/contrib/chat/browser/chatVariables';
import { ChatWidgetService } from 'vs/workbench/contrib/chat/browser/chatWidget'; 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/chatHistoryVariables';
import 'vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib'; import 'vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib';
import { ChatAgentLocation, ChatAgentService, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatAgentLocation, ChatAgentService, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
import { CONTEXT_IN_CHAT_SESSION } from 'vs/workbench/contrib/chat/common/chatContextKeys'; 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 { ChatWelcomeMessageModel } from 'vs/workbench/contrib/chat/common/chatModel';
import { chatAgentLeader, chatSubcommandLeader, chatVariableLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { chatAgentLeader, chatSubcommandLeader, chatVariableLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes';
import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; 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 { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import '../common/chatColors'; import '../common/chatColors';
import { ChatCodeBlockContextProviderService } from 'vs/workbench/contrib/chat/browser/codeBlockContextProviderService'; import { ChatExtensionPointHandler } from 'vs/workbench/contrib/chat/browser/chatParticipantContributions';
// Register configuration // Register configuration
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration); const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
@ -314,6 +313,7 @@ registerWorkbenchContribution2(ChatResolverContribution.ID, ChatResolverContribu
workbenchContributionsRegistry.registerWorkbenchContribution(ChatAccessibleViewContribution, LifecyclePhase.Eventually); workbenchContributionsRegistry.registerWorkbenchContribution(ChatAccessibleViewContribution, LifecyclePhase.Eventually);
workbenchContributionsRegistry.registerWorkbenchContribution(ChatSlashStaticSlashCommandsContribution, LifecyclePhase.Eventually); workbenchContributionsRegistry.registerWorkbenchContribution(ChatSlashStaticSlashCommandsContribution, LifecyclePhase.Eventually);
Registry.as<IEditorFactoryRegistry>(EditorExtensions.EditorFactory).registerEditorSerializer(ChatEditorInput.TypeID, ChatEditorInputSerializer); Registry.as<IEditorFactoryRegistry>(EditorExtensions.EditorFactory).registerEditorSerializer(ChatEditorInput.TypeID, ChatEditorInputSerializer);
registerWorkbenchContribution2(ChatExtensionPointHandler.ID, ChatExtensionPointHandler, WorkbenchPhase.BlockStartup);
registerChatActions(); registerChatActions();
registerChatCopyActions(); registerChatCopyActions();
@ -328,7 +328,6 @@ registerMoveActions();
registerNewChatActions(); registerNewChatActions();
registerSingleton(IChatService, ChatService, InstantiationType.Delayed); registerSingleton(IChatService, ChatService, InstantiationType.Delayed);
registerSingleton(IChatContributionService, ChatContributionService, InstantiationType.Delayed);
registerSingleton(IChatWidgetService, ChatWidgetService, InstantiationType.Delayed); registerSingleton(IChatWidgetService, ChatWidgetService, InstantiationType.Delayed);
registerSingleton(IQuickChatService, QuickChatService, InstantiationType.Delayed); registerSingleton(IQuickChatService, QuickChatService, InstantiationType.Delayed);
registerSingleton(IChatAccessibilityService, ChatAccessibilityService, InstantiationType.Delayed); registerSingleton(IChatAccessibilityService, ChatAccessibilityService, InstantiationType.Delayed);

View file

@ -11,11 +11,14 @@ import { Selection } from 'vs/editor/common/core/selection';
import { localize } from 'vs/nls'; import { localize } from 'vs/nls';
import { MenuId } from 'vs/platform/actions/common/actions'; import { MenuId } from 'vs/platform/actions/common/actions';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; 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 { IChatWidgetContrib } from 'vs/workbench/contrib/chat/browser/chatWidget';
import { ICodeBlockActionContext } from 'vs/workbench/contrib/chat/browser/codeBlockPart'; import { ICodeBlockActionContext } from 'vs/workbench/contrib/chat/browser/codeBlockPart';
import { ChatAgentLocation, IChatAgentCommand, IChatAgentData } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatAgentLocation, IChatAgentCommand, IChatAgentData } from 'vs/workbench/contrib/chat/common/chatAgents';
import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; 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 { IChatRequestViewModel, IChatResponseViewModel, IChatViewModel, IChatWelcomeMessageViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel';
import { IViewsService } from 'vs/workbench/services/views/common/viewsService';
export const IChatWidgetService = createDecorator<IChatWidgetService>('chatWidgetService'); export const IChatWidgetService = createDecorator<IChatWidgetService>('chatWidgetService');
@ -28,25 +31,24 @@ export interface IChatWidgetService {
*/ */
readonly lastFocusedWidget: IChatWidget | undefined; readonly lastFocusedWidget: IChatWidget | undefined;
/**
* Returns whether a view was successfully revealed.
*/
revealViewForProvider(providerId: string): Promise<IChatWidget | undefined>;
getWidgetByInputUri(uri: URI): IChatWidget | undefined; getWidgetByInputUri(uri: URI): IChatWidget | undefined;
getWidgetBySessionId(sessionId: string): IChatWidget | undefined; getWidgetBySessionId(sessionId: string): IChatWidget | undefined;
} }
export async function showChatView(viewsService: IViewsService): Promise<IChatWidget | undefined> {
return (await viewsService.openView<ChatViewPane>(CHAT_VIEW_ID))?.widget;
}
export const IQuickChatService = createDecorator<IQuickChatService>('quickChatService'); export const IQuickChatService = createDecorator<IQuickChatService>('quickChatService');
export interface IQuickChatService { export interface IQuickChatService {
readonly _serviceBrand: undefined; readonly _serviceBrand: undefined;
readonly onDidClose: Event<void>; readonly onDidClose: Event<void>;
readonly enabled: boolean; readonly enabled: boolean;
readonly focused: boolean; readonly focused: boolean;
toggle(providerId?: string, options?: IQuickChatOpenOptions): void; toggle(options?: IQuickChatOpenOptions): void;
focus(): void; focus(): void;
open(providerId?: string, options?: IQuickChatOpenOptions): void; open(options?: IQuickChatOpenOptions): void;
close(): void; close(): void;
openInChatView(): void; openInChatView(): void;
} }
@ -129,7 +131,6 @@ export interface IChatWidget {
readonly viewContext: IChatWidgetViewContext; readonly viewContext: IChatWidgetViewContext;
readonly viewModel: IChatViewModel | undefined; readonly viewModel: IChatViewModel | undefined;
readonly inputEditor: ICodeEditor; readonly inputEditor: ICodeEditor;
readonly providerId: string;
readonly supportsFileReferences: boolean; readonly supportsFileReferences: boolean;
readonly parsedInput: IParsedChatRequest; readonly parsedInput: IParsedChatRequest;
lastSelectedAgent: IChatAgentData | undefined; lastSelectedAgent: IChatAgentData | undefined;
@ -172,3 +173,5 @@ export interface IChatCodeBlockContextProviderService {
} }
export const GeneratingPhrase = localize('generating', "Generating"); export const GeneratingPhrase = localize('generating', "Generating");
export const CHAT_VIEW_ID = `workbench.panel.chat.view.${CHAT_PROVIDER_ID}`;

View file

@ -18,13 +18,14 @@ import { IEditorOpenContext } from 'vs/workbench/common/editor';
import { Memento } from 'vs/workbench/common/memento'; import { Memento } from 'vs/workbench/common/memento';
import { ChatEditorInput } from 'vs/workbench/contrib/chat/browser/chatEditorInput'; import { ChatEditorInput } from 'vs/workbench/contrib/chat/browser/chatEditorInput';
import { IChatViewState, ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget'; 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 { clearChatEditor } from 'vs/workbench/contrib/chat/browser/actions/chatClear';
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
import { ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents'; 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 { export interface IChatEditorOptions extends IEditorOptions {
target: { sessionId: string } | { providerId: string } | { data: ISerializableChatData }; target?: { sessionId: string } | { data: IExportableChatData | ISerializableChatData };
} }
export class ChatEditor extends EditorPane { export class ChatEditor extends EditorPane {
@ -101,7 +102,7 @@ export class ChatEditor extends EditorPane {
} }
private updateModel(model: IChatModel, viewState?: IChatViewState): void { 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._viewState = viewState ?? this._memento.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE) as IChatViewState;
this.widget.setModel(model, { ...this._viewState }); this.widget.setModel(model, { ...this._viewState });
} }

View file

@ -29,7 +29,6 @@ export class ChatEditorInput extends EditorInput {
private readonly inputCount: number; private readonly inputCount: number;
public sessionId: string | undefined; public sessionId: string | undefined;
public providerId: string | undefined;
private model: IChatModel | undefined; private model: IChatModel | undefined;
@ -59,8 +58,9 @@ export class ChatEditorInput extends EditorInput {
throw new Error('Invalid chat URI'); throw new Error('Invalid chat URI');
} }
this.sessionId = 'sessionId' in options.target ? options.target.sessionId : undefined; this.sessionId = (options.target && 'sessionId' in options.target) ?
this.providerId = 'providerId' in options.target ? options.target.providerId : undefined; options.target.sessionId :
undefined;
this.inputCount = ChatEditorInput.getNextCount(); this.inputCount = ChatEditorInput.getNextCount();
ChatEditorInput.countsInUse.add(this.inputCount); ChatEditorInput.countsInUse.add(this.inputCount);
this._register(toDisposable(() => ChatEditorInput.countsInUse.delete(this.inputCount))); this._register(toDisposable(() => ChatEditorInput.countsInUse.delete(this.inputCount)));
@ -93,8 +93,8 @@ export class ChatEditorInput extends EditorInput {
override async resolve(): Promise<ChatEditorModel | null> { override async resolve(): Promise<ChatEditorModel | null> {
if (typeof this.sessionId === 'string') { if (typeof this.sessionId === 'string') {
this.model = this.chatService.getOrRestoreSession(this.sessionId); this.model = this.chatService.getOrRestoreSession(this.sessionId);
} else if (typeof this.providerId === 'string') { } else if (!this.options.target) {
this.model = this.chatService.startSession(this.providerId, CancellationToken.None); this.model = this.chatService.startSession(CancellationToken.None);
} else if ('data' in this.options.target) { } else if ('data' in this.options.target) {
this.model = this.chatService.loadSessionFromContent(this.options.target.data); this.model = this.chatService.loadSessionFromContent(this.options.target.data);
} }
@ -104,7 +104,6 @@ export class ChatEditorInput extends EditorInput {
} }
this.sessionId = this.model.sessionId; this.sessionId = this.model.sessionId;
this.providerId = this.model.providerId;
this._register(this.model.onDidChange(() => this._onDidChangeLabel.fire())); this._register(this.model.onDidChange(() => this._onDidChangeLabel.fire()));
return this._register(new ChatEditorModel(this.model)); return this._register(new ChatEditorModel(this.model));

View file

@ -125,7 +125,6 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
private inputEditorHasText: IContextKey<boolean>; private inputEditorHasText: IContextKey<boolean>;
private chatCursorAtTop: IContextKey<boolean>; private chatCursorAtTop: IContextKey<boolean>;
private inputEditorHasFocus: IContextKey<boolean>; private inputEditorHasFocus: IContextKey<boolean>;
private providerId: string | undefined;
private cachedDimensions: dom.Dimension | undefined; private cachedDimensions: dom.Dimension | undefined;
private cachedToolbarWidth: number | undefined; private cachedToolbarWidth: number | undefined;
@ -174,9 +173,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
return localize('chatInput', "Chat Input"); return localize('chatInput', "Chat Input");
} }
setState(providerId: string, inputValue: string | undefined): void { setState(inputValue: string | undefined): void {
this.providerId = providerId; const history = this.historyService.getHistory();
const history = this.historyService.getHistory(providerId);
this.history = new HistoryNavigator(history, 50); this.history = new HistoryNavigator(history, 50);
if (typeof inputValue === 'string') { if (typeof inputValue === 'string') {
@ -495,7 +493,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
saveState(): void { saveState(): void {
const inputHistory = this.history.getHistory(); const inputHistory = this.history.getHistory();
this.historyService.saveHistory(this.providerId!, inputHistory); this.historyService.saveHistory(inputHistory);
} }
} }

View file

@ -5,9 +5,8 @@
import { isNonEmptyArray } from 'vs/base/common/arrays'; import { isNonEmptyArray } from 'vs/base/common/arrays';
import { Codicon } from 'vs/base/common/codicons'; 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 { localize, localize2 } from 'vs/nls';
import { registerAction2 } from 'vs/platform/actions/common/actions';
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; 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 { IProductService } from 'vs/platform/product/common/productService';
import { Registry } from 'vs/platform/registry/common/platform'; import { Registry } from 'vs/platform/registry/common/platform';
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; 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 { 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 { CHAT_VIEW_ID } from 'vs/workbench/contrib/chat/browser/chat';
import { getNewChatAction } from 'vs/workbench/contrib/chat/browser/actions/chatClearActions'; import { CHAT_SIDEBAR_PANEL_ID, ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane';
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 { ChatAgentLocation, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; 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 { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
import * as extensionsRegistry from 'vs/workbench/services/extensions/common/extensionsRegistry'; import * as extensionsRegistry from 'vs/workbench/services/extensions/common/extensionsRegistry';
const chatExtensionPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint<IRawChatProviderContribution[]>({
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<IRawChatParticipantContribution[]>({ const chatParticipantExtensionPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint<IRawChatParticipantContribution[]>({
extensionPoint: 'chatParticipants', extensionPoint: 'chatParticipants',
jsonSchema: { jsonSchema: {
@ -168,11 +127,9 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution {
private readonly disposables = new DisposableStore(); private readonly disposables = new DisposableStore();
private _welcomeViewDescriptor?: IViewDescriptor; private _welcomeViewDescriptor?: IViewDescriptor;
private _viewContainer: ViewContainer; private _viewContainer: ViewContainer;
private _registrationDisposables = new Map<string, IDisposable>();
private _participantRegistrationDisposables = new DisposableMap<string>(); private _participantRegistrationDisposables = new DisposableMap<string>();
constructor( constructor(
@IChatContributionService private readonly _chatContributionService: IChatContributionService,
@IChatAgentService private readonly _chatAgentService: IChatAgentService, @IChatAgentService private readonly _chatAgentService: IChatAgentService,
@IProductService private readonly productService: IProductService, @IProductService private readonly productService: IProductService,
@IContextKeyService private readonly contextService: IContextKeyService, @IContextKeyService private readonly contextService: IContextKeyService,
@ -196,20 +153,18 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution {
const contextKeyExpr = ContextKeyExpr.equals(showWelcomeViewConfigKey, true); const contextKeyExpr = ContextKeyExpr.equals(showWelcomeViewConfigKey, true);
const viewsRegistry = Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry); const viewsRegistry = Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry);
if (this.contextService.contextMatchesRules(contextKeyExpr)) { if (this.contextService.contextMatchesRules(contextKeyExpr)) {
const viewId = this._chatContributionService.getViewIdForProvider(this.productService.chatWelcomeView.welcomeViewId);
this._welcomeViewDescriptor = { this._welcomeViewDescriptor = {
id: viewId, id: CHAT_VIEW_ID,
name: { original: this.productService.chatWelcomeView.welcomeViewTitle, value: this.productService.chatWelcomeView.welcomeViewTitle }, name: { original: this.productService.chatWelcomeView.welcomeViewTitle, value: this.productService.chatWelcomeView.welcomeViewTitle },
containerIcon: this._viewContainer.icon, containerIcon: this._viewContainer.icon,
ctorDescriptor: new SyncDescriptor(ChatViewPane, [<IChatViewOptions>{ providerId: this.productService.chatWelcomeView.welcomeViewId }]), ctorDescriptor: new SyncDescriptor(ChatViewPane),
canToggleVisibility: false, canToggleVisibility: false,
canMoveView: true, canMoveView: true,
order: 100 order: 100
}; };
viewsRegistry.registerViews([this._welcomeViewDescriptor], this._viewContainer); viewsRegistry.registerViews([this._welcomeViewDescriptor], this._viewContainer);
viewsRegistry.registerViewWelcomeContent(viewId, { viewsRegistry.registerViewWelcomeContent(CHAT_VIEW_ID, {
content: this.productService.chatWelcomeView.welcomeViewContent, content: this.productService.chatWelcomeView.welcomeViewContent,
}); });
} else if (this._welcomeViewDescriptor) { } else if (this._welcomeViewDescriptor) {
@ -220,29 +175,6 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution {
} }
private handleAndRegisterChatExtensions(): void { 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) => { chatParticipantExtensionPoint.setHandler((extensions, delta) => {
for (const extension of delta.added) { for (const extension of delta.added) {
for (const providerDescriptor of extension.value) { for (const providerDescriptor of extension.value) {
@ -261,9 +193,12 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution {
continue; continue;
} }
this._participantRegistrationDisposables.set( const store = new DisposableStore();
getParticipantKey(extension.description.identifier, providerDescriptor.name), if (providerDescriptor.isDefault) {
this._chatAgentService.registerAgent( store.add(this.registerDefaultParticipant(providerDescriptor));
}
store.add(this._chatAgentService.registerAgent(
providerDescriptor.id, providerDescriptor.id,
{ {
extensionId: extension.description.identifier, extensionId: extension.description.identifier,
@ -280,6 +215,11 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution {
[ChatAgentLocation.Panel], [ChatAgentLocation.Panel],
slashCommands: providerDescriptor.commands ?? [] slashCommands: providerDescriptor.commands ?? []
} satisfies IChatAgentData)); } satisfies IChatAgentData));
this._participantRegistrationDisposables.set(
getParticipantKey(extension.description.identifier, providerDescriptor.name),
store
);
} }
} }
@ -309,72 +249,26 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution {
return viewContainer; return viewContainer;
} }
private registerChatProvider(providerDescriptor: IRawChatProviderContribution): IDisposable { private registerDefaultParticipant(defaultParticipantDescriptor: IRawChatParticipantContribution): IDisposable {
// Register View // Register View
const viewId = this._chatContributionService.getViewIdForProvider(providerDescriptor.id);
const viewDescriptor: IViewDescriptor[] = [{ const viewDescriptor: IViewDescriptor[] = [{
id: viewId, id: CHAT_VIEW_ID,
containerIcon: this._viewContainer.icon, containerIcon: this._viewContainer.icon,
containerTitle: this._viewContainer.title.value, containerTitle: this._viewContainer.title.value,
singleViewPaneContainerTitle: 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, canToggleVisibility: false,
canMoveView: true, canMoveView: true,
ctorDescriptor: new SyncDescriptor(ChatViewPane, [<IChatViewOptions>{ providerId: providerDescriptor.id }]), ctorDescriptor: new SyncDescriptor(ChatViewPane),
when: ContextKeyExpr.deserialize(providerDescriptor.when)
}]; }];
Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry).registerViews(viewDescriptor, this._viewContainer); Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry).registerViews(viewDescriptor, this._viewContainer);
// Per-provider actions return toDisposable(() => {
// 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<IViewsRegistry>(ViewExtensions.ViewsRegistry).deregisterViews(viewDescriptor, this._viewContainer); Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry).deregisterViews(viewDescriptor, this._viewContainer);
Registry.as<IViewContainersRegistry>(ViewExtensions.ViewContainersRegistry).deregisterViewContainer(this._viewContainer); });
disposables.dispose();
}
};
} }
} }
registerWorkbenchContribution2(ChatExtensionPointHandler.ID, ChatExtensionPointHandler, WorkbenchPhase.BlockStartup);
function getParticipantKey(extensionId: ExtensionIdentifier, participantName: string): string { function getParticipantKey(extensionId: ExtensionIdentifier, participantName: string): string {
return `${extensionId.value}_${participantName}`; return `${extensionId.value}_${participantName}`;
} }
export class ChatContributionService implements IChatContributionService {
declare _serviceBrand: undefined;
private _registeredProviders = new Map<string, IChatProviderContribution>();
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());
}
}

View file

@ -17,13 +17,13 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle
import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
import { IQuickInputService, IQuickWidget } from 'vs/platform/quickinput/common/quickInput'; import { IQuickInputService, IQuickWidget } from 'vs/platform/quickinput/common/quickInput';
import { editorBackground, inputBackground, quickInputBackground, quickInputForeground } from 'vs/platform/theme/common/colorRegistry'; import { editorBackground, inputBackground, quickInputBackground, quickInputForeground } from 'vs/platform/theme/common/colorRegistry';
import { IChatWidgetService, IQuickChatService, IQuickChatOpenOptions } from 'vs/workbench/contrib/chat/browser/chat'; import { IQuickChatOpenOptions, IQuickChatService, showChatView } from 'vs/workbench/contrib/chat/browser/chat';
import { IChatViewOptions } from 'vs/workbench/contrib/chat/browser/chatViewPane';
import { ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget'; import { ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget';
import { ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents';
import { ChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; import { ChatModel } from 'vs/workbench/contrib/chat/common/chatModel';
import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes';
import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; 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 { export class QuickChatService extends Disposable implements IQuickChatService {
readonly _serviceBrand: undefined; readonly _serviceBrand: undefined;
@ -45,7 +45,7 @@ export class QuickChatService extends Disposable implements IQuickChatService {
} }
get enabled(): boolean { get enabled(): boolean {
return this.chatService.getProviderInfos().length > 0; return !!this.chatService.isEnabled(ChatAgentLocation.Panel);
} }
get focused(): boolean { get focused(): boolean {
@ -56,13 +56,13 @@ export class QuickChatService extends Disposable implements IQuickChatService {
return dom.isAncestorOfActiveElement(widget); 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 // 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. // pick. This should not happen when there is a query.
if (this.focused && !options?.query) { if (this.focused && !options?.query) {
this.close(); this.close();
} else { } else {
this.open(providerId, options); this.open(options);
// If this is a partial query, the value should be cleared when closed as otherwise it // 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. // would remain for the next time the quick chat is opened in any context.
if (options?.isPartialQuery) { 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._input) {
if (this._currentChat && options?.query) { if (this._currentChat && options?.query) {
this._currentChat.focus(); this._currentChat.focus();
@ -87,15 +87,6 @@ export class QuickChatService extends Disposable implements IQuickChatService {
return this.focus(); 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(); const disposableStore = new DisposableStore();
this._input = this.quickInputService.createQuickWidget(); this._input = this.quickInputService.createQuickWidget();
@ -108,9 +99,7 @@ export class QuickChatService extends Disposable implements IQuickChatService {
this._input.show(); this._input.show();
if (!this._currentChat) { if (!this._currentChat) {
this._currentChat = this.instantiationService.createInstance(QuickChat, { this._currentChat = this.instantiationService.createInstance(QuickChat);
providerId: providerInfo.id,
});
// show needs to come after the quickpick is shown // show needs to come after the quickpick is shown
this._currentChat.render(this._container); this._currentChat.render(this._container);
@ -160,12 +149,11 @@ class QuickChat extends Disposable {
private _deferUpdatingDynamicLayout: boolean = false; private _deferUpdatingDynamicLayout: boolean = false;
constructor( constructor(
private readonly _options: IChatViewOptions,
@IInstantiationService private readonly instantiationService: IInstantiationService, @IInstantiationService private readonly instantiationService: IInstantiationService,
@IContextKeyService private readonly contextKeyService: IContextKeyService, @IContextKeyService private readonly contextKeyService: IContextKeyService,
@IChatService private readonly chatService: IChatService, @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(); super();
} }
@ -288,7 +276,7 @@ class QuickChat extends Disposable {
} }
async openChatView(): Promise<void> { async openChatView(): Promise<void> {
const widget = await this._chatWidgetService.revealViewForProvider(this._options.providerId); const widget = await showChatView(this.viewsService);
if (!widget?.viewModel || !this.model) { if (!widget?.viewModel || !this.model) {
return; return;
} }
@ -325,7 +313,7 @@ class QuickChat extends Disposable {
} }
private updateModel(): void { private updateModel(): void {
this.model ??= this.chatService.startSession(this._options.providerId, CancellationToken.None); this.model ??= this.chatService.startSession(CancellationToken.None);
if (!this.model) { if (!this.model) {
throw new Error('Could not start chat session'); throw new Error('Could not start chat session');
} }

View file

@ -24,13 +24,11 @@ import { SIDE_BAR_FOREGROUND } from 'vs/workbench/common/theme';
import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IViewDescriptorService } from 'vs/workbench/common/views';
import { IChatViewPane } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatViewPane } from 'vs/workbench/contrib/chat/browser/chat';
import { IChatViewState, ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget'; import { IChatViewState, ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget';
import { ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatAgentLocation, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
import { IChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; 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'; import { IChatService } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatViewTitleActionContext } from 'vs/workbench/contrib/chat/browser/actions/chatActions';
export interface IChatViewOptions {
readonly providerId: string;
}
interface IViewPaneState extends IChatViewState { interface IViewPaneState extends IChatViewState {
sessionId?: string; sessionId?: string;
@ -38,8 +36,6 @@ interface IViewPaneState extends IChatViewState {
export const CHAT_SIDEBAR_PANEL_ID = 'workbench.panel.chatSidebar'; export const CHAT_SIDEBAR_PANEL_ID = 'workbench.panel.chatSidebar';
export class ChatViewPane extends ViewPane implements IChatViewPane { export class ChatViewPane extends ViewPane implements IChatViewPane {
static ID = 'workbench.panel.chat.view';
private _widget!: ChatWidget; private _widget!: ChatWidget;
get widget(): ChatWidget { return this._widget; } get widget(): ChatWidget { return this._widget; }
@ -50,7 +46,6 @@ export class ChatViewPane extends ViewPane implements IChatViewPane {
private didUnregisterProvider = false; private didUnregisterProvider = false;
constructor( constructor(
private readonly chatViewOptions: IChatViewOptions,
options: IViewPaneOptions, options: IViewPaneOptions,
@IKeybindingService keybindingService: IKeybindingService, @IKeybindingService keybindingService: IKeybindingService,
@IContextMenuService contextMenuService: IContextMenuService, @IContextMenuService contextMenuService: IContextMenuService,
@ -64,15 +59,17 @@ export class ChatViewPane extends ViewPane implements IChatViewPane {
@IHoverService hoverService: IHoverService, @IHoverService hoverService: IHoverService,
@IStorageService private readonly storageService: IStorageService, @IStorageService private readonly storageService: IStorageService,
@IChatService private readonly chatService: IChatService, @IChatService private readonly chatService: IChatService,
@IChatAgentService private readonly chatAgentService: IChatAgentService,
@ILogService private readonly logService: ILogService, @ILogService private readonly logService: ILogService,
) { ) {
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService, hoverService); 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. // 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.viewState = this.memento.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE) as IViewPaneState;
this._register(this.chatService.onDidRegisterProvider(({ providerId }) => { this._register(this.chatAgentService.onDidChangeAgents(() => {
if (providerId === this.chatViewOptions.providerId && !this._widget?.viewModel) { if (this.chatAgentService.getDefaultAgent(ChatAgentLocation.Panel)) {
if (!this._widget?.viewModel) {
const sessionId = this.getSessionId(); const sessionId = this.getSessionId();
const model = sessionId ? this.chatService.getOrRestoreSession(sessionId) : undefined; const model = sessionId ? this.chatService.getOrRestoreSession(sessionId) : undefined;
@ -89,21 +86,26 @@ export class ChatViewPane extends ViewPane implements IChatViewPane {
this.widget.setVisible(true); this.widget.setVisible(true);
} }
} }
})); } else if (this._widget?.viewModel?.initState === ChatModelInitState.Initialized) {
this._register(this.chatService.onDidUnregisterProvider(({ providerId }) => { // Model is initialized, and the default agent disappeared, so show welcome view
if (providerId === this.chatViewOptions.providerId) {
this.didUnregisterProvider = true; this.didUnregisterProvider = true;
this._onDidChangeViewWelcomeState.fire(); this._onDidChangeViewWelcomeState.fire();
} }
})); }));
} }
override getActionsContext(): IChatViewTitleActionContext {
return {
chatView: this
};
}
private updateModel(model?: IChatModel | undefined, viewState?: IViewPaneState): void { private updateModel(model?: IChatModel | undefined, viewState?: IViewPaneState): void {
this.modelDisposables.clear(); this.modelDisposables.clear();
model = model ?? (this.chatService.transferredSessionData?.sessionId model = model ?? (this.chatService.transferredSessionData?.sessionId
? this.chatService.getOrRestoreSession(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) { if (!model) {
throw new Error('Could not start chat session'); throw new Error('Could not start chat session');
} }
@ -113,7 +115,7 @@ export class ChatViewPane extends ViewPane implements IChatViewPane {
} }
override shouldShowWelcome(): boolean { 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); 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, // Render the welcome view if this session gets disposed at any point,
// including if the provider registration fails // including if the provider registration fails
const disposeListener = sessionId ? this._register(this.chatService.onDidDisposeSession((e) => { 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; this.didProviderRegistrationFail = true;
disposeListener?.dispose(); disposeListener?.dispose();
this._onDidChangeViewWelcomeState.fire(); this._onDidChangeViewWelcomeState.fire();
@ -174,7 +176,7 @@ export class ChatViewPane extends ViewPane implements IChatViewPane {
this._widget.acceptInput(query); this._widget.acceptInput(query);
} }
async clear(): Promise<void> { clear(): void {
if (this.widget.viewModel) { if (this.widget.viewModel) {
this.chatService.clearSession(this.widget.viewModel.sessionId); this.chatService.clearSession(this.widget.viewModel.sessionId);
} }

View file

@ -29,12 +29,9 @@ import { ChatTreeItem, IChatAccessibilityService, IChatCodeBlockInfo, IChatFileT
import { ChatAccessibilityProvider } from 'vs/workbench/contrib/chat/browser/chatAccessibilityProvider'; import { ChatAccessibilityProvider } from 'vs/workbench/contrib/chat/browser/chatAccessibilityProvider';
import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart'; import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart';
import { ChatListDelegate, ChatListItemRenderer, IChatRendererDelegate } from 'vs/workbench/contrib/chat/browser/chatListRenderer'; 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 { 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 { 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 { 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 { ChatModelInitState, IChatModel } from 'vs/workbench/contrib/chat/common/chatModel';
import { ChatRequestAgentPart, IParsedChatRequest, chatAgentLeader, chatSubcommandLeader, extractAgentAndCommand } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatRequestAgentPart, IParsedChatRequest, chatAgentLeader, chatSubcommandLeader, extractAgentAndCommand } from 'vs/workbench/contrib/chat/common/chatParserTypes';
import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; 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 { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands';
import { ChatViewModel, IChatResponseViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { ChatViewModel, IChatResponseViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel';
import { CodeBlockModelCollection } from 'vs/workbench/contrib/chat/common/codeBlockModelCollection'; import { CodeBlockModelCollection } from 'vs/workbench/contrib/chat/common/codeBlockModelCollection';
import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; import { IChatListItemRendererOptions } from './chat';
const $ = dom.$; const $ = dom.$;
@ -242,10 +239,6 @@ export class ChatWidget extends Disposable implements IChatWidget {
return !!this.viewOptions.supportsFileReferences; return !!this.viewOptions.supportsFileReferences;
} }
get providerId(): string {
return this.viewModel?.providerId || '';
}
get input(): ChatInputPart { get input(): ChatInputPart {
return this.inputPart; return this.inputPart;
} }
@ -563,7 +556,6 @@ export class ChatWidget extends Disposable implements IChatWidget {
} }
this.chatService.notifyUserAction({ this.chatService.notifyUserAction({
providerId: this.viewModel.providerId,
sessionId: this.viewModel.sessionId, sessionId: this.viewModel.sessionId,
requestId: e.response.requestId, requestId: e.response.requestId,
agentId: e.response.agent?.id, agentId: e.response.agent?.id,
@ -641,7 +633,7 @@ export class ChatWidget extends Disposable implements IChatWidget {
this.viewModel = undefined; this.viewModel = undefined;
this.onDidChangeItems(); this.onDidChangeItems();
})); }));
this.inputPart.setState(model.providerId, viewState.inputValue); this.inputPart.setState(viewState.inputValue);
this.contribs.forEach(c => { this.contribs.forEach(c => {
if (c.setInputState && viewState.inputState?.[c.id]) { if (c.setInputState && viewState.inputState?.[c.id]) {
c.setInputState(viewState.inputState?.[c.id]); c.setInputState(viewState.inputState?.[c.id]);
@ -914,10 +906,7 @@ export class ChatWidgetService implements IChatWidgetService {
return this._lastFocusedWidget; return this._lastFocusedWidget;
} }
constructor( constructor() { }
@IViewsService private readonly viewsService: IViewsService,
@IChatContributionService private readonly chatContributionService: IChatContributionService,
) { }
getWidgetByInputUri(uri: URI): ChatWidget | undefined { getWidgetByInputUri(uri: URI): ChatWidget | undefined {
return this._widgets.find(w => isEqual(w.inputUri, uri)); 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); return this._widgets.find(w => w.viewModel?.sessionId === sessionId);
} }
async revealViewForProvider(providerId: string): Promise<ChatWidget | undefined> {
const viewId = this.chatContributionService.getViewIdForProvider(providerId);
const view = await this.viewsService.openView<ChatViewPane>(viewId);
return view?.widget;
}
private setLastFocusedWidget(widget: ChatWidget | undefined): void { private setLastFocusedWidget(widget: ChatWidget | undefined): void {
if (widget === this._lastFocusedWidget) { if (widget === this._lastFocusedWidget) {
return; return;

View file

@ -11,11 +11,12 @@ import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { ThemeIcon } from 'vs/base/common/themables'; import { ThemeIcon } from 'vs/base/common/themables';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import { ProviderResult } from 'vs/editor/common/languages'; import { ProviderResult } from 'vs/editor/common/languages';
import { 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 { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; 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 { 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'; import { IChatFollowup, IChatProgress, IChatResponseErrorDetails } from 'vs/workbench/contrib/chat/common/chatService';
//#region agent service, commands etc //#region agent service, commands etc
@ -139,7 +140,16 @@ export interface IChatAgentService {
getAgents(): IChatAgentData[]; getAgents(): IChatAgentData[];
getActivatedAgents(): Array<IChatAgent>; getActivatedAgents(): Array<IChatAgent>;
getAgentsByName(name: string): IChatAgentData[]; getAgentsByName(name: string): IChatAgentData[];
/**
* Get the default agent (only if activated)
*/
getDefaultAgent(location: ChatAgentLocation): IChatAgent | undefined; 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; getSecondaryAgent(): IChatAgentData | undefined;
updateAgent(id: string, updateMetadata: IChatAgentMetadata): void; updateAgent(id: string, updateMetadata: IChatAgentMetadata): void;
} }
@ -155,9 +165,13 @@ export class ChatAgentService implements IChatAgentService {
private readonly _onDidChangeAgents = new Emitter<IChatAgent | undefined>(); private readonly _onDidChangeAgents = new Emitter<IChatAgent | undefined>();
readonly onDidChangeAgents: Event<IChatAgent | undefined> = this._onDidChangeAgents.event; readonly onDidChangeAgents: Event<IChatAgent | undefined> = this._onDidChangeAgents.event;
private readonly _hasDefaultAgent: IContextKey<boolean>;
constructor( constructor(
@IContextKeyService private readonly contextKeyService: IContextKeyService @IContextKeyService private readonly contextKeyService: IContextKeyService
) { } ) {
this._hasDefaultAgent = CONTEXT_HAS_DEFAULT_AGENT.bindTo(this.contextKeyService);
}
registerAgent(id: string, data: IChatAgentData): IDisposable { registerAgent(id: string, data: IChatAgentData): IDisposable {
const existingAgent = this.getAgent(id); const existingAgent = this.getAgent(id);
@ -191,12 +205,20 @@ export class ChatAgentService implements IChatAgentService {
throw new Error(`Agent already has implementation: ${JSON.stringify(id)}`); throw new Error(`Agent already has implementation: ${JSON.stringify(id)}`);
} }
if (entry.data.isDefault) {
this._hasDefaultAgent.set(true);
}
entry.impl = agentImpl; entry.impl = agentImpl;
this._onDidChangeAgents.fire(new MergedChatAgent(entry.data, agentImpl)); this._onDidChangeAgents.fire(new MergedChatAgent(entry.data, agentImpl));
return toDisposable(() => { return toDisposable(() => {
entry.impl = undefined; entry.impl = undefined;
this._onDidChangeAgents.fire(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)); 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 { getSecondaryAgent(): IChatAgentData | undefined {
// TODO also static // TODO also static
return Iterable.find(this._agents.values(), a => !!a.data.metadata.isSecondary)?.data; return Iterable.find(this._agents.values(), a => !!a.data.metadata.isSecondary)?.data;

View file

@ -21,7 +21,8 @@ export const CONTEXT_CHAT_INPUT_HAS_FOCUS = new RawContextKey<boolean>('chatInpu
export const CONTEXT_IN_CHAT_INPUT = new RawContextKey<boolean>('inChatInput', false, { type: 'boolean', description: localize('inInteractiveInput', "True when focus is in the chat input, false otherwise.") }); export const CONTEXT_IN_CHAT_INPUT = new RawContextKey<boolean>('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<boolean>('inChat', false, { type: 'boolean', description: localize('inChat', "True when focus is in the chat widget, false otherwise.") }); export const CONTEXT_IN_CHAT_SESSION = new RawContextKey<boolean>('inChat', false, { type: 'boolean', description: localize('inChat', "True when focus is in the chat widget, false otherwise.") });
export const CONTEXT_PROVIDER_EXISTS = new RawContextKey<boolean>('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<boolean>('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<boolean>('chatCursorAtTop', false); export const CONTEXT_CHAT_INPUT_CURSOR_AT_TOP = new RawContextKey<boolean>('chatCursorAtTop', false);
export const CONTEXT_CHAT_INPUT_HAS_AGENT = new RawContextKey<boolean>('chatInputHasAgent', false); export const CONTEXT_CHAT_INPUT_HAS_AGENT = new RawContextKey<boolean>('chatInputHasAgent', false);
export const CONTEXT_CHAT_LOCATION = new RawContextKey<ChatAgentLocation>('chatLocation', undefined); export const CONTEXT_CHAT_LOCATION = new RawContextKey<ChatAgentLocation>('chatLocation', undefined);

View file

@ -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>('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;
}

View file

@ -18,7 +18,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { ILogService } from 'vs/platform/log/common/log'; import { ILogService } from 'vs/platform/log/common/log';
import { ChatAgentLocation, IChatAgentCommand, IChatAgentData, IChatAgentHistoryEntry, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; 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 { 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'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables';
export interface IChatPromptVariableData { export interface IChatPromptVariableData {
@ -65,7 +65,6 @@ export interface IResponse {
export interface IChatResponseModel { export interface IChatResponseModel {
readonly onDidChange: Event<void>; readonly onDidChange: Event<void>;
readonly id: string; readonly id: string;
readonly providerId: string;
readonly requestId: string; readonly requestId: string;
readonly username: string; readonly username: string;
readonly avatarIcon?: ThemeIcon | URI; readonly avatarIcon?: ThemeIcon | URI;
@ -260,10 +259,6 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel
return this._result; return this._result;
} }
public get providerId(): string {
return this.session.providerId;
}
public get username(): string { public get username(): string {
return this.session.responderUsername; return this.session.responderUsername;
} }
@ -391,7 +386,6 @@ export interface IChatModel {
readonly onDidDispose: Event<void>; readonly onDidDispose: Event<void>;
readonly onDidChange: Event<IChatChangeEvent>; readonly onDidChange: Event<IChatChangeEvent>;
readonly sessionId: string; readonly sessionId: string;
readonly providerId: string;
readonly initState: ChatModelInitState; readonly initState: ChatModelInitState;
readonly title: string; readonly title: string;
readonly welcomeMessage: IChatWelcomeMessageModel | undefined; readonly welcomeMessage: IChatWelcomeMessageModel | undefined;
@ -426,7 +420,6 @@ export interface ISerializableChatRequestData {
} }
export interface IExportableChatData { export interface IExportableChatData {
providerId: string;
welcomeMessage: (string | IChatFollowup[])[] | undefined; welcomeMessage: (string | IChatFollowup[])[] | undefined;
requests: ISerializableChatRequestData[]; requests: ISerializableChatRequestData[];
requesterUsername: string; requesterUsername: string;
@ -444,7 +437,6 @@ export interface ISerializableChatData extends IExportableChatData {
export function isExportableSessionData(obj: unknown): obj is IExportableChatData { export function isExportableSessionData(obj: unknown): obj is IExportableChatData {
const data = obj as IExportableChatData; const data = obj as IExportableChatData;
return typeof data === 'object' && return typeof data === 'object' &&
typeof data.providerId === 'string' &&
typeof data.requesterUsername === 'string'; typeof data.requesterUsername === 'string';
} }
@ -505,11 +497,6 @@ export class ChatModel extends Disposable implements IChatModel {
private _initState: ChatModelInitState = ChatModelInitState.Created; private _initState: ChatModelInitState = ChatModelInitState.Created;
private _isInitializedDeferred = new DeferredPromise<void>(); private _isInitializedDeferred = new DeferredPromise<void>();
private _session: IChat | undefined;
get session(): IChat | undefined {
return this._session;
}
private _welcomeMessage: ChatWelcomeMessageModel | undefined; private _welcomeMessage: ChatWelcomeMessageModel | undefined;
get welcomeMessage(): ChatWelcomeMessageModel | undefined { get welcomeMessage(): ChatWelcomeMessageModel | undefined {
return this._welcomeMessage; return this._welcomeMessage;
@ -580,7 +567,6 @@ export class ChatModel extends Disposable implements IChatModel {
} }
constructor( constructor(
public readonly providerId: string,
private readonly initialData: ISerializableChatData | IExportableChatData | undefined, private readonly initialData: ISerializableChatData | IExportableChatData | undefined,
@ILogService private readonly logService: ILogService, @ILogService private readonly logService: ILogService,
@IChatAgentService private readonly chatAgentService: IChatAgentService, @IChatAgentService private readonly chatAgentService: IChatAgentService,
@ -672,19 +658,17 @@ export class ChatModel extends Disposable implements IChatModel {
} }
deinitialize(): void { deinitialize(): void {
this._session = undefined;
this._initState = ChatModelInitState.Created; this._initState = ChatModelInitState.Created;
this._isInitializedDeferred = new DeferredPromise<void>(); this._isInitializedDeferred = new DeferredPromise<void>();
} }
initialize(session: IChat, welcomeMessage: ChatWelcomeMessageModel | undefined): void { initialize(welcomeMessage: ChatWelcomeMessageModel | undefined): void {
if (this.initState !== ChatModelInitState.Initializing) { if (this.initState !== ChatModelInitState.Initializing) {
// Must call startInitialize before initialize, and only call it once // Must call startInitialize before initialize, and only call it once
throw new Error(`ChatModel is in the wrong state for initialize: ${ChatModelInitState[this.initState]}`); throw new Error(`ChatModel is in the wrong state for initialize: ${ChatModelInitState[this.initState]}`);
} }
this._initState = ChatModelInitState.Initialized; this._initState = ChatModelInitState.Initialized;
this._session = session;
if (!this._welcomeMessage) { if (!this._welcomeMessage) {
// Could also have loaded the welcome message from persisted data // Could also have loaded the welcome message from persisted data
this._welcomeMessage = welcomeMessage; this._welcomeMessage = welcomeMessage;
@ -713,10 +697,6 @@ export class ChatModel extends Disposable implements IChatModel {
} }
addRequest(message: IParsedChatRequest, variableData: IChatRequestVariableData, chatAgent?: IChatAgentData, slashCommand?: IChatAgentCommand): ChatRequestModel { 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); const request = new ChatRequestModel(this, message, variableData);
request.response = new ChatResponseModel([], this, chatAgent, slashCommand, request.id); 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 { acceptResponseProgress(request: ChatRequestModel, progress: IChatProgress, quiet?: boolean): void {
if (!this._session) {
throw new Error('acceptResponseProgress: No session');
}
if (!request.response) { if (!request.response) {
request.response = new ChatResponseModel([], this, undefined, undefined, request.id); 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 { setResponse(request: ChatRequestModel, result: IChatAgentResult): void {
if (!this._session) {
throw new Error('completeResponse: No session');
}
if (!request.response) { if (!request.response) {
request.response = new ChatResponseModel([], this, undefined, undefined, request.id); request.response = new ChatResponseModel([], this, undefined, undefined, request.id);
} }
@ -851,7 +823,6 @@ export class ChatModel extends Disposable implements IChatModel {
contentReferences: r.response?.contentReferences contentReferences: r.response?.contentReferences
}; };
}), }),
providerId: this.providerId,
}; };
} }
@ -865,7 +836,6 @@ export class ChatModel extends Disposable implements IChatModel {
} }
override dispose() { override dispose() {
this._session?.dispose?.();
this._requests.forEach(r => r.response?.dispose()); this._requests.forEach(r => r.response?.dispose());
this._onDidDispose.fire(); this._onDidDispose.fire();

View file

@ -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';

View file

@ -6,25 +6,18 @@
import { CancellationToken } from 'vs/base/common/cancellation'; import { CancellationToken } from 'vs/base/common/cancellation';
import { Event } from 'vs/base/common/event'; import { Event } from 'vs/base/common/event';
import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IMarkdownString } from 'vs/base/common/htmlContent';
import { IDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import { IRange, Range } from 'vs/editor/common/core/range'; 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 { FileType } from 'vs/platform/files/common/files';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ChatAgentLocation, IChatAgentCommand, IChatAgentData, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; 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 { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes';
import { IChatParserContext } from 'vs/workbench/contrib/chat/common/chatRequestParser'; import { IChatParserContext } from 'vs/workbench/contrib/chat/common/chatRequestParser';
import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; 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 { export interface IChatRequest {
session: IChat;
message: string; message: string;
variables: Record<string, IChatRequestVariableValue[]>; variables: Record<string, IChatRequestVariableValue[]>;
} }
@ -159,11 +152,6 @@ export type IChatProgress =
| IChatCommandButton | IChatCommandButton
| IChatTextEdit; | IChatTextEdit;
export interface IChatProvider {
readonly id: string;
prepareSession(token: CancellationToken): ProviderResult<IChat | undefined>;
}
export interface IChatFollowup { export interface IChatFollowup {
kind: 'reply'; kind: 'reply';
message: string; message: string;
@ -231,7 +219,6 @@ export type ChatUserAction = IChatVoteAction | IChatCopyAction | IChatInsertActi
export interface IChatUserActionEvent { export interface IChatUserActionEvent {
action: ChatUserAction; action: ChatUserAction;
providerId: string;
agentId: string | undefined; agentId: string | undefined;
sessionId: string; sessionId: string;
requestId: string; requestId: string;
@ -282,16 +269,12 @@ export interface IChatService {
_serviceBrand: undefined; _serviceBrand: undefined;
transferredSessionData: IChatTransferredSessionData | undefined; transferredSessionData: IChatTransferredSessionData | undefined;
onDidRegisterProvider: Event<{ providerId: string }>; isEnabled(location: ChatAgentLocation): boolean;
onDidUnregisterProvider: Event<{ providerId: string }>; hasSessions(): boolean;
registerProvider(provider: IChatProvider): IDisposable; startSession(token: CancellationToken): ChatModel | undefined;
hasSessions(providerId: string): boolean;
getProviderInfos(): IChatProviderInfo[];
startSession(providerId: string, token: CancellationToken): ChatModel | undefined;
getSession(sessionId: string): IChatModel | undefined; getSession(sessionId: string): IChatModel | undefined;
getSessionId(sessionProviderId: number): string | undefined;
getOrRestoreSession(sessionId: string): IChatModel | undefined; getOrRestoreSession(sessionId: string): IChatModel | undefined;
loadSessionFromContent(data: ISerializableChatData): IChatModel | undefined; loadSessionFromContent(data: IExportableChatData | ISerializableChatData): IChatModel | undefined;
/** /**
* Returns whether the request was accepted. * Returns whether the request was accepted.
@ -307,7 +290,7 @@ export interface IChatService {
onDidPerformUserAction: Event<IChatUserActionEvent>; onDidPerformUserAction: Event<IChatUserActionEvent>;
notifyUserAction(event: IChatUserActionEvent): void; 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; transferChatSession(transferredSessionData: IChatTransferredSessionData, toWorkspace: URI): void;
} }

View file

@ -8,13 +8,12 @@ import { ErrorNoTelemetry } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event'; import { Emitter, Event } from 'vs/base/common/event';
import { MarkdownString } from 'vs/base/common/htmlContent'; import { MarkdownString } from 'vs/base/common/htmlContent';
import { Iterable } from 'vs/base/common/iterator'; 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 { revive } from 'vs/base/common/marshalling';
import { StopWatch } from 'vs/base/common/stopwatch'; import { StopWatch } from 'vs/base/common/stopwatch';
import { URI, UriComponents } from 'vs/base/common/uri'; import { URI, UriComponents } from 'vs/base/common/uri';
import { localize } from 'vs/nls'; import { localize } from 'vs/nls';
import { CommandsRegistry } from 'vs/platform/commands/common/commands'; 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 { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log'; import { ILogService } from 'vs/platform/log/common/log';
import { Progress } from 'vs/platform/progress/common/progress'; 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 { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { ChatAgentLocation, IChatAgent, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; 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, ChatRequestModel, ChatWelcomeMessageModel, IChatModel, IChatRequestVariableData, IChatRequestVariableEntry, IExportableChatData, ISerializableChatData, ISerializableChatsData, getHistoryEntriesFromModel, updateRanges } from 'vs/workbench/contrib/chat/common/chatModel';
import { ChatModel, ChatModelInitState, ChatRequestModel, ChatWelcomeMessageModel, IChatModel, IChatRequestVariableData, IChatRequestVariableEntry, 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 { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, IParsedChatRequest, getPromptText } from 'vs/workbench/contrib/chat/common/chatParserTypes';
import { ChatRequestParser, IChatParserContext } from 'vs/workbench/contrib/chat/common/chatRequestParser'; 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 { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands';
import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables';
import { ChatMessageRole, IChatMessage } from 'vs/workbench/contrib/chat/common/languageModels'; import { ChatMessageRole, IChatMessage } from 'vs/workbench/contrib/chat/common/languageModels';
@ -44,7 +42,6 @@ interface IChatTransfer {
const SESSION_TRANSFER_EXPIRATION_IN_MILLISECONDS = 1000 * 60; const SESSION_TRANSFER_EXPIRATION_IN_MILLISECONDS = 1000 * 60;
type ChatProviderInvokedEvent = { type ChatProviderInvokedEvent = {
providerId: string;
timeToFirstProgress: number | undefined; timeToFirstProgress: number | undefined;
totalTime: number | undefined; totalTime: number | undefined;
result: 'success' | 'error' | 'errorWithOutput' | 'cancelled' | 'filtered'; result: 'success' | 'error' | 'errorWithOutput' | 'cancelled' | 'filtered';
@ -55,7 +52,6 @@ type ChatProviderInvokedEvent = {
}; };
type ChatProviderInvokedClassification = { 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.' }; 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`.' }; 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.' }; result: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether invoking the ChatProvider resulted in an error.' };
@ -68,60 +64,50 @@ type ChatProviderInvokedClassification = {
}; };
type ChatVoteEvent = { type ChatVoteEvent = {
providerId: string;
direction: 'up' | 'down'; direction: 'up' | 'down';
}; };
type ChatVoteClassification = { 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.' }; direction: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the user voted up or down.' };
owner: 'roblourens'; owner: 'roblourens';
comment: 'Provides insight into the performance of Chat providers.'; comment: 'Provides insight into the performance of Chat providers.';
}; };
type ChatCopyEvent = { type ChatCopyEvent = {
providerId: string;
copyKind: 'action' | 'toolbar'; copyKind: 'action' | 'toolbar';
}; };
type ChatCopyClassification = { 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.' }; copyKind: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'How the copy was initiated.' };
owner: 'roblourens'; owner: 'roblourens';
comment: 'Provides insight into the usage of Chat features.'; comment: 'Provides insight into the usage of Chat features.';
}; };
type ChatInsertEvent = { type ChatInsertEvent = {
providerId: string;
newFile: boolean; newFile: boolean;
}; };
type ChatInsertClassification = { 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.' }; newFile: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the code was inserted into a new untitled file.' };
owner: 'roblourens'; owner: 'roblourens';
comment: 'Provides insight into the usage of Chat features.'; comment: 'Provides insight into the usage of Chat features.';
}; };
type ChatCommandEvent = { type ChatCommandEvent = {
providerId: string;
commandId: string; commandId: string;
}; };
type ChatCommandClassification = { 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.' }; commandId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The id of the command that was executed.' };
owner: 'roblourens'; owner: 'roblourens';
comment: 'Provides insight into the usage of Chat features.'; comment: 'Provides insight into the usage of Chat features.';
}; };
type ChatTerminalEvent = { type ChatTerminalEvent = {
providerId: string;
languageId: string; languageId: string;
}; };
type ChatTerminalClassification = { 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.' }; languageId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The language of the code that was run in the terminal.' };
owner: 'roblourens'; owner: 'roblourens';
comment: 'Provides insight into the usage of Chat features.'; comment: 'Provides insight into the usage of Chat features.';
@ -132,12 +118,10 @@ const maxPersistedSessions = 25;
export class ChatService extends Disposable implements IChatService { export class ChatService extends Disposable implements IChatService {
declare _serviceBrand: undefined; declare _serviceBrand: undefined;
private readonly _providers = new Map<string, IChatProvider>();
private readonly _sessionModels = this._register(new DisposableMap<string, ChatModel>()); private readonly _sessionModels = this._register(new DisposableMap<string, ChatModel>());
private readonly _pendingRequests = this._register(new DisposableMap<string, CancellationTokenSource>()); private readonly _pendingRequests = this._register(new DisposableMap<string, CancellationTokenSource>());
private _persistedSessions: ISerializableChatsData; private _persistedSessions: ISerializableChatsData;
private readonly _hasProvider: IContextKey<boolean>;
private _transferredSessionData: IChatTransferredSessionData | undefined; private _transferredSessionData: IChatTransferredSessionData | undefined;
public get 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<IChatUserActionEvent>()); private readonly _onDidPerformUserAction = this._register(new Emitter<IChatUserActionEvent>());
public readonly onDidPerformUserAction: Event<IChatUserActionEvent> = this._onDidPerformUserAction.event; public readonly onDidPerformUserAction: Event<IChatUserActionEvent> = 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; 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<string, CancellationTokenSource>()); private readonly _sessionFollowupCancelTokens = this._register(new DisposableMap<string, CancellationTokenSource>());
constructor( constructor(
@ -164,7 +142,6 @@ export class ChatService extends Disposable implements IChatService {
@IExtensionService private readonly extensionService: IExtensionService, @IExtensionService private readonly extensionService: IExtensionService,
@IInstantiationService private readonly instantiationService: IInstantiationService, @IInstantiationService private readonly instantiationService: IInstantiationService,
@ITelemetryService private readonly telemetryService: ITelemetryService, @ITelemetryService private readonly telemetryService: ITelemetryService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
@IChatSlashCommandService private readonly chatSlashCommandService: IChatSlashCommandService, @IChatSlashCommandService private readonly chatSlashCommandService: IChatSlashCommandService,
@IChatVariablesService private readonly chatVariablesService: IChatVariablesService, @IChatVariablesService private readonly chatVariablesService: IChatVariablesService,
@ -172,8 +149,6 @@ export class ChatService extends Disposable implements IChatService {
) { ) {
super(); super();
this._hasProvider = CONTEXT_PROVIDER_EXISTS.bindTo(this.contextKeyService);
const sessionData = storageService.get(serializedChatKey, StorageScope.WORKSPACE, ''); const sessionData = storageService.get(serializedChatKey, StorageScope.WORKSPACE, '');
if (sessionData) { if (sessionData) {
this._persistedSessions = this.deserializeChats(sessionData); this._persistedSessions = this.deserializeChats(sessionData);
@ -196,6 +171,10 @@ export class ChatService extends Disposable implements IChatService {
this._register(storageService.onWillSaveState(() => this.saveState())); this._register(storageService.onWillSaveState(() => this.saveState()));
} }
isEnabled(location: ChatAgentLocation): boolean {
return this.chatAgentService.getContributedDefaultAgent(location) !== undefined;
}
private saveState(): void { private saveState(): void {
let allSessions: (ChatModel | ISerializableChatData)[] = Array.from(this._sessionModels.values()) let allSessions: (ChatModel | ISerializableChatData)[] = Array.from(this._sessionModels.values())
.filter(session => session.getRequests().length > 0); .filter(session => session.getRequests().length > 0);
@ -221,29 +200,24 @@ export class ChatService extends Disposable implements IChatService {
notifyUserAction(action: IChatUserActionEvent): void { notifyUserAction(action: IChatUserActionEvent): void {
if (action.action.kind === 'vote') { if (action.action.kind === 'vote') {
this.telemetryService.publicLog2<ChatVoteEvent, ChatVoteClassification>('interactiveSessionVote', { this.telemetryService.publicLog2<ChatVoteEvent, ChatVoteClassification>('interactiveSessionVote', {
providerId: action.providerId,
direction: action.action.direction === InteractiveSessionVoteDirection.Up ? 'up' : 'down' direction: action.action.direction === InteractiveSessionVoteDirection.Up ? 'up' : 'down'
}); });
} else if (action.action.kind === 'copy') { } else if (action.action.kind === 'copy') {
this.telemetryService.publicLog2<ChatCopyEvent, ChatCopyClassification>('interactiveSessionCopy', { this.telemetryService.publicLog2<ChatCopyEvent, ChatCopyClassification>('interactiveSessionCopy', {
providerId: action.providerId,
copyKind: action.action.copyKind === ChatCopyKind.Action ? 'action' : 'toolbar' copyKind: action.action.copyKind === ChatCopyKind.Action ? 'action' : 'toolbar'
}); });
} else if (action.action.kind === 'insert') { } else if (action.action.kind === 'insert') {
this.telemetryService.publicLog2<ChatInsertEvent, ChatInsertClassification>('interactiveSessionInsert', { this.telemetryService.publicLog2<ChatInsertEvent, ChatInsertClassification>('interactiveSessionInsert', {
providerId: action.providerId,
newFile: !!action.action.newFile newFile: !!action.action.newFile
}); });
} else if (action.action.kind === 'command') { } else if (action.action.kind === 'command') {
const command = CommandsRegistry.getCommand(action.action.commandButton.command.id); const command = CommandsRegistry.getCommand(action.action.commandButton.command.id);
const commandId = command ? action.action.commandButton.command.id : 'INVALID'; const commandId = command ? action.action.commandButton.command.id : 'INVALID';
this.telemetryService.publicLog2<ChatCommandEvent, ChatCommandClassification>('interactiveSessionCommand', { this.telemetryService.publicLog2<ChatCommandEvent, ChatCommandClassification>('interactiveSessionCommand', {
providerId: action.providerId,
commandId commandId
}); });
} else if (action.action.kind === 'runInTerminal') { } else if (action.action.kind === 'runInTerminal') {
this.telemetryService.publicLog2<ChatTerminalEvent, ChatTerminalClassification>('interactiveSessionRunInTerminal', { this.telemetryService.publicLog2<ChatTerminalEvent, ChatTerminalClassification>('interactiveSessionRunInTerminal', {
providerId: action.providerId,
languageId: action.action.languageId ?? '' languageId: action.action.languageId ?? ''
}); });
} }
@ -251,8 +225,12 @@ export class ChatService extends Disposable implements IChatService {
this._onDidPerformUserAction.fire(action); this._onDidPerformUserAction.fire(action);
} }
private trace(method: string, message: string): void { private trace(method: string, message?: string): void {
if (message) {
this.logService.trace(`ChatService#${method}: ${message}`); this.logService.trace(`ChatService#${method}: ${message}`);
} else {
this.logService.trace(`ChatService#${method}`);
}
} }
private error(method: string, message: string): void { private error(method: string, message: string): void {
@ -341,53 +319,42 @@ export class ChatService extends Disposable implements IChatService {
this.saveState(); this.saveState();
} }
startSession(providerId: string, token: CancellationToken): ChatModel { startSession(token: CancellationToken): ChatModel {
this.trace('startSession', `providerId=${providerId}`); this.trace('startSession');
return this._startSession(providerId, undefined, token); return this._startSession(undefined, token);
} }
private _startSession(providerId: string, someSessionHistory: ISerializableChatData | undefined, token: CancellationToken): ChatModel { private _startSession(someSessionHistory: IExportableChatData | ISerializableChatData | undefined, token: CancellationToken): ChatModel {
this.trace('_startSession', `providerId=${providerId}`); const model = this.instantiationService.createInstance(ChatModel, someSessionHistory);
const model = this.instantiationService.createInstance(ChatModel, providerId, someSessionHistory);
this._sessionModels.set(model.sessionId, model); this._sessionModels.set(model.sessionId, model);
this.initializeSession(model, token); this.initializeSession(model, token);
return model; return model;
} }
private reinitializeModel(model: ChatModel): void { // TODO, when default agent shows up?
this.trace('reinitializeModel', `Start reinit`); // private reinitializeModel(model: ChatModel): void {
this.initializeSession(model, CancellationToken.None); // this.trace('reinitializeModel', `Start reinit`);
} // this.initializeSession(model, CancellationToken.None);
// }
private async initializeSession(model: ChatModel, token: CancellationToken): Promise<void> { private async initializeSession(model: ChatModel, token: CancellationToken): Promise<void> {
try { try {
this.trace('initializeSession', `Initialize session ${model.sessionId}`); this.trace('initializeSession', `Initialize session ${model.sessionId}`);
model.startInitialize(); model.startInitialize();
await this.extensionService.activateByEvent(`onInteractiveSession:${model.providerId}`);
const provider = this._providers.get(model.providerId); await this.extensionService.whenInstalledExtensionsRegistered();
if (!provider) { const defaultAgentData = this.chatAgentService.getContributedDefaultAgent(ChatAgentLocation.Panel);
throw new ErrorNoTelemetry(`Unknown provider: ${model.providerId}`); if (!defaultAgentData) {
throw new ErrorNoTelemetry('No default agent contributed');
} }
let session: IChat | undefined; await this.extensionService.activateByEvent(`onChatParticipant:${defaultAgentData.id}`);
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`);
const defaultAgent = this.chatAgentService.getDefaultAgent(ChatAgentLocation.Panel); const defaultAgent = this.chatAgentService.getDefaultAgent(ChatAgentLocation.Panel);
if (!defaultAgent) { 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 welcomeMessage = model.welcomeMessage ? undefined : await defaultAgent.provideWelcomeMessage?.(token) ?? undefined;
const welcomeModel = welcomeMessage && this.instantiationService.createInstance( const welcomeModel = welcomeMessage && this.instantiationService.createInstance(
ChatWelcomeMessageModel, ChatWelcomeMessageModel,
@ -395,12 +362,12 @@ export class ChatService extends Disposable implements IChatService {
await defaultAgent.provideSampleQuestions?.(token) ?? [] await defaultAgent.provideSampleQuestions?.(token) ?? []
); );
model.initialize(session, welcomeModel); model.initialize(welcomeModel);
} catch (err) { } catch (err) {
this.trace('startSession', `initializeSession failed: ${err}`); this.trace('startSession', `initializeSession failed: ${err}`);
model.setInitializationError(err); model.setInitializationError(err);
this._sessionModels.deleteAndDispose(model.sessionId); 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); 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 { getOrRestoreSession(sessionId: string): ChatModel | undefined {
this.trace('getOrRestoreSession', `sessionId: ${sessionId}`); this.trace('getOrRestoreSession', `sessionId: ${sessionId}`);
const model = this._sessionModels.get(sessionId); const model = this._sessionModels.get(sessionId);
@ -428,11 +391,11 @@ export class ChatService extends Disposable implements IChatService {
this._transferredSessionData = undefined; this._transferredSessionData = undefined;
} }
return this._startSession(sessionData.providerId, sessionData, CancellationToken.None); return this._startSession(sessionData, CancellationToken.None);
} }
loadSessionFromContent(data: ISerializableChatData): IChatModel | undefined { loadSessionFromContent(data: IExportableChatData | ISerializableChatData): IChatModel | undefined {
return this._startSession(data.providerId, data, CancellationToken.None); return this._startSession(data, CancellationToken.None);
} }
async sendRequest(sessionId: string, request: string, implicitVariablesEnabled?: boolean, location: ChatAgentLocation = ChatAgentLocation.Panel, parserContext?: IChatParserContext): Promise<IChatSendRequestData | undefined> { async sendRequest(sessionId: string, request: string, implicitVariablesEnabled?: boolean, location: ChatAgentLocation = ChatAgentLocation.Panel, parserContext?: IChatParserContext): Promise<IChatSendRequestData | undefined> {
@ -448,10 +411,6 @@ export class ChatService extends Disposable implements IChatService {
} }
await model.waitForInitialization(); await model.waitForInitialization();
const provider = this._providers.get(model.providerId);
if (!provider) {
throw new Error(`Unknown provider: ${model.providerId}`);
}
if (this._pendingRequests.has(sessionId)) { if (this._pendingRequests.has(sessionId)) {
this.trace('sendRequest', `Session ${sessionId} already has a pending request`); 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 // This method is only returning whether the request was accepted - don't block on the actual request
return { return {
responseCompletePromise: this._sendRequestAsync(model, sessionId, provider, parsedRequest, implicitVariablesEnabled ?? false, defaultAgent, location), responseCompletePromise: this._sendRequestAsync(model, sessionId, parsedRequest, implicitVariablesEnabled ?? false, defaultAgent, location),
agent, agent,
slashCommand: agentSlashCommandPart?.command, slashCommand: agentSlashCommandPart?.command,
}; };
@ -480,7 +439,7 @@ export class ChatService extends Disposable implements IChatService {
return newTokenSource.token; return newTokenSource.token;
} }
private async _sendRequestAsync(model: ChatModel, sessionId: string, provider: IChatProvider, parsedRequest: IParsedChatRequest, implicitVariablesEnabled: boolean, defaultAgent: IChatAgent, location: ChatAgentLocation): Promise<void> { private async _sendRequestAsync(model: ChatModel, sessionId: string, parsedRequest: IParsedChatRequest, implicitVariablesEnabled: boolean, defaultAgent: IChatAgent, location: ChatAgentLocation): Promise<void> {
const followupsCancelToken = this.refreshFollowupsCancellationToken(sessionId); const followupsCancelToken = this.refreshFollowupsCancellationToken(sessionId);
let request: ChatRequestModel; let request: ChatRequestModel;
const agentPart = 'kind' in parsedRequest ? undefined : parsedRequest.parts.find((r): r is ChatRequestAgentPart => r instanceof ChatRequestAgentPart); 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(() => { const listener = token.onCancellationRequested(() => {
this.trace('sendRequest', `Request for session ${model.sessionId} was cancelled`); this.trace('sendRequest', `Request for session ${model.sessionId} was cancelled`);
this.telemetryService.publicLog2<ChatProviderInvokedEvent, ChatProviderInvokedClassification>('interactiveSessionProviderInvoked', { this.telemetryService.publicLog2<ChatProviderInvokedEvent, ChatProviderInvokedClassification>('interactiveSessionProviderInvoked', {
providerId: provider.id,
timeToFirstProgress: undefined, timeToFirstProgress: undefined,
// Normally timings happen inside the EH around the actual provider. For cancellation we can measure how long the user waited before cancelling // 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(), totalTime: stopWatch.elapsed(),
@ -600,7 +558,6 @@ export class ChatService extends Disposable implements IChatService {
rawResult.errorDetails ? 'error' : rawResult.errorDetails ? 'error' :
'success'; 'success';
this.telemetryService.publicLog2<ChatProviderInvokedEvent, ChatProviderInvokedClassification>('interactiveSessionProviderInvoked', { this.telemetryService.publicLog2<ChatProviderInvokedEvent, ChatProviderInvokedClassification>('interactiveSessionProviderInvoked', {
providerId: provider.id,
timeToFirstProgress: rawResult.timings?.firstProgress, timeToFirstProgress: rawResult.timings?.firstProgress,
totalTime: rawResult.timings?.totalElapsed, totalTime: rawResult.timings?.totalElapsed,
result, result,
@ -638,18 +595,10 @@ export class ChatService extends Disposable implements IChatService {
} }
await model.waitForInitialization(); await model.waitForInitialization();
const provider = this._providers.get(model.providerId);
if (!provider) {
throw new Error(`Unknown provider: ${model.providerId}`);
}
model.removeRequest(requestId); model.removeRequest(requestId);
} }
getProviders(): string[] {
return Array.from(this._providers.keys());
}
async addCompleteRequest(sessionId: string, message: IParsedChatRequest | string, variableData: IChatRequestVariableData | undefined, response: IChatCompleteResponse): Promise<void> { async addCompleteRequest(sessionId: string, message: IParsedChatRequest | string, variableData: IChatRequestVariableData | undefined, response: IChatCompleteResponse): Promise<void> {
this.trace('addCompleteRequest', `message: ${message}`); this.trace('addCompleteRequest', `message: ${message}`);
@ -695,47 +644,11 @@ export class ChatService extends Disposable implements IChatService {
this._sessionModels.deleteAndDispose(sessionId); this._sessionModels.deleteAndDispose(sessionId);
this._pendingRequests.get(sessionId)?.cancel(); this._pendingRequests.get(sessionId)?.cancel();
this._pendingRequests.deleteAndDispose(sessionId); this._pendingRequests.deleteAndDispose(sessionId);
this._onDidDisposeSession.fire({ sessionId, providerId: model.providerId, reason: 'cleared' }); this._onDidDisposeSession.fire({ sessionId, reason: 'cleared' });
} }
registerProvider(provider: IChatProvider): IDisposable { public hasSessions(): boolean {
this.trace('registerProvider', `Adding new chat provider`); return !!Object.values(this._persistedSessions);
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,
};
});
} }
transferChatSession(transferredSessionData: IChatTransferredSessionData, toWorkspace: URI): void { transferChatSession(transferredSessionData: IChatTransferredSessionData, toWorkspace: URI): void {

View file

@ -47,7 +47,6 @@ export interface IChatSessionInitEvent {
export interface IChatViewModel { export interface IChatViewModel {
readonly model: IChatModel; readonly model: IChatModel;
readonly initState: ChatModelInitState; readonly initState: ChatModelInitState;
readonly providerId: string;
readonly sessionId: string; readonly sessionId: string;
readonly onDidDisposeModel: Event<void>; readonly onDidDisposeModel: Event<void>;
readonly onDidChange: Event<IChatViewModelChangeEvent>; readonly onDidChange: Event<IChatViewModelChangeEvent>;
@ -110,7 +109,6 @@ export interface IChatResponseViewModel {
readonly sessionId: string; readonly sessionId: string;
/** This ID updates every time the underlying data changes */ /** This ID updates every time the underlying data changes */
readonly dataId: string; readonly dataId: string;
readonly providerId: string;
/** The ID of the associated IChatRequestViewModel */ /** The ID of the associated IChatRequestViewModel */
readonly requestId: string; readonly requestId: string;
readonly username: string; readonly username: string;
@ -175,10 +173,6 @@ export class ChatViewModel extends Disposable implements IChatViewModel {
return this._model.requestInProgress; return this._model.requestInProgress;
} }
get providerId() {
return this._model.providerId;
}
get initState() { get initState() {
return this._model.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]}`; return this._model.id + `_${this._modelChangeCount}` + `_${ChatModelInitState[this._model.session.initState]}`;
} }
get providerId() {
return this._model.providerId;
}
get sessionId() { get sessionId() {
return this._model.session.sessionId; return this._model.session.sessionId;
} }

View file

@ -7,6 +7,7 @@ import { Emitter, Event } from 'vs/base/common/event';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { Memento } from 'vs/workbench/common/memento'; import { Memento } from 'vs/workbench/common/memento';
import { CHAT_PROVIDER_ID } from 'vs/workbench/contrib/chat/common/chatParticipantContribTypes';
export interface IChatHistoryEntry { export interface IChatHistoryEntry {
text: string; text: string;
@ -20,8 +21,8 @@ export interface IChatWidgetHistoryService {
readonly onDidClearHistory: Event<void>; readonly onDidClearHistory: Event<void>;
clearHistory(): void; clearHistory(): void;
getHistory(providerId: string): IChatHistoryEntry[]; getHistory(): IChatHistoryEntry[];
saveHistory(providerId: string, history: IChatHistoryEntry[]): void; saveHistory(history: IChatHistoryEntry[]): void;
} }
interface IChatHistory { interface IChatHistory {
@ -50,15 +51,15 @@ export class ChatWidgetHistoryService implements IChatWidgetHistoryService {
this.viewState = loadedState; this.viewState = loadedState;
} }
getHistory(providerId: string): IChatHistoryEntry[] { getHistory(): IChatHistoryEntry[] {
return this.viewState.history?.[providerId] ?? []; return this.viewState.history?.[CHAT_PROVIDER_ID] ?? [];
} }
saveHistory(providerId: string, history: IChatHistoryEntry[]): void { saveHistory(history: IChatHistoryEntry[]): void {
if (!this.viewState.history) { if (!this.viewState.history) {
this.viewState.history = {}; this.viewState.history = {};
} }
this.viewState.history[providerId] = history; this.viewState.history[CHAT_PROVIDER_ID] = history;
this.memento.saveMemento(); this.memento.saveMemento();
} }

View file

@ -3,57 +3,56 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/voiceChatActions'; import { RunOnceScheduler, disposableTimeout } from 'vs/base/common/async';
import { Event } from 'vs/base/common/event';
import { firstOrDefault } from 'vs/base/common/arrays';
import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { Codicon } from 'vs/base/common/codicons'; 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 { 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 { localize, localize2 } from 'vs/nls';
import { Action2, IAction2Options, MenuId } from 'vs/platform/actions/common/actions'; 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 { ContextKeyExpr, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; 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 { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ThemeIcon } from 'vs/base/common/themables'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
import { ProgressLocation } from 'vs/platform/progress/common/progress'; 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 { 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<boolean>('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_GETTING_READY = new RawContextKey<boolean>('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<boolean>('voiceChatInProgress', false, { type: 'boolean', description: localize('voiceChatInProgress', "True when voice recording from microphone is in progress for voice chat.") }); const CONTEXT_VOICE_CHAT_IN_PROGRESS = new RawContextKey<boolean>('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<boolean>('term
const CONTEXT_VOICE_CHAT_IN_VIEW_IN_PROGRESS = new RawContextKey<boolean>('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_VIEW_IN_PROGRESS = new RawContextKey<boolean>('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<boolean>('voiceChatInEditorInProgress', false, { type: 'boolean', description: localize('voiceChatInEditorInProgress', "True when voice recording from microphone is in progress in the chat editor.") }); const CONTEXT_VOICE_CHAT_IN_EDITOR_IN_PROGRESS = new RawContextKey<boolean>('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)); const FocusInChatInput = assertIsDefined(ContextKeyExpr.or(CTX_INLINE_CHAT_FOCUSED, CONTEXT_IN_CHAT_INPUT));
type VoiceChatSessionContext = 'inline' | 'terminal' | 'quick' | 'view' | 'editor'; 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<IVoiceChatSessionController | undefined> { static async create(accessor: ServicesAccessor, context: 'inline' | 'terminal' | 'quick' | 'view' | 'focused'): Promise<IVoiceChatSessionController | undefined> {
const chatWidgetService = accessor.get(IChatWidgetService); const chatWidgetService = accessor.get(IChatWidgetService);
const viewsService = accessor.get(IViewsService); const viewsService = accessor.get(IViewsService);
const chatContributionService = accessor.get(IChatContributionService);
const quickChatService = accessor.get(IQuickChatService); const quickChatService = accessor.get(IQuickChatService);
const layoutService = accessor.get(IWorkbenchLayoutService); const layoutService = accessor.get(IWorkbenchLayoutService);
const editorService = accessor.get(IEditorService); const editorService = accessor.get(IEditorService);
@ -126,11 +124,11 @@ class VoiceChatSessionControllerFactory {
layoutService.hasFocus(Parts.PANEL_PART) || layoutService.hasFocus(Parts.PANEL_PART) ||
layoutService.hasFocus(Parts.AUXILIARYBAR_PART) layoutService.hasFocus(Parts.AUXILIARYBAR_PART)
) { ) {
return VoiceChatSessionControllerFactory.doCreateForChatView(chatInput, viewsService, chatContributionService); return VoiceChatSessionControllerFactory.doCreateForChatView(chatInput, viewsService);
} }
if (layoutService.hasFocus(Parts.EDITOR_PART)) { if (layoutService.hasFocus(Parts.EDITOR_PART)) {
return VoiceChatSessionControllerFactory.doCreateForChatEditor(chatInput, viewsService, chatContributionService); return VoiceChatSessionControllerFactory.doCreateForChatEditor(chatInput, viewsService);
} }
return VoiceChatSessionControllerFactory.doCreateForQuickChat(chatInput, quickChatService); return VoiceChatSessionControllerFactory.doCreateForQuickChat(chatInput, quickChatService);
@ -150,7 +148,7 @@ class VoiceChatSessionControllerFactory {
if (context === 'view' || context === 'focused' /* fallback in case 'focused' was not successful */) { if (context === 'view' || context === 'focused' /* fallback in case 'focused' was not successful */) {
const chatView = await VoiceChatSessionControllerFactory.revealChatView(accessor); const chatView = await VoiceChatSessionControllerFactory.revealChatView(accessor);
if (chatView) { 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<IChatWidget | undefined> { static async revealChatView(accessor: ServicesAccessor): Promise<IChatWidget | undefined> {
const chatWidgetService = accessor.get(IChatWidgetService);
const chatService = accessor.get(IChatService); const chatService = accessor.get(IChatService);
const viewsService = accessor.get(IViewsService);
const provider = firstOrDefault(chatService.getProviderInfos()); if (chatService.isEnabled(ChatAgentLocation.Panel)) {
if (provider) { return showChatView(viewsService);
return chatWidgetService.revealViewForProvider(provider.id);
} }
return undefined; return undefined;
} }
private static doCreateForChatView(chatView: IChatWidget, viewsService: IViewsService, chatContributionService: IChatContributionService): IVoiceChatSessionController { private static doCreateForChatView(chatView: IChatWidget, viewsService: IViewsService): IVoiceChatSessionController {
return VoiceChatSessionControllerFactory.doCreateForChatViewOrEditor('view', chatView, viewsService, chatContributionService); return VoiceChatSessionControllerFactory.doCreateForChatViewOrEditor('view', chatView, viewsService);
} }
private static doCreateForChatEditor(chatView: IChatWidget, viewsService: IViewsService, chatContributionService: IChatContributionService): IVoiceChatSessionController { private static doCreateForChatEditor(chatView: IChatWidget, viewsService: IViewsService): IVoiceChatSessionController {
return VoiceChatSessionControllerFactory.doCreateForChatViewOrEditor('editor', chatView, viewsService, chatContributionService); 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 { return {
context, context,
onDidAcceptInput: chatView.onDidAcceptInput, onDidAcceptInput: chatView.onDidAcceptInput,
// TODO@bpasero cancellation needs to work better for chat editors that are not view bound // 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(), focusInput: () => chatView.focusInput(),
acceptInput: () => chatView.acceptInput(), acceptInput: () => chatView.acceptInput(),
updateInput: text => chatView.setInput(text), updateInput: text => chatView.setInput(text),
@ -882,7 +878,7 @@ export class KeywordActivationContribution extends Disposable implements IWorkbe
@IInstantiationService instantiationService: IInstantiationService, @IInstantiationService instantiationService: IInstantiationService,
@IEditorService private readonly editorService: IEditorService, @IEditorService private readonly editorService: IEditorService,
@IHostService private readonly hostService: IHostService, @IHostService private readonly hostService: IHostService,
@IChatService private readonly chatService: IChatService @IChatAgentService private readonly chatAgentService: IChatAgentService,
) { ) {
super(); super();
@ -897,7 +893,12 @@ export class KeywordActivationContribution extends Disposable implements IWorkbe
this.handleKeywordActivation(); 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.onDidStartSpeechToTextSession(() => this.handleKeywordActivation()));
this._register(this.speechService.onDidEndSpeechToTextSession(() => this.handleKeywordActivation())); this._register(this.speechService.onDidEndSpeechToTextSession(() => this.handleKeywordActivation()));
@ -910,7 +911,7 @@ export class KeywordActivationContribution extends Disposable implements IWorkbe
} }
private updateConfiguration(): void { 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 return; // these settings require a speech and chat provider
} }

View file

@ -6,7 +6,9 @@
import * as assert from 'assert'; import * as assert from 'assert';
import { CancellationToken } from 'vs/base/common/cancellation'; import { CancellationToken } from 'vs/base/common/cancellation';
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; 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 { 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 { ILogService, NullLogService } from 'vs/platform/log/common/log';
import { IStorageService } from 'vs/platform/storage/common/storage'; import { IStorageService } from 'vs/platform/storage/common/storage';
import { ChatVariablesService } from 'vs/workbench/contrib/chat/browser/chatVariables'; import { ChatVariablesService } from 'vs/workbench/contrib/chat/browser/chatVariables';
@ -33,6 +35,7 @@ suite('ChatVariables', function () {
instantiationService.stub(IExtensionService, new TestExtensionService()); instantiationService.stub(IExtensionService, new TestExtensionService());
instantiationService.stub(IChatVariablesService, service); instantiationService.stub(IChatVariablesService, service);
instantiationService.stub(IChatService, new MockChatService()); instantiationService.stub(IChatService, new MockChatService());
instantiationService.stub(IContextKeyService, new MockContextKeyService());
instantiationService.stub(IChatAgentService, instantiationService.createInstance(ChatAgentService)); instantiationService.stub(IChatAgentService, instantiationService.createInstance(ChatAgentService));
}); });

View file

@ -14,13 +14,6 @@ export class MockChatWidgetService implements IChatWidgetService {
*/ */
readonly lastFocusedWidget: IChatWidget | undefined; readonly lastFocusedWidget: IChatWidget | undefined;
/**
* Returns whether a view was successfully revealed.
*/
async revealViewForProvider(providerId: string): Promise<IChatWidget | undefined> {
return undefined;
}
getWidgetByInputUri(uri: URI): IChatWidget | undefined { getWidgetByInputUri(uri: URI): IChatWidget | undefined {
return undefined; return undefined;
} }

View file

@ -91,6 +91,5 @@
}, },
contentReferences: [ ] contentReferences: [ ]
} }
], ]
providerId: "testProvider"
} }

View file

@ -4,6 +4,5 @@
responderUsername: "test", responderUsername: "test",
responderAvatarIconUri: undefined, responderAvatarIconUri: undefined,
welcomeMessage: undefined, welcomeMessage: undefined,
requests: [ ], requests: [ ]
providerId: "testProvider"
} }

View file

@ -97,6 +97,5 @@
}, },
contentReferences: [ ] contentReferences: [ ]
} }
], ]
providerId: "testProvider"
} }

View file

@ -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"
}

View file

@ -1,9 +0,0 @@
{
requesterUsername: "test",
requesterAvatarIconUri: undefined,
responderUsername: "test",
responderAvatarIconUri: undefined,
welcomeMessage: undefined,
requests: [ ],
providerId: "testProvider"
}

View file

@ -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"
}

View file

@ -11,7 +11,9 @@ import { assertSnapshot } from 'vs/base/test/common/snapshot';
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
import { OffsetRange } from 'vs/editor/common/core/offsetRange'; import { OffsetRange } from 'vs/editor/common/core/offsetRange';
import { Range } from 'vs/editor/common/core/range'; 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 { 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 { ILogService, NullLogService } from 'vs/platform/log/common/log';
import { IStorageService } from 'vs/platform/storage/common/storage'; import { IStorageService } from 'vs/platform/storage/common/storage';
import { ChatAgentService, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; 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(IStorageService, testDisposables.add(new TestStorageService()));
instantiationService.stub(ILogService, new NullLogService()); instantiationService.stub(ILogService, new NullLogService());
instantiationService.stub(IExtensionService, new TestExtensionService()); instantiationService.stub(IExtensionService, new TestExtensionService());
instantiationService.stub(IContextKeyService, new MockContextKeyService());
instantiationService.stub(IChatAgentService, instantiationService.createInstance(ChatAgentService)); instantiationService.stub(IChatAgentService, instantiationService.createInstance(ChatAgentService));
}); });
test('Waits for initialization', async () => { 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; let hasInitialized = false;
model.waitForInitialization().then(() => { model.waitForInitialization().then(() => {
@ -45,13 +48,13 @@ suite('ChatModel', () => {
assert.strictEqual(hasInitialized, false); assert.strictEqual(hasInitialized, false);
model.startInitialize(); model.startInitialize();
model.initialize({} as any, undefined); model.initialize(undefined);
await timeout(0); await timeout(0);
assert.strictEqual(hasInitialized, true); assert.strictEqual(hasInitialized, true);
}); });
test('must call startInitialize before initialize', async () => { 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; let hasInitialized = false;
model.waitForInitialization().then(() => { model.waitForInitialization().then(() => {
@ -61,12 +64,12 @@ suite('ChatModel', () => {
await timeout(0); await timeout(0);
assert.strictEqual(hasInitialized, false); assert.strictEqual(hasInitialized, false);
assert.throws(() => model.initialize({} as any, undefined)); assert.throws(() => model.initialize(undefined));
assert.strictEqual(hasInitialized, false); assert.strictEqual(hasInitialized, false);
}); });
test('deinitialize/reinitialize', async () => { test('deinitialize/reinitialize', async () => {
const model = testDisposables.add(instantiationService.createInstance(ChatModel, 'provider', undefined)); const model = testDisposables.add(instantiationService.createInstance(ChatModel, undefined));
let hasInitialized = false; let hasInitialized = false;
model.waitForInitialization().then(() => { model.waitForInitialization().then(() => {
@ -74,7 +77,7 @@ suite('ChatModel', () => {
}); });
model.startInitialize(); model.startInitialize();
model.initialize({} as any, undefined); model.initialize(undefined);
await timeout(0); await timeout(0);
assert.strictEqual(hasInitialized, true); assert.strictEqual(hasInitialized, true);
@ -85,31 +88,31 @@ suite('ChatModel', () => {
}); });
model.startInitialize(); model.startInitialize();
model.initialize({} as any, undefined); model.initialize(undefined);
await timeout(0); await timeout(0);
assert.strictEqual(hasInitialized2, true); assert.strictEqual(hasInitialized2, true);
}); });
test('cannot initialize twice', async () => { 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.startInitialize();
model.initialize({} as any, undefined); model.initialize(undefined);
assert.throws(() => model.initialize({} as any, undefined)); assert.throws(() => model.initialize(undefined));
}); });
test('Initialization fails when model is disposed', async () => { 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(); model.dispose();
assert.throws(() => model.initialize({} as any, undefined)); assert.throws(() => model.initialize(undefined));
}); });
test('removeRequest', async () => { test('removeRequest', async () => {
const model = testDisposables.add(instantiationService.createInstance(ChatModel, 'provider', undefined)); const model = testDisposables.add(instantiationService.createInstance(ChatModel, undefined));
model.startInitialize(); model.startInitialize();
model.initialize({} as any, undefined); model.initialize(undefined);
const text = 'hello'; const text = 'hello';
model.addRequest({ text, parts: [new ChatRequestTextPart(new OffsetRange(0, text.length), new Range(1, text.length, 1, text.length), text)] }, { variables: [] }); 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(); const requests = model.getRequests();

View file

@ -6,7 +6,9 @@
import { MockObject, mockObject } from 'vs/base/test/common/mock'; import { MockObject, mockObject } from 'vs/base/test/common/mock';
import { assertSnapshot } from 'vs/base/test/common/snapshot'; import { assertSnapshot } from 'vs/base/test/common/snapshot';
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; 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 { 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 { ILogService, NullLogService } from 'vs/platform/log/common/log';
import { IStorageService } from 'vs/platform/storage/common/storage'; import { IStorageService } from 'vs/platform/storage/common/storage';
import { ChatAgentLocation, ChatAgentService, IChatAgentCommand, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; 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(ILogService, new NullLogService());
instantiationService.stub(IExtensionService, new TestExtensionService()); instantiationService.stub(IExtensionService, new TestExtensionService());
instantiationService.stub(IChatService, new MockChatService()); instantiationService.stub(IChatService, new MockChatService());
instantiationService.stub(IContextKeyService, new MockContextKeyService());
instantiationService.stub(IChatAgentService, instantiationService.createInstance(ChatAgentService)); instantiationService.stub(IChatAgentService, instantiationService.createInstance(ChatAgentService));
varService = mockObject<IChatVariablesService>()({}); varService = mockObject<IChatVariablesService>()({});

View file

@ -5,12 +5,10 @@
import * as assert from 'assert'; import * as assert from 'assert';
import { CancellationToken } from 'vs/base/common/cancellation'; import { CancellationToken } from 'vs/base/common/cancellation';
import { Disposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import { assertSnapshot } from 'vs/base/test/common/snapshot'; import { assertSnapshot } from 'vs/base/test/common/snapshot';
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
import { Range } from 'vs/editor/common/core/range'; import { Range } from 'vs/editor/common/core/range';
import { ProviderResult } from 'vs/editor/common/languages';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; 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 { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { ChatAgentLocation, ChatAgentService, IChatAgent, IChatAgentImplementation, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; 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 { 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 { ChatService } from 'vs/workbench/contrib/chat/common/chatServiceImpl';
import { ChatSlashCommandService, IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { ChatSlashCommandService, IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands';
import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; 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 { IViewsService } from 'vs/workbench/services/views/common/viewsService';
import { TestContextService, TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; 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<IChat> {
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 chatAgentWithUsedContextId = 'ChatProviderWithUsedContext';
const chatAgentWithUsedContext: IChatAgent = { const chatAgentWithUsedContext: IChatAgent = {
id: chatAgentWithUsedContextId, id: chatAgentWithUsedContextId,
@ -100,7 +77,6 @@ suite('ChatService', () => {
instantiationService.stub(IExtensionService, new TestExtensionService()); instantiationService.stub(IExtensionService, new TestExtensionService());
instantiationService.stub(IContextKeyService, new MockContextKeyService()); instantiationService.stub(IContextKeyService, new MockContextKeyService());
instantiationService.stub(IViewsService, new TestExtensionService()); instantiationService.stub(IViewsService, new TestExtensionService());
instantiationService.stub(IChatContributionService, new TestExtensionService());
instantiationService.stub(IWorkspaceContextService, new TestContextService()); instantiationService.stub(IWorkspaceContextService, new TestContextService());
instantiationService.stub(IChatSlashCommandService, testDisposables.add(instantiationService.createInstance(ChatSlashCommandService))); instantiationService.stub(IChatSlashCommandService, testDisposables.add(instantiationService.createInstance(ChatSlashCommandService)));
instantiationService.stub(IChatService, new MockChatService()); instantiationService.stub(IChatService, new MockChatService());
@ -121,23 +97,16 @@ suite('ChatService', () => {
test('retrieveSession', async () => { test('retrieveSession', async () => {
const testService = testDisposables.add(instantiationService.createInstance(ChatService)); const testService = testDisposables.add(instantiationService.createInstance(ChatService));
const provider1 = testDisposables.add(new SimpleTestProvider('provider1')); const session1 = testDisposables.add(testService.startSession(CancellationToken.None));
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));
await session1.waitForInitialization(); await session1.waitForInitialization();
session1.addRequest({ parts: [], text: 'request 1' }, { variables: [] }); 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(); await session2.waitForInitialization();
session2.addRequest({ parts: [], text: 'request 2' }, { variables: [] }); session2.addRequest({ parts: [], text: 'request 2' }, { variables: [] });
storageService.flush(); storageService.flush();
const testService2 = testDisposables.add(instantiationService.createInstance(ChatService)); 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)!); const retrieved1 = testDisposables.add(testService2.getOrRestoreSession(session1.sessionId)!);
await retrieved1.waitForInitialization(); await retrieved1.waitForInitialization();
const retrieved2 = testDisposables.add(testService2.getOrRestoreSession(session2.sessionId)!); const retrieved2 = testDisposables.add(testService2.getOrRestoreSession(session2.sessionId)!);
@ -146,57 +115,10 @@ suite('ChatService', () => {
assert.deepStrictEqual(retrieved2.getRequests()[0]?.message.text, 'request 2'); 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<any> {
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<IChat | undefined> {
throw new Error('Function not implemented.');
}
}));
assert.throws(() => {
testDisposables.add(testService.registerProvider({
id,
prepareSession: function (token: CancellationToken): ProviderResult<IChat | undefined> {
throw new Error('Function not implemented.');
}
}));
}, 'Expected to throw for dupe provider');
});
test('addCompleteRequest', async () => { test('addCompleteRequest', async () => {
const testService = testDisposables.add(instantiationService.createInstance(ChatService)); 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); assert.strictEqual(model.getRequests().length, 0);
await testService.addCompleteRequest(model.sessionId, 'test request', undefined, { message: 'test response' }); await testService.addCompleteRequest(model.sessionId, 'test request', undefined, { message: 'test response' });
@ -209,9 +131,8 @@ suite('ChatService', () => {
testDisposables.add(chatAgentService.registerAgentImplementation(chatAgentWithUsedContextId, chatAgentWithUsedContext)); testDisposables.add(chatAgentService.registerAgentImplementation(chatAgentWithUsedContextId, chatAgentWithUsedContext));
chatAgentService.updateAgent(chatAgentWithUsedContextId, { requester: { name: 'test' }, fullName: 'test' }); chatAgentService.updateAgent(chatAgentWithUsedContextId, { requester: { name: 'test' }, fullName: 'test' });
const testService = testDisposables.add(instantiationService.createInstance(ChatService)); 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); assert.strictEqual(model.getRequests().length, 0);
await assertSnapshot(model.toExport()); await assertSnapshot(model.toExport());
@ -232,9 +153,8 @@ suite('ChatService', () => {
// create the first service, send request, get response, and serialize the state // create the first service, send request, get response, and serialize the state
{ // serapate block to not leak variables in outer scope { // serapate block to not leak variables in outer scope
const testService = testDisposables.add(instantiationService.createInstance(ChatService)); 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); assert.strictEqual(chatModel1.getRequests().length, 0);
const response = await testService.sendRequest(chatModel1.sessionId, `@${chatAgentWithUsedContextId} test request`); const response = await testService.sendRequest(chatModel1.sessionId, `@${chatAgentWithUsedContextId} test request`);
@ -248,7 +168,6 @@ suite('ChatService', () => {
// try deserializing the state into a new service // try deserializing the state into a new service
const testService2 = testDisposables.add(instantiationService.createInstance(ChatService)); const testService2 = testDisposables.add(instantiationService.createInstance(ChatService));
testDisposables.add(testService2.registerProvider(testDisposables.add(new SimpleTestProvider('testProvider'))));
const chatModel2 = testService2.loadSessionFromContent(serializedChatData); const chatModel2 = testService2.loadSessionFromContent(serializedChatData);
assert(chatModel2); assert(chatModel2);

View file

@ -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.');
}
}

View file

@ -5,43 +5,37 @@
import { CancellationToken } from 'vs/base/common/cancellation'; import { CancellationToken } from 'vs/base/common/cancellation';
import { Event } from 'vs/base/common/event'; import { Event } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri'; 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 { ChatModel, IChatModel, IChatRequestVariableData, ISerializableChatData } from 'vs/workbench/contrib/chat/common/chatModel';
import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; 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 { export class MockChatService implements IChatService {
_serviceBrand: undefined; _serviceBrand: undefined;
transferredSessionData: IChatTransferredSessionData | 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.'); throw new Error('Method not implemented.');
} }
getProviderInfos(): IChatProviderInfo[] { getProviderInfos(): IChatProviderInfo[] {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
startSession(providerId: string, token: CancellationToken): ChatModel | undefined { startSession(token: CancellationToken): ChatModel | undefined {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
getSession(sessionId: string): IChatModel | undefined { getSession(sessionId: string): IChatModel | undefined {
return {} as IChatModel; return {} as IChatModel;
} }
getSessionId(sessionProviderId: number): string | undefined {
throw new Error('Method not implemented.');
}
getOrRestoreSession(sessionId: string): IChatModel | undefined { getOrRestoreSession(sessionId: string): IChatModel | undefined {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
loadSessionFromContent(data: ISerializableChatData): IChatModel | undefined { loadSessionFromContent(data: ISerializableChatData): IChatModel | undefined {
throw new Error('Method not implemented.'); 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. * Returns whether the request was accepted.
*/ */
@ -74,7 +68,7 @@ export class MockChatService implements IChatService {
notifyUserAction(event: IChatUserActionEvent): void { notifyUserAction(event: IChatUserActionEvent): void {
throw new Error('Method not implemented.'); 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 { transferChatSession(transferredSessionData: IChatTransferredSessionData, toWorkspace: URI): void {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');

View file

@ -57,6 +57,7 @@ suite('VoiceChat', () => {
getActivatedAgents(): IChatAgent[] { return agents; } getActivatedAgents(): IChatAgent[] { return agents; }
getAgents(): IChatAgent[] { return agents; } getAgents(): IChatAgent[] { return agents; }
getDefaultAgent(): IChatAgent | undefined { throw new Error(); } getDefaultAgent(): IChatAgent | undefined { throw new Error(); }
getContributedDefaultAgent(): IChatAgentData | undefined { throw new Error(); }
getSecondaryAgent(): IChatAgent | undefined { throw new Error(); } getSecondaryAgent(): IChatAgent | undefined { throw new Error(); }
registerAgent(id: string, data: IChatAgentData): IDisposable { throw new Error('Method not implemented.'); } registerAgent(id: string, data: IChatAgentData): IDisposable { throw new Error('Method not implemented.'); }
getAgent(id: string): IChatAgentData | undefined { throw new Error('Method not implemented.'); } getAgent(id: string): IChatAgentData | undefined { throw new Error('Method not implemented.'); }

View file

@ -50,7 +50,7 @@ export class InlineChatContentWidget implements IContentWidget {
@IContextKeyService contextKeyService: IContextKeyService, @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( const scopedInstaService = instaService.createChild(
new ServiceCollection([ new ServiceCollection([

View file

@ -29,7 +29,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c
import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log'; 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 { ChatAgentLocation, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
import { chatAgentLeader, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { chatAgentLeader, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart } from 'vs/workbench/contrib/chat/common/chatParserTypes';
import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; 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 { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart';
import { OffsetRange } from 'vs/editor/common/core/offsetRange'; import { OffsetRange } from 'vs/editor/common/core/offsetRange';
import { isEqual } from 'vs/base/common/resources'; import { isEqual } from 'vs/base/common/resources';
import { IViewsService } from 'vs/workbench/services/views/common/viewsService';
export const enum State { export const enum State {
CREATE_SESSION = 'CREATE_SESSION', CREATE_SESSION = 'CREATE_SESSION',
@ -1229,8 +1230,7 @@ async function showMessageResponse(accessor: ServicesAccessor, query: string, re
return; return;
} }
const chatWidgetService = accessor.get(IChatWidgetService); const widget = await showChatView(accessor.get(IViewsService));
const widget = await chatWidgetService.revealViewForProvider(agent.name);
if (widget && widget.viewModel) { if (widget && widget.viewModel) {
chatService.addCompleteRequest(widget.viewModel.sessionId, query, undefined, { message: response }); chatService.addCompleteRequest(widget.viewModel.sessionId, query, undefined, { message: response });
widget.focusLastMessage(); widget.focusLastMessage();
@ -1238,13 +1238,12 @@ async function showMessageResponse(accessor: ServicesAccessor, query: string, re
} }
async function sendRequest(accessor: ServicesAccessor, query: string) { async function sendRequest(accessor: ServicesAccessor, query: string) {
const widgetService = accessor.get(IChatWidgetService);
const chatAgentService = accessor.get(IChatAgentService); const chatAgentService = accessor.get(IChatAgentService);
const agent = chatAgentService.getActivatedAgents().find(agent => agent.locations.includes(ChatAgentLocation.Panel) && agent.isDefault); const agent = chatAgentService.getActivatedAgents().find(agent => agent.locations.includes(ChatAgentLocation.Panel) && agent.isDefault);
if (!agent) { if (!agent) {
return; return;
} }
const widget = await widgetService.revealViewForProvider(agent.name); const widget = await showChatView(accessor.get(IViewsService));
if (!widget) { if (!widget) {
return; return;
} }

View file

@ -2,41 +2,40 @@
* Copyright (c) Microsoft Corporation. All rights reserved. * Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information. * 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 { coalesceInPlace, isNonEmptyArray } from 'vs/base/common/arrays';
import { MarkdownString } from 'vs/base/common/htmlContent'; import { raceCancellation } from 'vs/base/common/async';
import { TextEdit } from 'vs/editor/common/languages'; import { CancellationToken } from 'vs/base/common/cancellation';
import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
import { Codicon } from 'vs/base/common/codicons'; import { Codicon } from 'vs/base/common/codicons';
import { CancellationError } from 'vs/base/common/errors'; 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 { 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 { class BridgeAgent implements IChatAgentImplementation {
@ -299,34 +298,6 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService {
this._store.add(this._chatAgentService.onDidChangeAgents(() => addOrRemoveBridgeAgent())); this._store.add(this._chatAgentService.onDidChangeAgents(() => addOrRemoveBridgeAgent()));
const brigdeAgent = this._store.add(new MutableDisposable()); const brigdeAgent = this._store.add(new MutableDisposable());
addOrRemoveBridgeAgent(); addOrRemoveBridgeAgent();
// MARK: register fake chat provider
const mapping = this._store.add(new DisposableMap<IInlineChatSessionProvider>());
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() { dispose() {
@ -335,10 +306,6 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService {
this._sessions.clear(); 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<Session | undefined> { async createSession(editor: IActiveCodeEditor, options: { editMode: EditMode; wholeRange?: Range }, token: CancellationToken): Promise<Session | undefined> {
const provider = Iterable.first(this._inlineChatService.getAllProvider()); const provider = Iterable.first(this._inlineChatService.getAllProvider());
@ -347,7 +314,7 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService {
return undefined; return undefined;
} }
const chatModel = this._chatService.startSession(this._asChatProviderBrigdeName(provider), token); const chatModel = this._chatService.startSession(token);
if (!chatModel) { if (!chatModel) {
this._logService.trace('[IE] NO chatModel found'); this._logService.trace('[IE] NO chatModel found');
return undefined; return undefined;

View file

@ -93,7 +93,6 @@ export interface IInlineChatWidgetConstructionOptions {
export interface IInlineChatMessage { export interface IInlineChatMessage {
message: IMarkdownString; message: IMarkdownString;
requestId: string; requestId: string;
providerId: string;
} }
export interface IInlineChatMessageAppender { export interface IInlineChatMessageAppender {
@ -302,9 +301,9 @@ export class InlineChatWidget {
// LEGACY - default chat model // LEGACY - default chat model
// this is only here for as long as we offer updateChatMessage // 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.startInitialize();
this._defaultChatModel.initialize({ id: 1 }, undefined); this._defaultChatModel.initialize(undefined);
this.setChatModel(this._defaultChatModel); this.setChatModel(this._defaultChatModel);
} }

View file

@ -43,7 +43,6 @@ import { IInlineChatSavingService } from '../../browser/inlineChatSavingService'
import { IInlineChatSessionService } from '../../browser/inlineChatSessionService'; import { IInlineChatSessionService } from '../../browser/inlineChatSessionService';
import { InlineChatSessionServiceImpl } from '../../browser/inlineChatSessionServiceImpl'; import { InlineChatSessionServiceImpl } from '../../browser/inlineChatSessionServiceImpl';
import { TestWorkerService } from './testWorkerService'; import { TestWorkerService } from './testWorkerService';
import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService';
import { IExtensionService, nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { IExtensionService, nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatService } from 'vs/workbench/contrib/chat/common/chatService';
import { ChatService } from 'vs/workbench/contrib/chat/common/chatServiceImpl'; import { ChatService } from 'vs/workbench/contrib/chat/common/chatServiceImpl';
@ -127,7 +126,6 @@ suite('InteractiveChatController', function () {
[IExtensionService, new TestExtensionService()], [IExtensionService, new TestExtensionService()],
[IContextKeyService, new MockContextKeyService()], [IContextKeyService, new MockContextKeyService()],
[IViewsService, new TestExtensionService()], [IViewsService, new TestExtensionService()],
[IChatContributionService, new TestExtensionService()],
[IWorkspaceContextService, new TestContextService()], [IWorkspaceContextService, new TestContextService()],
[IChatWidgetHistoryService, new SyncDescriptor(ChatWidgetHistoryService)], [IChatWidgetHistoryService, new SyncDescriptor(ChatWidgetHistoryService)],
[IChatWidgetService, new SyncDescriptor(ChatWidgetService)], [IChatWidgetService, new SyncDescriptor(ChatWidgetService)],

View file

@ -46,7 +46,6 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { ChatWidgetService } from 'vs/workbench/contrib/chat/browser/chatWidget'; 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 { IChatService } from 'vs/workbench/contrib/chat/common/chatService';
import { ChatService } from 'vs/workbench/contrib/chat/common/chatServiceImpl'; import { ChatService } from 'vs/workbench/contrib/chat/common/chatServiceImpl';
import { IChatSlashCommandService, ChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; 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 { IViewsService } from 'vs/workbench/services/views/common/viewsService';
import { TestExtensionService, TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; import { TestExtensionService, TestContextService } from 'vs/workbench/test/common/workbenchTestServices';
import { IChatAgentService, ChatAgentService, ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatAgentService, ChatAgentService, ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents';
import { MockChatContributionService } from 'vs/workbench/contrib/chat/test/common/mockChatContributionService';
suite('InlineChatSession', function () { suite('InlineChatSession', function () {
@ -80,14 +78,12 @@ suite('InlineChatSession', function () {
[IExtensionService, new TestExtensionService()], [IExtensionService, new TestExtensionService()],
[IContextKeyService, new MockContextKeyService()], [IContextKeyService, new MockContextKeyService()],
[IViewsService, new TestExtensionService()], [IViewsService, new TestExtensionService()],
[IChatContributionService, new TestExtensionService()],
[IWorkspaceContextService, new TestContextService()], [IWorkspaceContextService, new TestContextService()],
[IChatWidgetHistoryService, new SyncDescriptor(ChatWidgetHistoryService)], [IChatWidgetHistoryService, new SyncDescriptor(ChatWidgetHistoryService)],
[IChatWidgetService, new SyncDescriptor(ChatWidgetService)], [IChatWidgetService, new SyncDescriptor(ChatWidgetService)],
[IChatSlashCommandService, new SyncDescriptor(ChatSlashCommandService)], [IChatSlashCommandService, new SyncDescriptor(ChatSlashCommandService)],
[IChatService, new SyncDescriptor(ChatService)], [IChatService, new SyncDescriptor(ChatService)],
[IEditorWorkerService, new SyncDescriptor(TestWorkerService)], [IEditorWorkerService, new SyncDescriptor(TestWorkerService)],
[IChatContributionService, new MockChatContributionService()],
[IChatAgentService, new SyncDescriptor(ChatAgentService)], [IChatAgentService, new SyncDescriptor(ChatAgentService)],
[IInlineChatService, new SyncDescriptor(InlineChatServiceImpl)], [IInlineChatService, new SyncDescriptor(InlineChatServiceImpl)],
[IContextKeyService, contextKeyService], [IContextKeyService, contextKeyService],

View file

@ -614,7 +614,6 @@ export class NotebookChatController extends Disposable implements INotebookEdito
if (!progressiveChatResponse) { if (!progressiveChatResponse) {
const message = { const message = {
message: new MarkdownString(data.markdownFragment, { supportThemeIcons: true, supportHtml: true, isTrusted: false }), message: new MarkdownString(data.markdownFragment, { supportThemeIcons: true, supportHtml: true, isTrusted: false }),
providerId: this._activeSession!.provider.label,
requestId: request.requestId, requestId: request.requestId,
}; };
progressiveChatResponse = this._widget?.inlineChatWidget.updateChatMessage(message, true); progressiveChatResponse = this._widget?.inlineChatWidget.updateChatMessage(message, true);

View file

@ -12,7 +12,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; 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 { ChatAgentLocation, IChatAgentRequest, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes';
import { ChatUserAction, IChatProgress, IChatService, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; 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 { MarkdownString } from 'vs/base/common/htmlContent';
import { ChatModel, ChatRequestModel, IChatRequestVariableData, getHistoryEntriesFromModel } from 'vs/workbench/contrib/chat/common/chatModel'; import { ChatModel, ChatRequestModel, IChatRequestVariableData, getHistoryEntriesFromModel } from 'vs/workbench/contrib/chat/common/chatModel';
import { TerminalChatContextKeys } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; import { TerminalChatContextKeys } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat';
import { IViewsService } from 'vs/workbench/services/views/common/viewsService';
const enum Message { const enum Message {
NONE = 0, NONE = 0,
@ -95,9 +96,9 @@ export class TerminalChatController extends Disposable implements ITerminalContr
@IChatAgentService private readonly _chatAgentService: IChatAgentService, @IChatAgentService private readonly _chatAgentService: IChatAgentService,
@IContextKeyService private readonly _contextKeyService: IContextKeyService, @IContextKeyService private readonly _contextKeyService: IContextKeyService,
@IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService, @IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService,
@IChatWidgetService private readonly _chatWidgetService: IChatWidgetService,
@IChatService private readonly _chatService: IChatService, @IChatService private readonly _chatService: IChatService,
@IChatCodeBlockContextProviderService private readonly _chatCodeBlockContextProviderService: IChatCodeBlockContextProviderService, @IChatCodeBlockContextProviderService private readonly _chatCodeBlockContextProviderService: IChatCodeBlockContextProviderService,
@IViewsService private readonly _viewsService: IViewsService,
) { ) {
super(); super();
@ -134,13 +135,11 @@ export class TerminalChatController extends Disposable implements ITerminalContr
// a default chat model (unless configured) and feedback is reported against that one. This // a default chat model (unless configured) and feedback is reported against that one. This
// code forwards the feedback to an actual registered provider // code forwards the feedback to an actual registered provider
this._register(this._chatService.onDidPerformUserAction(e => { this._register(this._chatService.onDidPerformUserAction(e => {
if (e.providerId === this._chatWidget?.rawValue?.inlineChatWidget.getChatModel().providerId) {
if (e.action.kind === 'bug') { if (e.action.kind === 'bug') {
this.acceptFeedback(undefined); this.acceptFeedback(undefined);
} else if (e.action.kind === 'vote') { } else if (e.action.kind === 'vote') {
this.acceptFeedback(e.action.direction === InteractiveSessionVoteDirection.Up); this.acceptFeedback(e.action.direction === InteractiveSessionVoteDirection.Up);
} }
}
})); }));
} }
@ -179,9 +178,8 @@ export class TerminalChatController extends Disposable implements ITerminalContr
} }
acceptFeedback(helpful?: boolean): void { acceptFeedback(helpful?: boolean): void {
const providerId = this._chatService.getProviderInfos()?.[0]?.id;
const model = this._model.value; const model = this._model.value;
if (!providerId || !this._currentRequest || !model) { if (!this._currentRequest || !model) {
return; return;
} }
let action: ChatUserAction; let action: ChatUserAction;
@ -195,7 +193,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr
for (const request of model.getRequests()) { for (const request of model.getRequests()) {
if (request.response?.response.value || request.response?.result) { if (request.response?.response.value || request.response?.result) {
this._chatService.notifyUserAction({ this._chatService.notifyUserAction({
providerId,
sessionId: request.session.sessionId, sessionId: request.session.sessionId,
requestId: request.id, requestId: request.id,
agentId: request.response?.agent?.id, agentId: request.response?.agent?.id,
@ -253,12 +250,8 @@ export class TerminalChatController extends Disposable implements ITerminalContr
} }
async acceptInput(): Promise<void> { async acceptInput(): Promise<void> {
const providerInfo = this._chatService.getProviderInfos()?.[0];
if (!providerInfo) {
return;
}
if (!this._model.value) { 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) { if (!this._model.value) {
throw new Error('Could not start chat session'); throw new Error('Could not start chat session');
} }
@ -328,7 +321,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr
if (this._currentRequest) { if (this._currentRequest) {
this._chatAccessibilityService.acceptResponse(responseContent, accessibilityRequestId); this._chatAccessibilityService.acceptResponse(responseContent, accessibilityRequestId);
const containsCode = responseContent.includes('```'); 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 firstCodeBlock = await this.chatWidget?.inlineChatWidget.getCodeBlockInfo(0);
const secondCodeBlock = await this.chatWidget?.inlineChatWidget.getCodeBlockInfo(1); const secondCodeBlock = await this.chatWidget?.inlineChatWidget.getCodeBlockInfo(1);
this._responseContainsCodeBlockContextKey.set(!!firstCodeBlock); this._responseContainsCodeBlockContextKey.set(!!firstCodeBlock);
@ -377,11 +370,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr
} }
async viewInChat(): Promise<void> { async viewInChat(): Promise<void> {
const providerInfo = this._chatService.getProviderInfos()?.[0]; const widget = await showChatView(this._viewsService);
if (!providerInfo) {
return;
}
const widget = await this._chatWidgetService.revealViewForProvider(providerInfo.id);
const request = this._currentRequest; const request = this._currentRequest;
if (!widget || !request?.response) { if (!widget || !request?.response) {
return; return;

View file

@ -8,11 +8,7 @@ declare module 'vscode' {
* The tab represents an interactive window. * The tab represents an interactive window.
*/ */
export class TabInputChat { export class TabInputChat {
/** constructor();
* The uri of the history notebook in the interactive window.
*/
readonly providerId: string;
constructor(providerId: string);
} }
export interface Tab { export interface Tab {

View file

@ -128,6 +128,9 @@ declare module 'vscode' {
// current version of the proposal. // current version of the proposal.
export const _version: 1 | number; export const _version: 1 | number;
/**
* @deprecated
*/
export function registerInteractiveSessionProvider(id: string, provider: InteractiveSessionProvider): Disposable; export function registerInteractiveSessionProvider(id: string, provider: InteractiveSessionProvider): Disposable;
export function registerInteractiveEditorSessionProvider(provider: InteractiveEditorSessionProvider, metadata?: InteractiveEditorSessionProviderMetadata): Disposable; export function registerInteractiveEditorSessionProvider(provider: InteractiveEditorSessionProvider, metadata?: InteractiveEditorSessionProviderMetadata): Disposable;