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

View File

@ -76,7 +76,6 @@ import './mainThreadNotebookRenderers';
import './mainThreadNotebookSaveParticipant';
import './mainThreadInteractive';
import './mainThreadInlineChat';
import './mainThreadChat';
import './mainThreadTask';
import './mainThreadLabelService';
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 { revive } from 'vs/base/common/marshalling';
import { escapeRegExpCharacters } from 'vs/base/common/strings';
import { URI, UriComponents } from 'vs/base/common/uri';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { getWordAtText } from 'vs/editor/common/core/wordHelper';
@ -15,6 +16,7 @@ import { ITextModel } from 'vs/editor/common/model';
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
import { ExtHostChatAgentsShape2, ExtHostContext, IChatProgressDto, IExtensionChatAgentMetadata, MainContext, MainThreadChatAgentsShape2 } from 'vs/workbench/api/common/extHost.protocol';
import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat';
import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart';
@ -48,6 +50,7 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA
@ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService,
@IChatWidgetService private readonly _chatWidgetService: IChatWidgetService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@ILogService private readonly _logService: ILogService,
) {
super();
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatAgents2);
@ -75,6 +78,18 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA
this._agents.deleteAndDispose(handle);
}
$transferActiveChatSession(toWorkspace: UriComponents): void {
const widget = this._chatWidgetService.lastFocusedWidget;
const sessionId = widget?.viewModel?.model.sessionId;
if (!sessionId) {
this._logService.error(`MainThreadChat#$transferActiveChatSession: No active chat session found`);
return;
}
const inputValue = widget?.inputEditor.getValue() ?? '';
this._chatService.transferChatSession({ sessionId, inputValue }, URI.revive(toWorkspace));
}
$registerAgent(handle: number, extension: ExtensionIdentifier, id: string, metadata: IExtensionChatAgentMetadata, dynamicProps: { name: string; description: string } | undefined): void {
const staticAgentRegistration = this._chatAgentService.getAgent(id);
if (!staticAgentRegistration && !dynamicProps) {

View File

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

View File

@ -6,7 +6,7 @@
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import * as errors from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { combinedDisposable } from 'vs/base/common/lifecycle';
import { Disposable, combinedDisposable } from 'vs/base/common/lifecycle';
import { Schemas, matchesScheme } from 'vs/base/common/network';
import Severity from 'vs/base/common/severity';
import { URI } from 'vs/base/common/uri';
@ -27,9 +27,7 @@ import { ExtHostApiCommands } from 'vs/workbench/api/common/extHostApiCommands';
import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService';
import { IExtHostAuthentication } from 'vs/workbench/api/common/extHostAuthentication';
import { ExtHostBulkEdits } from 'vs/workbench/api/common/extHostBulkEdits';
import { ExtHostChat } from 'vs/workbench/api/common/extHostChat';
import { ExtHostChatAgents2 } from 'vs/workbench/api/common/extHostChatAgents2';
import { IExtHostLanguageModels } from 'vs/workbench/api/common/extHostLanguageModels';
import { ExtHostChatVariables } from 'vs/workbench/api/common/extHostChatVariables';
import { ExtHostClipboard } from 'vs/workbench/api/common/extHostClipboard';
import { ExtHostEditorInsets } from 'vs/workbench/api/common/extHostCodeInsets';
@ -57,6 +55,7 @@ import { ExtHostInteractiveEditor } from 'vs/workbench/api/common/extHostInlineC
import { ExtHostInteractive } from 'vs/workbench/api/common/extHostInteractive';
import { ExtHostLabelService } from 'vs/workbench/api/common/extHostLabelService';
import { ExtHostLanguageFeatures } from 'vs/workbench/api/common/extHostLanguageFeatures';
import { IExtHostLanguageModels } from 'vs/workbench/api/common/extHostLanguageModels';
import { ExtHostLanguages } from 'vs/workbench/api/common/extHostLanguages';
import { IExtHostLocalizationService } from 'vs/workbench/api/common/extHostLocalizationService';
import { IExtHostManagedSockets } from 'vs/workbench/api/common/extHostManagedSockets';
@ -84,6 +83,7 @@ import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePa
import { IExtHostTask } from 'vs/workbench/api/common/extHostTask';
import { ExtHostTelemetryLogger, IExtHostTelemetry, isNewAppInstall } from 'vs/workbench/api/common/extHostTelemetry';
import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService';
import { IExtHostTerminalShellIntegration } from 'vs/workbench/api/common/extHostTerminalShellIntegration';
import { ExtHostTesting } from 'vs/workbench/api/common/extHostTesting';
import { ExtHostEditors } from 'vs/workbench/api/common/extHostTextEditors';
import { ExtHostTheming } from 'vs/workbench/api/common/extHostTheming';
@ -107,7 +107,6 @@ import { checkProposedApiEnabled, isProposedApiEnabled } from 'vs/workbench/serv
import { ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyIdentifier';
import { TextSearchCompleteMessageType } from 'vs/workbench/services/search/common/searchExtTypes';
import type * as vscode from 'vscode';
import { IExtHostTerminalShellIntegration } from 'vs/workbench/api/common/extHostTerminalShellIntegration';
export interface IExtensionRegistries {
mine: ExtensionDescriptionRegistry;
@ -213,7 +212,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
const extHostInteractiveEditor = rpcProtocol.set(ExtHostContext.ExtHostInlineChat, new ExtHostInteractiveEditor(rpcProtocol, extHostCommands, extHostDocuments, extHostLogService));
const extHostChatAgents2 = rpcProtocol.set(ExtHostContext.ExtHostChatAgents2, new ExtHostChatAgents2(rpcProtocol, extHostLogService, extHostCommands));
const extHostChatVariables = rpcProtocol.set(ExtHostContext.ExtHostChatVariables, new ExtHostChatVariables(rpcProtocol));
const extHostChat = rpcProtocol.set(ExtHostContext.ExtHostChat, new ExtHostChat(rpcProtocol));
const extHostAiRelatedInformation = rpcProtocol.set(ExtHostContext.ExtHostAiRelatedInformation, new ExtHostRelatedInformation(rpcProtocol));
const extHostAiEmbeddingVector = rpcProtocol.set(ExtHostContext.ExtHostAiEmbeddingVector, new ExtHostAiEmbeddingVector(rpcProtocol));
const extHostStatusBar = rpcProtocol.set(ExtHostContext.ExtHostStatusBar, new ExtHostStatusBar(rpcProtocol, extHostCommands.converter));
@ -1392,12 +1390,13 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
return extHostInteractiveEditor.registerProvider(extension, provider, metadata);
},
registerInteractiveSessionProvider(id: string, provider: vscode.InteractiveSessionProvider) {
// temp stub
checkProposedApiEnabled(extension, 'interactive');
return extHostChat.registerChatProvider(extension, id, provider);
return Disposable.None;
},
transferActiveChat(toWorkspace: vscode.Uri) {
checkProposedApiEnabled(extension, 'interactive');
return extHostChat.transferActiveChat(toWorkspace);
return extHostChatAgents2.transferActiveChat(toWorkspace);
}
};

View File

@ -769,7 +769,6 @@ export interface InteractiveEditorInputDto {
export interface ChatEditorInputDto {
kind: TabInputKind.ChatEditorInput;
providerId: string;
}
export interface MultiDiffEditorInputDto {
@ -1219,6 +1218,8 @@ export interface MainThreadChatAgentsShape2 extends IDisposable {
$updateAgent(handle: number, metadataUpdate: IExtensionChatAgentMetadata): void;
$unregisterAgent(handle: number): void;
$handleProgressChunk(requestId: string, chunk: IChatProgressDto): Promise<number | void>;
$transferActiveChatSession(toWorkspace: UriComponents): void;
}
export interface IChatAgentCompletionItem {
@ -1287,7 +1288,6 @@ export interface MainThreadUrlsShape extends IDisposable {
}
export interface IChatDto {
id: number;
}
export interface IChatRequestDto {
@ -1318,18 +1318,6 @@ export type IDocumentContextDto = {
export type IChatProgressDto =
| Dto<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 {
$handleExternalUri(handle: number, uri: UriComponents): Promise<void>;
}
@ -2839,7 +2827,6 @@ export const MainContext = {
MainThreadNotebookKernels: createProxyIdentifier<MainThreadNotebookKernelsShape>('MainThreadNotebookKernels'),
MainThreadNotebookRenderers: createProxyIdentifier<MainThreadNotebookRenderersShape>('MainThreadNotebookRenderers'),
MainThreadInteractive: createProxyIdentifier<MainThreadInteractiveShape>('MainThreadInteractive'),
MainThreadChat: createProxyIdentifier<MainThreadChatShape>('MainThreadChat'),
MainThreadInlineChat: createProxyIdentifier<MainThreadInlineChatShape>('MainThreadInlineChatShape'),
MainThreadTheming: createProxyIdentifier<MainThreadThemingShape>('MainThreadTheming'),
MainThreadTunnelService: createProxyIdentifier<MainThreadTunnelServiceShape>('MainThreadTunnelService'),
@ -2904,7 +2891,6 @@ export const ExtHostContext = {
ExtHostNotebookDocumentSaveParticipant: createProxyIdentifier<ExtHostNotebookDocumentSaveParticipantShape>('ExtHostNotebookDocumentSaveParticipant'),
ExtHostInteractive: createProxyIdentifier<ExtHostInteractiveShape>('ExtHostInteractive'),
ExtHostInlineChat: createProxyIdentifier<ExtHostInlineChatShape>('ExtHostInlineChatShape'),
ExtHostChat: createProxyIdentifier<ExtHostChatShape>('ExtHostChat'),
ExtHostChatAgents2: createProxyIdentifier<ExtHostChatAgentsShape2>('ExtHostChatAgents'),
ExtHostChatVariables: createProxyIdentifier<ExtHostChatVariablesShape>('ExtHostChatVariables'),
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);
}
transferActiveChat(newWorkspace: vscode.Uri): void {
this._proxy.$transferActiveChatSession(newWorkspace);
}
createChatAgent(extension: IExtensionDescription, id: string, handler: vscode.ChatExtendedRequestHandler): vscode.ChatParticipant {
const handle = ExtHostChatAgents2._idPool++;
const agent = new ExtHostChatAgent(extension, id, this._proxy, handle, handler);

View File

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

View File

@ -4207,7 +4207,7 @@ export class InteractiveWindowInput {
}
export class ChatEditorTabInput {
constructor(readonly providerId: string) { }
constructor() { }
}
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 { IAccessibleViewService, IAccessibleContentProvider, IAccessibleViewOptions, AccessibleViewType } from 'vs/workbench/contrib/accessibility/browser/accessibleView';
import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions';
import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys';
import { CONTEXT_HAS_DEFAULT_AGENT } from 'vs/workbench/contrib/chat/common/chatContextKeys';
import { CommentAccessibilityHelpNLS } from 'vs/workbench/contrib/comments/browser/commentsAccessibility';
import { CommentCommandId } from 'vs/workbench/contrib/comments/common/commentCommandIds';
import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/commentContextKeys';
@ -117,7 +117,7 @@ export function getCommentCommandInfo(keybindingService: IKeybindingService, con
}
export function getChatCommandInfo(keybindingService: IKeybindingService, contextKeyService: IContextKeyService): string | undefined {
if (CONTEXT_PROVIDER_EXISTS.getValue(contextKeyService)) {
if (CONTEXT_HAS_DEFAULT_AGENT.getValue(contextKeyService)) {
const commentCommandInfo: string[] = [];
commentCommandInfo.push(descriptionForCommand('workbench.action.quickchat.toggle', AccessibilityHelpNLS.quickChat, AccessibilityHelpNLS.quickChatNoKb, keybindingService));
commentCommandInfo.push(descriptionForCommand('inlineChat.start', AccessibilityHelpNLS.startInlineChat, AccessibilityHelpNLS.startInlineChatNoKb, keybindingService));

View File

@ -11,7 +11,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorAction2, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { localize, localize2 } from 'vs/nls';
import { Action2, IAction2Options, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IsLinuxContext, IsWindowsContext } from 'vs/platform/contextkey/common/contextkeys';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
@ -21,19 +21,26 @@ import { ViewAction } from 'vs/workbench/browser/parts/views/viewPane';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions';
import { runAccessibilityHelpAction } from 'vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp';
import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat';
import { CHAT_VIEW_ID, IChatWidgetService, showChatView } from 'vs/workbench/contrib/chat/browser/chat';
import { IChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatEditor';
import { ChatEditorInput } from 'vs/workbench/contrib/chat/browser/chatEditorInput';
import { ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane';
import { ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents';
import { CONTEXT_CHAT_INPUT_CURSOR_AT_TOP, CONTEXT_CHAT_LOCATION, CONTEXT_IN_CHAT_INPUT, CONTEXT_IN_CHAT_SESSION, CONTEXT_PROVIDER_EXISTS, CONTEXT_REQUEST, CONTEXT_RESPONSE } from 'vs/workbench/contrib/chat/common/chatContextKeys';
import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService';
import { CONTEXT_CHAT_INPUT_CURSOR_AT_TOP, CONTEXT_CHAT_LOCATION, CONTEXT_IN_CHAT_INPUT, CONTEXT_IN_CHAT_SESSION, CONTEXT_HAS_DEFAULT_AGENT, CONTEXT_REQUEST, CONTEXT_RESPONSE } from 'vs/workbench/contrib/chat/common/chatContextKeys';
import { IChatDetail, IChatService } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatWidgetHistoryService } from 'vs/workbench/contrib/chat/common/chatWidgetHistoryService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { IViewsService } from 'vs/workbench/services/views/common/viewsService';
export interface IChatViewTitleActionContext {
chatView: ChatViewPane;
}
export function isChatViewTitleActionContext(obj: unknown): obj is IChatViewTitleActionContext {
return obj instanceof Object && 'chatView' in obj;
}
export const CHAT_CATEGORY = localize2('chat.category', 'Chat');
export const CHAT_OPEN_ACTION_ID = 'workbench.action.chat.open';
@ -53,7 +60,7 @@ class OpenChatGlobalAction extends Action2 {
super({
id: CHAT_OPEN_ACTION_ID,
title: localize2('openChat', "Open Chat"),
precondition: CONTEXT_PROVIDER_EXISTS,
precondition: CONTEXT_HAS_DEFAULT_AGENT,
icon: Codicon.commentDiscussion,
f1: false,
category: CHAT_CATEGORY,
@ -70,13 +77,7 @@ class OpenChatGlobalAction extends Action2 {
override async run(accessor: ServicesAccessor, opts?: string | IChatViewOpenOptions): Promise<void> {
opts = typeof opts === 'string' ? { query: opts } : opts;
const chatService = accessor.get(IChatService);
const chatWidgetService = accessor.get(IChatWidgetService);
const providers = chatService.getProviderInfos();
if (!providers.length) {
return;
}
const chatWidget = await chatWidgetService.revealViewForProvider(providers[0].id);
const chatWidget = await showChatView(accessor.get(IViewsService));
if (!chatWidget) {
return;
}
@ -92,15 +93,82 @@ class OpenChatGlobalAction extends Action2 {
}
}
class ChatHistoryAction extends ViewAction<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() {
registerAction2(OpenChatGlobalAction);
registerAction2(ChatHistoryAction);
registerAction2(OpenChatEditorAction);
registerAction2(class ClearChatInputHistoryAction extends Action2 {
constructor() {
super({
id: 'workbench.action.chat.clearInputHistory',
title: localize2('interactiveSession.clearHistory.label', "Clear Input History"),
precondition: CONTEXT_PROVIDER_EXISTS,
precondition: CONTEXT_HAS_DEFAULT_AGENT,
category: CHAT_CATEGORY,
f1: true,
});
@ -116,7 +184,7 @@ export function registerChatActions() {
super({
id: 'workbench.action.chat.clearHistory',
title: localize2('chat.clear.label', "Clear All Workspace Chats"),
precondition: CONTEXT_PROVIDER_EXISTS,
precondition: CONTEXT_HAS_DEFAULT_AGENT,
category: CHAT_CATEGORY,
f1: true,
});
@ -193,79 +261,3 @@ export function registerChatActions() {
}
});
}
export function getOpenChatEditorAction(id: string, label: string, when?: string) {
return class OpenChatEditor extends Action2 {
constructor() {
super({
id: `workbench.action.openChat.${id}`,
title: localize2('interactiveSession.open', "Open Editor ({0})", label),
f1: true,
category: CHAT_CATEGORY,
precondition: ContextKeyExpr.deserialize(when)
});
}
async run(accessor: ServicesAccessor) {
const editorService = accessor.get(IEditorService);
await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options: <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 chatEditorInput = editorService.activeEditor;
if (chatEditorInput instanceof ChatEditorInput && chatEditorInput.providerId) {
if (chatEditorInput instanceof ChatEditorInput) {
await editorService.replaceEditors([{
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);
}
}

View File

@ -7,23 +7,20 @@ import { Codicon } from 'vs/base/common/codicons';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { localize2 } from 'vs/nls';
import { Action2, IAction2Options, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService';
import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { ViewAction } from 'vs/workbench/browser/parts/views/viewPane';
import { ActiveEditorContext } from 'vs/workbench/common/contextkeys';
import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions';
import { CHAT_CATEGORY, isChatViewTitleActionContext } from 'vs/workbench/contrib/chat/browser/actions/chatActions';
import { clearChatEditor } from 'vs/workbench/contrib/chat/browser/actions/chatClear';
import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat';
import { CHAT_VIEW_ID, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat';
import { ChatEditorInput } from 'vs/workbench/contrib/chat/browser/chatEditorInput';
import { ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane';
import { CONTEXT_IN_CHAT_SESSION, CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys';
import { CONTEXT_IN_CHAT_SESSION, CONTEXT_HAS_DEFAULT_AGENT } from 'vs/workbench/contrib/chat/common/chatContextKeys';
export const ACTION_ID_NEW_CHAT = `workbench.action.chat.newChat`;
export function registerNewChatActions() {
registerAction2(class NewChatEditorAction extends Action2 {
constructor() {
super({
@ -31,7 +28,7 @@ export function registerNewChatActions() {
title: localize2('chat.newChat.label', "New Chat"),
icon: Codicon.plus,
f1: false,
precondition: CONTEXT_PROVIDER_EXISTS,
precondition: CONTEXT_HAS_DEFAULT_AGENT,
menu: [{
id: MenuId.EditorTitle,
group: 'navigation',
@ -46,7 +43,6 @@ export function registerNewChatActions() {
}
});
registerAction2(class GlobalClearChatAction extends Action2 {
constructor() {
super({
@ -54,7 +50,7 @@ export function registerNewChatActions() {
title: localize2('chat.newChat.label', "New Chat"),
category: CHAT_CATEGORY,
icon: Codicon.plus,
precondition: CONTEXT_PROVIDER_EXISTS,
precondition: CONTEXT_HAS_DEFAULT_AGENT,
f1: true,
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
@ -64,58 +60,42 @@ export function registerNewChatActions() {
},
when: CONTEXT_IN_CHAT_SESSION
},
menu: {
menu: [{
id: MenuId.ChatContext,
group: 'z_clear'
}
},
{
id: MenuId.ViewTitle,
when: ContextKeyExpr.equals('view', CHAT_VIEW_ID),
group: 'navigation',
order: -1
}]
});
}
run(accessor: ServicesAccessor, ...args: any[]) {
const widgetService = accessor.get(IChatWidgetService);
const context = args[0];
if (isChatViewTitleActionContext(context)) {
// Is running in the Chat view title
announceChatCleared(accessor);
context.chatView.clear();
context.chatView.widget.focusInput();
} else {
// Is running from f1 or keybinding
const widgetService = accessor.get(IChatWidgetService);
const widget = widgetService.lastFocusedWidget;
if (!widget) {
return;
const widget = widgetService.lastFocusedWidget;
if (!widget) {
return;
}
announceChatCleared(accessor);
widget.clear();
widget.focusInput();
}
announceChatCleared(accessor);
widget.clear();
widget.focusInput();
}
});
}
const getNewChatActionDescriptorForViewTitle = (viewId: string, providerId: string): Readonly<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 {
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 { IChatWidgetService, IChatCodeBlockContextProviderService } from 'vs/workbench/contrib/chat/browser/chat';
import { ICodeBlockActionContext, ICodeCompareBlockActionContext } from 'vs/workbench/contrib/chat/browser/codeBlockPart';
import { CONTEXT_IN_CHAT_INPUT, CONTEXT_IN_CHAT_SESSION, CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys';
import { CONTEXT_IN_CHAT_INPUT, CONTEXT_IN_CHAT_SESSION, CONTEXT_HAS_DEFAULT_AGENT } from 'vs/workbench/contrib/chat/common/chatContextKeys';
import { ChatCopyKind, IChatService, IDocumentContext } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatResponseViewModel, isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel';
import { insertCell } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations';
@ -109,7 +109,6 @@ export function registerChatCodeBlockActions() {
if (isResponseVM(context.element)) {
const chatService = accessor.get(IChatService);
chatService.notifyUserAction({
providerId: context.element.providerId,
agentId: context.element.agent?.id,
sessionId: context.element.sessionId,
requestId: context.element.requestId,
@ -155,7 +154,6 @@ export function registerChatCodeBlockActions() {
const element = context.element as IChatResponseViewModel | undefined;
if (element) {
chatService.notifyUserAction({
providerId: element.providerId,
agentId: element.agent?.id,
sessionId: element.sessionId,
requestId: element.requestId,
@ -185,7 +183,7 @@ export function registerChatCodeBlockActions() {
super({
id: 'workbench.action.chat.insertCodeBlock',
title: localize2('interactive.insertCodeBlock.label', "Insert at Cursor"),
precondition: CONTEXT_PROVIDER_EXISTS,
precondition: CONTEXT_HAS_DEFAULT_AGENT,
f1: true,
category: CHAT_CATEGORY,
icon: Codicon.insert,
@ -331,7 +329,6 @@ export function registerChatCodeBlockActions() {
if (isResponseVM(context.element)) {
const chatService = accessor.get(IChatService);
chatService.notifyUserAction({
providerId: context.element.providerId,
agentId: context.element.agent?.id,
sessionId: context.element.sessionId,
requestId: context.element.requestId,
@ -352,7 +349,7 @@ export function registerChatCodeBlockActions() {
super({
id: 'workbench.action.chat.insertIntoNewFile',
title: localize2('interactive.insertIntoNewFile.label', "Insert into New File"),
precondition: CONTEXT_PROVIDER_EXISTS,
precondition: CONTEXT_HAS_DEFAULT_AGENT,
f1: true,
category: CHAT_CATEGORY,
icon: Codicon.newFile,
@ -377,7 +374,6 @@ export function registerChatCodeBlockActions() {
if (isResponseVM(context.element)) {
chatService.notifyUserAction({
providerId: context.element.providerId,
agentId: context.element.agent?.id,
sessionId: context.element.sessionId,
requestId: context.element.requestId,
@ -407,7 +403,7 @@ export function registerChatCodeBlockActions() {
super({
id: 'workbench.action.chat.runInTerminal',
title: localize2('interactive.runInTerminal.label', "Insert into Terminal"),
precondition: CONTEXT_PROVIDER_EXISTS,
precondition: CONTEXT_HAS_DEFAULT_AGENT,
f1: true,
category: CHAT_CATEGORY,
icon: Codicon.terminal,
@ -470,7 +466,6 @@ export function registerChatCodeBlockActions() {
if (isResponseVM(context.element)) {
chatService.notifyUserAction({
providerId: context.element.providerId,
agentId: context.element.agent?.id,
sessionId: context.element.sessionId,
requestId: context.element.requestId,
@ -526,7 +521,7 @@ export function registerChatCodeBlockActions() {
weight: KeybindingWeight.WorkbenchContrib,
when: CONTEXT_IN_CHAT_SESSION,
},
precondition: CONTEXT_PROVIDER_EXISTS,
precondition: CONTEXT_HAS_DEFAULT_AGENT,
f1: true,
category: CHAT_CATEGORY,
});
@ -548,7 +543,7 @@ export function registerChatCodeBlockActions() {
weight: KeybindingWeight.WorkbenchContrib,
when: CONTEXT_IN_CHAT_SESSION,
},
precondition: CONTEXT_PROVIDER_EXISTS,
precondition: CONTEXT_HAS_DEFAULT_AGENT,
f1: true,
category: CHAT_CATEGORY,
});

View File

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

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

View File

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

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 { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions';
import { IQuickChatService, IQuickChatOpenOptions } from 'vs/workbench/contrib/chat/browser/chat';
import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys';
import { IQuickChatOpenOptions, IQuickChatService } from 'vs/workbench/contrib/chat/browser/chat';
import { CONTEXT_HAS_DEFAULT_AGENT } from 'vs/workbench/contrib/chat/common/chatContextKeys';
import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController';
export const ASK_QUICK_QUESTION_ACTION_ID = 'workbench.action.quickchat.toggle';
export function registerQuickChatActions() {
registerAction2(QuickChatGlobalAction);
registerAction2(AskQuickChatAction);
registerAction2(class OpenInChatViewAction extends Action2 {
constructor() {
@ -94,6 +95,7 @@ export function registerQuickChatActions() {
controller.focus();
}
});
}
class QuickChatGlobalAction extends Action2 {
@ -101,7 +103,7 @@ class QuickChatGlobalAction extends Action2 {
super({
id: ASK_QUICK_QUESTION_ACTION_ID,
title: localize2('quickChat', 'Quick Chat'),
precondition: CONTEXT_PROVIDER_EXISTS,
precondition: CONTEXT_HAS_DEFAULT_AGENT,
icon: Codicon.commentDiscussion,
f1: false,
category: CHAT_CATEGORY,
@ -153,35 +155,25 @@ class QuickChatGlobalAction extends Action2 {
if (options?.query) {
options.selection = new Selection(1, options.query.length + 1, 1, options.query.length + 1);
}
quickChatService.toggle(undefined, options);
quickChatService.toggle(options);
}
}
/**
* Returns a provider specific action that will open the quick chat for that provider.
* This is used to include the provider label in the action title so it shows up in
* the command palette.
* @param id The id of the provider
* @param label The label of the provider
* @returns An action that will open the quick chat for this provider
*/
export function getQuickChatActionForProvider(id: string, label: string) {
return class AskQuickChatAction extends Action2 {
constructor() {
super({
id: `workbench.action.openQuickChat.${id}`,
category: CHAT_CATEGORY,
title: localize2('interactiveSession.open', "Open Quick Chat ({0})", label),
f1: true
});
}
class AskQuickChatAction extends Action2 {
constructor() {
super({
id: `workbench.action.openQuickChat`,
category: CHAT_CATEGORY,
title: localize2('interactiveSession.open', "Open Quick Chat"),
f1: true
});
}
override run(accessor: ServicesAccessor, query?: string): void {
const quickChatService = accessor.get(IQuickChatService);
quickChatService.toggle(id, query ? {
query,
selection: new Selection(1, query.length + 1, 1, query.length + 1)
} : undefined);
}
};
override run(accessor: ServicesAccessor, query?: string): void {
const quickChatService = accessor.get(IQuickChatService);
quickChatService.toggle(query ? {
query,
selection: new Selection(1, query.length + 1, 1, query.length + 1)
} : undefined);
}
}

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,9 +5,8 @@
import { isNonEmptyArray } from 'vs/base/common/arrays';
import { Codicon } from 'vs/base/common/codicons';
import { DisposableMap, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { DisposableMap, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { localize, localize2 } from 'vs/nls';
import { registerAction2 } from 'vs/platform/actions/common/actions';
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
@ -15,55 +14,15 @@ import { ILogService } from 'vs/platform/log/common/log';
import { IProductService } from 'vs/platform/product/common/productService';
import { Registry } from 'vs/platform/registry/common/platform';
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IViewContainersRegistry, IViewDescriptor, IViewsRegistry, ViewContainer, ViewContainerLocation, Extensions as ViewExtensions } from 'vs/workbench/common/views';
import { getHistoryAction, getOpenChatEditorAction } from 'vs/workbench/contrib/chat/browser/actions/chatActions';
import { getNewChatAction } from 'vs/workbench/contrib/chat/browser/actions/chatClearActions';
import { getMoveToEditorAction, getMoveToNewWindowAction } from 'vs/workbench/contrib/chat/browser/actions/chatMoveActions';
import { getQuickChatActionForProvider } from 'vs/workbench/contrib/chat/browser/actions/chatQuickInputActions';
import { CHAT_SIDEBAR_PANEL_ID, ChatViewPane, IChatViewOptions } from 'vs/workbench/contrib/chat/browser/chatViewPane';
import { CHAT_VIEW_ID } from 'vs/workbench/contrib/chat/browser/chat';
import { CHAT_SIDEBAR_PANEL_ID, ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane';
import { ChatAgentLocation, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
import { IChatContributionService, IChatProviderContribution, IRawChatParticipantContribution, IRawChatProviderContribution } from 'vs/workbench/contrib/chat/common/chatContributionService';
import { IRawChatParticipantContribution } from 'vs/workbench/contrib/chat/common/chatParticipantContribTypes';
import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
import * as extensionsRegistry from 'vs/workbench/services/extensions/common/extensionsRegistry';
const chatExtensionPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint<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[]>({
extensionPoint: 'chatParticipants',
jsonSchema: {
@ -168,11 +127,9 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution {
private readonly disposables = new DisposableStore();
private _welcomeViewDescriptor?: IViewDescriptor;
private _viewContainer: ViewContainer;
private _registrationDisposables = new Map<string, IDisposable>();
private _participantRegistrationDisposables = new DisposableMap<string>();
constructor(
@IChatContributionService private readonly _chatContributionService: IChatContributionService,
@IChatAgentService private readonly _chatAgentService: IChatAgentService,
@IProductService private readonly productService: IProductService,
@IContextKeyService private readonly contextService: IContextKeyService,
@ -196,20 +153,18 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution {
const contextKeyExpr = ContextKeyExpr.equals(showWelcomeViewConfigKey, true);
const viewsRegistry = Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry);
if (this.contextService.contextMatchesRules(contextKeyExpr)) {
const viewId = this._chatContributionService.getViewIdForProvider(this.productService.chatWelcomeView.welcomeViewId);
this._welcomeViewDescriptor = {
id: viewId,
id: CHAT_VIEW_ID,
name: { original: this.productService.chatWelcomeView.welcomeViewTitle, value: this.productService.chatWelcomeView.welcomeViewTitle },
containerIcon: this._viewContainer.icon,
ctorDescriptor: new SyncDescriptor(ChatViewPane, [<IChatViewOptions>{ providerId: this.productService.chatWelcomeView.welcomeViewId }]),
ctorDescriptor: new SyncDescriptor(ChatViewPane),
canToggleVisibility: false,
canMoveView: true,
order: 100
};
viewsRegistry.registerViews([this._welcomeViewDescriptor], this._viewContainer);
viewsRegistry.registerViewWelcomeContent(viewId, {
viewsRegistry.registerViewWelcomeContent(CHAT_VIEW_ID, {
content: this.productService.chatWelcomeView.welcomeViewContent,
});
} else if (this._welcomeViewDescriptor) {
@ -220,29 +175,6 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution {
}
private handleAndRegisterChatExtensions(): void {
chatExtensionPoint.setHandler((extensions, delta) => {
for (const extension of delta.added) {
const extensionDisposable = new DisposableStore();
for (const providerDescriptor of extension.value) {
this.registerChatProvider(providerDescriptor);
this._chatContributionService.registerChatProvider(providerDescriptor);
}
this._registrationDisposables.set(extension.description.identifier.value, extensionDisposable);
}
for (const extension of delta.removed) {
const registration = this._registrationDisposables.get(extension.description.identifier.value);
if (registration) {
registration.dispose();
this._registrationDisposables.delete(extension.description.identifier.value);
}
for (const providerDescriptor of extension.value) {
this._chatContributionService.deregisterChatProvider(providerDescriptor.id);
}
}
});
chatParticipantExtensionPoint.setHandler((extensions, delta) => {
for (const extension of delta.added) {
for (const providerDescriptor of extension.value) {
@ -261,25 +193,33 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution {
continue;
}
const store = new DisposableStore();
if (providerDescriptor.isDefault) {
store.add(this.registerDefaultParticipant(providerDescriptor));
}
store.add(this._chatAgentService.registerAgent(
providerDescriptor.id,
{
extensionId: extension.description.identifier,
id: providerDescriptor.id,
description: providerDescriptor.description,
metadata: {
isSticky: providerDescriptor.isSticky,
},
name: providerDescriptor.name,
isDefault: providerDescriptor.isDefault,
defaultImplicitVariables: providerDescriptor.defaultImplicitVariables,
locations: isNonEmptyArray(providerDescriptor.locations) ?
providerDescriptor.locations.map(ChatAgentLocation.fromRaw) :
[ChatAgentLocation.Panel],
slashCommands: providerDescriptor.commands ?? []
} satisfies IChatAgentData));
this._participantRegistrationDisposables.set(
getParticipantKey(extension.description.identifier, providerDescriptor.name),
this._chatAgentService.registerAgent(
providerDescriptor.id,
{
extensionId: extension.description.identifier,
id: providerDescriptor.id,
description: providerDescriptor.description,
metadata: {
isSticky: providerDescriptor.isSticky,
},
name: providerDescriptor.name,
isDefault: providerDescriptor.isDefault,
defaultImplicitVariables: providerDescriptor.defaultImplicitVariables,
locations: isNonEmptyArray(providerDescriptor.locations) ?
providerDescriptor.locations.map(ChatAgentLocation.fromRaw) :
[ChatAgentLocation.Panel],
slashCommands: providerDescriptor.commands ?? []
} satisfies IChatAgentData));
store
);
}
}
@ -309,72 +249,26 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution {
return viewContainer;
}
private registerChatProvider(providerDescriptor: IRawChatProviderContribution): IDisposable {
private registerDefaultParticipant(defaultParticipantDescriptor: IRawChatParticipantContribution): IDisposable {
// Register View
const viewId = this._chatContributionService.getViewIdForProvider(providerDescriptor.id);
const viewDescriptor: IViewDescriptor[] = [{
id: viewId,
id: CHAT_VIEW_ID,
containerIcon: this._viewContainer.icon,
containerTitle: this._viewContainer.title.value,
singleViewPaneContainerTitle: this._viewContainer.title.value,
name: { value: providerDescriptor.label, original: providerDescriptor.label },
name: { value: defaultParticipantDescriptor.name, original: defaultParticipantDescriptor.name },
canToggleVisibility: false,
canMoveView: true,
ctorDescriptor: new SyncDescriptor(ChatViewPane, [<IChatViewOptions>{ providerId: providerDescriptor.id }]),
when: ContextKeyExpr.deserialize(providerDescriptor.when)
ctorDescriptor: new SyncDescriptor(ChatViewPane),
}];
Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry).registerViews(viewDescriptor, this._viewContainer);
// Per-provider actions
// Actions in view title
const disposables = new DisposableStore();
disposables.add(registerAction2(getHistoryAction(viewId, providerDescriptor.id)));
disposables.add(registerAction2(getNewChatAction(viewId, providerDescriptor.id)));
disposables.add(registerAction2(getMoveToEditorAction(viewId, providerDescriptor.id)));
disposables.add(registerAction2(getMoveToNewWindowAction(viewId, providerDescriptor.id)));
// "Open Chat" Actions
disposables.add(registerAction2(getOpenChatEditorAction(providerDescriptor.id, providerDescriptor.label, providerDescriptor.when)));
disposables.add(registerAction2(getQuickChatActionForProvider(providerDescriptor.id, providerDescriptor.label)));
return {
dispose: () => {
Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry).deregisterViews(viewDescriptor, this._viewContainer);
Registry.as<IViewContainersRegistry>(ViewExtensions.ViewContainersRegistry).deregisterViewContainer(this._viewContainer);
disposables.dispose();
}
};
return toDisposable(() => {
Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry).deregisterViews(viewDescriptor, this._viewContainer);
});
}
}
registerWorkbenchContribution2(ChatExtensionPointHandler.ID, ChatExtensionPointHandler, WorkbenchPhase.BlockStartup);
function getParticipantKey(extensionId: ExtensionIdentifier, participantName: string): string {
return `${extensionId.value}_${participantName}`;
}
export class ChatContributionService implements IChatContributionService {
declare _serviceBrand: undefined;
private _registeredProviders = new Map<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 { IQuickInputService, IQuickWidget } from 'vs/platform/quickinput/common/quickInput';
import { editorBackground, inputBackground, quickInputBackground, quickInputForeground } from 'vs/platform/theme/common/colorRegistry';
import { IChatWidgetService, IQuickChatService, IQuickChatOpenOptions } from 'vs/workbench/contrib/chat/browser/chat';
import { IChatViewOptions } from 'vs/workbench/contrib/chat/browser/chatViewPane';
import { IQuickChatOpenOptions, IQuickChatService, showChatView } from 'vs/workbench/contrib/chat/browser/chat';
import { ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget';
import { ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents';
import { ChatModel } from 'vs/workbench/contrib/chat/common/chatModel';
import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes';
import { IChatService } from 'vs/workbench/contrib/chat/common/chatService';
import { IViewsService } from 'vs/workbench/services/views/common/viewsService';
export class QuickChatService extends Disposable implements IQuickChatService {
readonly _serviceBrand: undefined;
@ -45,7 +45,7 @@ export class QuickChatService extends Disposable implements IQuickChatService {
}
get enabled(): boolean {
return this.chatService.getProviderInfos().length > 0;
return !!this.chatService.isEnabled(ChatAgentLocation.Panel);
}
get focused(): boolean {
@ -56,13 +56,13 @@ export class QuickChatService extends Disposable implements IQuickChatService {
return dom.isAncestorOfActiveElement(widget);
}
toggle(providerId?: string, options?: IQuickChatOpenOptions): void {
toggle(options?: IQuickChatOpenOptions): void {
// If the input is already shown, hide it. This provides a toggle behavior of the quick
// pick. This should not happen when there is a query.
if (this.focused && !options?.query) {
this.close();
} else {
this.open(providerId, options);
this.open(options);
// If this is a partial query, the value should be cleared when closed as otherwise it
// would remain for the next time the quick chat is opened in any context.
if (options?.isPartialQuery) {
@ -74,7 +74,7 @@ export class QuickChatService extends Disposable implements IQuickChatService {
}
}
open(providerId?: string, options?: IQuickChatOpenOptions): void {
open(options?: IQuickChatOpenOptions): void {
if (this._input) {
if (this._currentChat && options?.query) {
this._currentChat.focus();
@ -87,15 +87,6 @@ export class QuickChatService extends Disposable implements IQuickChatService {
return this.focus();
}
// Check if any providers are available. If not, show nothing
// This shouldn't be needed because of the precondition, but just in case
const providerInfo = providerId
? this.chatService.getProviderInfos().find(info => info.id === providerId)
: this.chatService.getProviderInfos()[0];
if (!providerInfo) {
return;
}
const disposableStore = new DisposableStore();
this._input = this.quickInputService.createQuickWidget();
@ -108,9 +99,7 @@ export class QuickChatService extends Disposable implements IQuickChatService {
this._input.show();
if (!this._currentChat) {
this._currentChat = this.instantiationService.createInstance(QuickChat, {
providerId: providerInfo.id,
});
this._currentChat = this.instantiationService.createInstance(QuickChat);
// show needs to come after the quickpick is shown
this._currentChat.render(this._container);
@ -160,12 +149,11 @@ class QuickChat extends Disposable {
private _deferUpdatingDynamicLayout: boolean = false;
constructor(
private readonly _options: IChatViewOptions,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IChatService private readonly chatService: IChatService,
@IChatWidgetService private readonly _chatWidgetService: IChatWidgetService,
@ILayoutService private readonly layoutService: ILayoutService
@ILayoutService private readonly layoutService: ILayoutService,
@IViewsService private readonly viewsService: IViewsService,
) {
super();
}
@ -288,7 +276,7 @@ class QuickChat extends Disposable {
}
async openChatView(): Promise<void> {
const widget = await this._chatWidgetService.revealViewForProvider(this._options.providerId);
const widget = await showChatView(this.viewsService);
if (!widget?.viewModel || !this.model) {
return;
}
@ -325,7 +313,7 @@ class QuickChat extends Disposable {
}
private updateModel(): void {
this.model ??= this.chatService.startSession(this._options.providerId, CancellationToken.None);
this.model ??= this.chatService.startSession(CancellationToken.None);
if (!this.model) {
throw new Error('Could not start chat session');
}

View File

@ -24,13 +24,11 @@ import { SIDE_BAR_FOREGROUND } from 'vs/workbench/common/theme';
import { IViewDescriptorService } from 'vs/workbench/common/views';
import { IChatViewPane } from 'vs/workbench/contrib/chat/browser/chat';
import { IChatViewState, ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget';
import { ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents';
import { IChatModel } from 'vs/workbench/contrib/chat/common/chatModel';
import { ChatAgentLocation, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
import { CHAT_PROVIDER_ID } from 'vs/workbench/contrib/chat/common/chatParticipantContribTypes';
import { ChatModelInitState, IChatModel } from 'vs/workbench/contrib/chat/common/chatModel';
import { IChatService } from 'vs/workbench/contrib/chat/common/chatService';
export interface IChatViewOptions {
readonly providerId: string;
}
import { IChatViewTitleActionContext } from 'vs/workbench/contrib/chat/browser/actions/chatActions';
interface IViewPaneState extends IChatViewState {
sessionId?: string;
@ -38,8 +36,6 @@ interface IViewPaneState extends IChatViewState {
export const CHAT_SIDEBAR_PANEL_ID = 'workbench.panel.chatSidebar';
export class ChatViewPane extends ViewPane implements IChatViewPane {
static ID = 'workbench.panel.chat.view';
private _widget!: ChatWidget;
get widget(): ChatWidget { return this._widget; }
@ -50,7 +46,6 @@ export class ChatViewPane extends ViewPane implements IChatViewPane {
private didUnregisterProvider = false;
constructor(
private readonly chatViewOptions: IChatViewOptions,
options: IViewPaneOptions,
@IKeybindingService keybindingService: IKeybindingService,
@IContextMenuService contextMenuService: IContextMenuService,
@ -64,46 +59,53 @@ export class ChatViewPane extends ViewPane implements IChatViewPane {
@IHoverService hoverService: IHoverService,
@IStorageService private readonly storageService: IStorageService,
@IChatService private readonly chatService: IChatService,
@IChatAgentService private readonly chatAgentService: IChatAgentService,
@ILogService private readonly logService: ILogService,
) {
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService, hoverService);
// View state for the ViewPane is currently global per-provider basically, but some other strictly per-model state will require a separate memento.
this.memento = new Memento('interactive-session-view-' + this.chatViewOptions.providerId, this.storageService);
this.memento = new Memento('interactive-session-view-' + CHAT_PROVIDER_ID, this.storageService);
this.viewState = this.memento.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE) as IViewPaneState;
this._register(this.chatService.onDidRegisterProvider(({ providerId }) => {
if (providerId === this.chatViewOptions.providerId && !this._widget?.viewModel) {
const sessionId = this.getSessionId();
const model = sessionId ? this.chatService.getOrRestoreSession(sessionId) : undefined;
this._register(this.chatAgentService.onDidChangeAgents(() => {
if (this.chatAgentService.getDefaultAgent(ChatAgentLocation.Panel)) {
if (!this._widget?.viewModel) {
const sessionId = this.getSessionId();
const model = sessionId ? this.chatService.getOrRestoreSession(sessionId) : undefined;
// The widget may be hidden at this point, because welcome views were allowed. Use setVisible to
// avoid doing a render while the widget is hidden. This is changing the condition in `shouldShowWelcome`
// so it should fire onDidChangeViewWelcomeState.
try {
this._widget.setVisible(false);
this.updateModel(model);
this.didProviderRegistrationFail = false;
this.didUnregisterProvider = false;
this._onDidChangeViewWelcomeState.fire();
} finally {
this.widget.setVisible(true);
// The widget may be hidden at this point, because welcome views were allowed. Use setVisible to
// avoid doing a render while the widget is hidden. This is changing the condition in `shouldShowWelcome`
// so it should fire onDidChangeViewWelcomeState.
try {
this._widget.setVisible(false);
this.updateModel(model);
this.didProviderRegistrationFail = false;
this.didUnregisterProvider = false;
this._onDidChangeViewWelcomeState.fire();
} finally {
this.widget.setVisible(true);
}
}
}
}));
this._register(this.chatService.onDidUnregisterProvider(({ providerId }) => {
if (providerId === this.chatViewOptions.providerId) {
} else if (this._widget?.viewModel?.initState === ChatModelInitState.Initialized) {
// Model is initialized, and the default agent disappeared, so show welcome view
this.didUnregisterProvider = true;
this._onDidChangeViewWelcomeState.fire();
}
}));
}
override getActionsContext(): IChatViewTitleActionContext {
return {
chatView: this
};
}
private updateModel(model?: IChatModel | undefined, viewState?: IViewPaneState): void {
this.modelDisposables.clear();
model = model ?? (this.chatService.transferredSessionData?.sessionId
? this.chatService.getOrRestoreSession(this.chatService.transferredSessionData.sessionId)
: this.chatService.startSession(this.chatViewOptions.providerId, CancellationToken.None));
: this.chatService.startSession(CancellationToken.None));
if (!model) {
throw new Error('Could not start chat session');
}
@ -113,7 +115,7 @@ export class ChatViewPane extends ViewPane implements IChatViewPane {
}
override shouldShowWelcome(): boolean {
const noPersistedSessions = !this.chatService.hasSessions(this.chatViewOptions.providerId);
const noPersistedSessions = !this.chatService.hasSessions();
return this.didUnregisterProvider || !this._widget?.viewModel && (noPersistedSessions || this.didProviderRegistrationFail);
}
@ -155,7 +157,7 @@ export class ChatViewPane extends ViewPane implements IChatViewPane {
// Render the welcome view if this session gets disposed at any point,
// including if the provider registration fails
const disposeListener = sessionId ? this._register(this.chatService.onDidDisposeSession((e) => {
if (e.reason === 'initializationFailed' && e.providerId === this.chatViewOptions.providerId) {
if (e.reason === 'initializationFailed') {
this.didProviderRegistrationFail = true;
disposeListener?.dispose();
this._onDidChangeViewWelcomeState.fire();
@ -174,7 +176,7 @@ export class ChatViewPane extends ViewPane implements IChatViewPane {
this._widget.acceptInput(query);
}
async clear(): Promise<void> {
clear(): void {
if (this.widget.viewModel) {
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 { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart';
import { ChatListDelegate, ChatListItemRenderer, IChatRendererDelegate } from 'vs/workbench/contrib/chat/browser/chatListRenderer';
import { IChatListItemRendererOptions } from './chat';
import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions';
import { ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane';
import { ChatAgentLocation, IChatAgentCommand, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
import { CONTEXT_CHAT_INPUT_HAS_AGENT, CONTEXT_CHAT_LOCATION, CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_SESSION, CONTEXT_RESPONSE_FILTERED } from 'vs/workbench/contrib/chat/common/chatContextKeys';
import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService';
import { ChatModelInitState, IChatModel } from 'vs/workbench/contrib/chat/common/chatModel';
import { ChatRequestAgentPart, IParsedChatRequest, chatAgentLeader, chatSubcommandLeader, extractAgentAndCommand } from 'vs/workbench/contrib/chat/common/chatParserTypes';
import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser';
@ -42,7 +39,7 @@ import { IChatFollowup, IChatService } from 'vs/workbench/contrib/chat/common/ch
import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands';
import { ChatViewModel, IChatResponseViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel';
import { CodeBlockModelCollection } from 'vs/workbench/contrib/chat/common/codeBlockModelCollection';
import { IViewsService } from 'vs/workbench/services/views/common/viewsService';
import { IChatListItemRendererOptions } from './chat';
const $ = dom.$;
@ -242,10 +239,6 @@ export class ChatWidget extends Disposable implements IChatWidget {
return !!this.viewOptions.supportsFileReferences;
}
get providerId(): string {
return this.viewModel?.providerId || '';
}
get input(): ChatInputPart {
return this.inputPart;
}
@ -563,7 +556,6 @@ export class ChatWidget extends Disposable implements IChatWidget {
}
this.chatService.notifyUserAction({
providerId: this.viewModel.providerId,
sessionId: this.viewModel.sessionId,
requestId: e.response.requestId,
agentId: e.response.agent?.id,
@ -641,7 +633,7 @@ export class ChatWidget extends Disposable implements IChatWidget {
this.viewModel = undefined;
this.onDidChangeItems();
}));
this.inputPart.setState(model.providerId, viewState.inputValue);
this.inputPart.setState(viewState.inputValue);
this.contribs.forEach(c => {
if (c.setInputState && viewState.inputState?.[c.id]) {
c.setInputState(viewState.inputState?.[c.id]);
@ -914,10 +906,7 @@ export class ChatWidgetService implements IChatWidgetService {
return this._lastFocusedWidget;
}
constructor(
@IViewsService private readonly viewsService: IViewsService,
@IChatContributionService private readonly chatContributionService: IChatContributionService,
) { }
constructor() { }
getWidgetByInputUri(uri: URI): ChatWidget | undefined {
return this._widgets.find(w => isEqual(w.inputUri, uri));
@ -927,13 +916,6 @@ export class ChatWidgetService implements IChatWidgetService {
return this._widgets.find(w => w.viewModel?.sessionId === sessionId);
}
async revealViewForProvider(providerId: string): Promise<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 {
if (widget === this._lastFocusedWidget) {
return;

View File

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

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

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

View File

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

View File

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

View File

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

View File

@ -3,57 +3,56 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/voiceChatActions';
import { Event } from 'vs/base/common/event';
import { firstOrDefault } from 'vs/base/common/arrays';
import { RunOnceScheduler, disposableTimeout } from 'vs/base/common/async';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { Codicon } from 'vs/base/common/codicons';
import { Color } from 'vs/base/common/color';
import { Event } from 'vs/base/common/event';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { ThemeIcon } from 'vs/base/common/themables';
import { assertIsDefined, isNumber } from 'vs/base/common/types';
import 'vs/css!./media/voiceChatActions';
import { getCodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { localize, localize2 } from 'vs/nls';
import { Action2, IAction2Options, MenuId } from 'vs/platform/actions/common/actions';
import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { ContextKeyExpr, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { spinningLoading } from 'vs/platform/theme/common/iconRegistry';
import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions';
import { IChatWidget, IChatWidgetService, IQuickChatService } from 'vs/workbench/contrib/chat/browser/chat';
import { IChatService, KEYWORD_ACTIVIATION_SETTING_ID } from 'vs/workbench/contrib/chat/common/chatService';
import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
import { CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_INPUT, CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys';
import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController';
import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands';
import { ActiveEditorContext } from 'vs/workbench/common/contextkeys';
import { IViewsService } from 'vs/workbench/services/views/common/viewsService';
import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService';
import { HasSpeechProvider, ISpeechService, KeywordRecognitionStatus, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService';
import { RunOnceScheduler, disposableTimeout } from 'vs/base/common/async';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { ACTIVITY_BAR_BADGE_BACKGROUND } from 'vs/workbench/common/theme';
import { ColorScheme } from 'vs/platform/theme/common/theme';
import { Color } from 'vs/base/common/color';
import { contrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { assertIsDefined, isNumber } from 'vs/base/common/types';
import { AccessibilityVoiceSettingId, SpeechTimeoutDefault, accessibilityConfigurationNodeBase } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration';
import { IChatExecuteActionContext } from 'vs/workbench/contrib/chat/browser/actions/chatExecuteActions';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { Registry } from 'vs/platform/registry/common/platform';
import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry';
import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/browser/statusbar';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { getCodeEditor } from 'vs/editor/browser/editorBrowser';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions';
import { IVoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChat';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ThemeIcon } from 'vs/base/common/themables';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { ProgressLocation } from 'vs/platform/progress/common/progress';
import { TerminalChatController, TerminalChatContextKeys } from 'vs/workbench/contrib/terminal/browser/terminalContribExports';
import { Registry } from 'vs/platform/registry/common/platform';
import { contrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry';
import { spinningLoading } from 'vs/platform/theme/common/iconRegistry';
import { ColorScheme } from 'vs/platform/theme/common/theme';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { ActiveEditorContext } from 'vs/workbench/common/contextkeys';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { ACTIVITY_BAR_BADGE_BACKGROUND } from 'vs/workbench/common/theme';
import { AccessibilityVoiceSettingId, SpeechTimeoutDefault, accessibilityConfigurationNodeBase } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration';
import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions';
import { IChatExecuteActionContext } from 'vs/workbench/contrib/chat/browser/actions/chatExecuteActions';
import { CHAT_VIEW_ID, IChatWidget, IChatWidgetService, IQuickChatService, showChatView } from 'vs/workbench/contrib/chat/browser/chat';
import { ChatAgentLocation, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
import { CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_INPUT, CONTEXT_HAS_DEFAULT_AGENT } from 'vs/workbench/contrib/chat/common/chatContextKeys';
import { IChatService, KEYWORD_ACTIVIATION_SETTING_ID } from 'vs/workbench/contrib/chat/common/chatService';
import { IVoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChat';
import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions';
import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController';
import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
import { NOTEBOOK_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
import { HasSpeechProvider, ISpeechService, KeywordRecognitionStatus, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService';
import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
import { TerminalChatContextKeys, TerminalChatController } from 'vs/workbench/contrib/terminal/browser/terminalContribExports';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService';
import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/browser/statusbar';
import { IViewsService } from 'vs/workbench/services/views/common/viewsService';
const CONTEXT_VOICE_CHAT_GETTING_READY = new RawContextKey<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.") });
@ -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_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));
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> {
const chatWidgetService = accessor.get(IChatWidgetService);
const viewsService = accessor.get(IViewsService);
const chatContributionService = accessor.get(IChatContributionService);
const quickChatService = accessor.get(IQuickChatService);
const layoutService = accessor.get(IWorkbenchLayoutService);
const editorService = accessor.get(IEditorService);
@ -126,11 +124,11 @@ class VoiceChatSessionControllerFactory {
layoutService.hasFocus(Parts.PANEL_PART) ||
layoutService.hasFocus(Parts.AUXILIARYBAR_PART)
) {
return VoiceChatSessionControllerFactory.doCreateForChatView(chatInput, viewsService, chatContributionService);
return VoiceChatSessionControllerFactory.doCreateForChatView(chatInput, viewsService);
}
if (layoutService.hasFocus(Parts.EDITOR_PART)) {
return VoiceChatSessionControllerFactory.doCreateForChatEditor(chatInput, viewsService, chatContributionService);
return VoiceChatSessionControllerFactory.doCreateForChatEditor(chatInput, viewsService);
}
return VoiceChatSessionControllerFactory.doCreateForQuickChat(chatInput, quickChatService);
@ -150,7 +148,7 @@ class VoiceChatSessionControllerFactory {
if (context === 'view' || context === 'focused' /* fallback in case 'focused' was not successful */) {
const chatView = await VoiceChatSessionControllerFactory.revealChatView(accessor);
if (chatView) {
return VoiceChatSessionControllerFactory.doCreateForChatView(chatView, viewsService, chatContributionService);
return VoiceChatSessionControllerFactory.doCreateForChatView(chatView, viewsService);
}
}
@ -190,31 +188,29 @@ class VoiceChatSessionControllerFactory {
}
static async revealChatView(accessor: ServicesAccessor): Promise<IChatWidget | undefined> {
const chatWidgetService = accessor.get(IChatWidgetService);
const chatService = accessor.get(IChatService);
const provider = firstOrDefault(chatService.getProviderInfos());
if (provider) {
return chatWidgetService.revealViewForProvider(provider.id);
const viewsService = accessor.get(IViewsService);
if (chatService.isEnabled(ChatAgentLocation.Panel)) {
return showChatView(viewsService);
}
return undefined;
}
private static doCreateForChatView(chatView: IChatWidget, viewsService: IViewsService, chatContributionService: IChatContributionService): IVoiceChatSessionController {
return VoiceChatSessionControllerFactory.doCreateForChatViewOrEditor('view', chatView, viewsService, chatContributionService);
private static doCreateForChatView(chatView: IChatWidget, viewsService: IViewsService): IVoiceChatSessionController {
return VoiceChatSessionControllerFactory.doCreateForChatViewOrEditor('view', chatView, viewsService);
}
private static doCreateForChatEditor(chatView: IChatWidget, viewsService: IViewsService, chatContributionService: IChatContributionService): IVoiceChatSessionController {
return VoiceChatSessionControllerFactory.doCreateForChatViewOrEditor('editor', chatView, viewsService, chatContributionService);
private static doCreateForChatEditor(chatView: IChatWidget, viewsService: IViewsService): IVoiceChatSessionController {
return VoiceChatSessionControllerFactory.doCreateForChatViewOrEditor('editor', chatView, viewsService);
}
private static doCreateForChatViewOrEditor(context: 'view' | 'editor', chatView: IChatWidget, viewsService: IViewsService, chatContributionService: IChatContributionService): IVoiceChatSessionController {
private static doCreateForChatViewOrEditor(context: 'view' | 'editor', chatView: IChatWidget, viewsService: IViewsService): IVoiceChatSessionController {
return {
context,
onDidAcceptInput: chatView.onDidAcceptInput,
// TODO@bpasero cancellation needs to work better for chat editors that are not view bound
onDidCancelInput: Event.filter(viewsService.onDidChangeViewVisibility, e => e.id === chatContributionService.getViewIdForProvider(chatView.providerId)),
onDidCancelInput: Event.filter(viewsService.onDidChangeViewVisibility, e => e.id === CHAT_VIEW_ID),
focusInput: () => chatView.focusInput(),
acceptInput: () => chatView.acceptInput(),
updateInput: text => chatView.setInput(text),
@ -882,7 +878,7 @@ export class KeywordActivationContribution extends Disposable implements IWorkbe
@IInstantiationService instantiationService: IInstantiationService,
@IEditorService private readonly editorService: IEditorService,
@IHostService private readonly hostService: IHostService,
@IChatService private readonly chatService: IChatService
@IChatAgentService private readonly chatAgentService: IChatAgentService,
) {
super();
@ -897,7 +893,12 @@ export class KeywordActivationContribution extends Disposable implements IWorkbe
this.handleKeywordActivation();
}));
this._register(this.chatService.onDidRegisterProvider(() => this.updateConfiguration()));
const onDidAddDefaultAgent = this._register(this.chatAgentService.onDidChangeAgents(() => {
if (this.chatAgentService.getDefaultAgent(ChatAgentLocation.Panel)) {
this.updateConfiguration();
onDidAddDefaultAgent.dispose();
}
}));
this._register(this.speechService.onDidStartSpeechToTextSession(() => this.handleKeywordActivation()));
this._register(this.speechService.onDidEndSpeechToTextSession(() => this.handleKeywordActivation()));
@ -910,7 +911,7 @@ export class KeywordActivationContribution extends Disposable implements IWorkbe
}
private updateConfiguration(): void {
if (!this.speechService.hasSpeechProvider || this.chatService.getProviderInfos().length === 0) {
if (!this.speechService.hasSpeechProvider || !this.chatAgentService.getDefaultAgent(ChatAgentLocation.Panel)) {
return; // these settings require a speech and chat provider
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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 { Event } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents';
import { ChatModel, IChatModel, IChatRequestVariableData, ISerializableChatData } from 'vs/workbench/contrib/chat/common/chatModel';
import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes';
import { IChatCompleteResponse, IChatDetail, IChatProvider, IChatProviderInfo, IChatSendRequestData, IChatService, IChatTransferredSessionData, IChatUserActionEvent } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatCompleteResponse, IChatDetail, IChatProviderInfo, IChatSendRequestData, IChatService, IChatTransferredSessionData, IChatUserActionEvent } from 'vs/workbench/contrib/chat/common/chatService';
export class MockChatService implements IChatService {
_serviceBrand: undefined;
transferredSessionData: IChatTransferredSessionData | undefined;
hasSessions(providerId: string): boolean {
isEnabled(location: ChatAgentLocation): boolean {
throw new Error('Method not implemented.');
}
hasSessions(): boolean {
throw new Error('Method not implemented.');
}
getProviderInfos(): IChatProviderInfo[] {
throw new Error('Method not implemented.');
}
startSession(providerId: string, token: CancellationToken): ChatModel | undefined {
startSession(token: CancellationToken): ChatModel | undefined {
throw new Error('Method not implemented.');
}
getSession(sessionId: string): IChatModel | undefined {
return {} as IChatModel;
}
getSessionId(sessionProviderId: number): string | undefined {
throw new Error('Method not implemented.');
}
getOrRestoreSession(sessionId: string): IChatModel | undefined {
throw new Error('Method not implemented.');
}
loadSessionFromContent(data: ISerializableChatData): IChatModel | undefined {
throw new Error('Method not implemented.');
}
onDidRegisterProvider: Event<{ providerId: string }> = undefined!;
onDidUnregisterProvider: Event<{ providerId: string }> = undefined!;
registerProvider(provider: IChatProvider): IDisposable {
throw new Error('Method not implemented.');
}
/**
* Returns whether the request was accepted.
*/
@ -74,7 +68,7 @@ export class MockChatService implements IChatService {
notifyUserAction(event: IChatUserActionEvent): void {
throw new Error('Method not implemented.');
}
onDidDisposeSession: Event<{ sessionId: string; providerId: string; reason: 'initializationFailed' | 'cleared' }> = undefined!;
onDidDisposeSession: Event<{ sessionId: string; reason: 'initializationFailed' | 'cleared' }> = undefined!;
transferChatSession(transferredSessionData: IChatTransferredSessionData, toWorkspace: URI): void {
throw new Error('Method not implemented.');

View File

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

View File

@ -50,7 +50,7 @@ export class InlineChatContentWidget implements IContentWidget {
@IContextKeyService contextKeyService: IContextKeyService,
) {
this._defaultChatModel = this._store.add(instaService.createInstance(ChatModel, `inlineChatDefaultModel/editorContentWidgetPlaceholder`, undefined));
this._defaultChatModel = this._store.add(instaService.createInstance(ChatModel, undefined));
const scopedInstaService = instaService.createChild(
new ServiceCollection([

View File

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

View File

@ -2,41 +2,40 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import { Emitter, Event } from 'vs/base/common/event';
import { EditMode, IInlineChatSession, IInlineChatService, IInlineChatSessionProvider, InlineChatResponseFeedbackKind, IInlineChatProgressItem, IInlineChatResponse, IInlineChatRequest, InlineChatResponseType, IInlineChatBulkEditResponse } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
import { Range } from 'vs/editor/common/core/range';
import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IModelService } from 'vs/editor/common/services/model';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { DisposableMap, DisposableStore, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel';
import { ILogService } from 'vs/platform/log/common/log';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Iterable } from 'vs/base/common/iterator';
import { raceCancellation } from 'vs/base/common/async';
import { Recording, IInlineChatSessionService, ISessionKeyComputer, IInlineChatSessionEvent, IInlineChatSessionEndEvent } from './inlineChatSessionService';
import { EmptyResponse, ErrorResponse, HunkData, ReplyResponse, Session, SessionExchange, SessionWholeRange, StashedSession, TelemetryData, TelemetryDataClassification } from './inlineChatSession';
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker';
import { ITextModel, IValidEditOperation } from 'vs/editor/common/model';
import { Schemas } from 'vs/base/common/network';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { generateUuid } from 'vs/base/common/uuid';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput';
import { DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor';
import { IChatFollowup, IChatProgress, IChatService, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
import { ChatAgentLocation, IChatAgentCommand, IChatAgentData, IChatAgentHistoryEntry, IChatAgentImplementation, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
import { Progress } from 'vs/platform/progress/common/progress';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { coalesceInPlace, isNonEmptyArray } from 'vs/base/common/arrays';
import { MarkdownString } from 'vs/base/common/htmlContent';
import { TextEdit } from 'vs/editor/common/languages';
import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
import { raceCancellation } from 'vs/base/common/async';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Codicon } from 'vs/base/common/codicons';
import { CancellationError } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { MarkdownString } from 'vs/base/common/htmlContent';
import { Iterable } from 'vs/base/common/iterator';
import { DisposableStore, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { LRUCache } from 'vs/base/common/map';
import { Schemas } from 'vs/base/common/network';
import { URI } from 'vs/base/common/uri';
import { generateUuid } from 'vs/base/common/uuid';
import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { Range } from 'vs/editor/common/core/range';
import { TextEdit } from 'vs/editor/common/languages';
import { ITextModel, IValidEditOperation } from 'vs/editor/common/model';
import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel';
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker';
import { IModelService } from 'vs/editor/common/services/model';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
import { Progress } from 'vs/platform/progress/common/progress';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor';
import { ChatAgentLocation, IChatAgentCommand, IChatAgentData, IChatAgentHistoryEntry, IChatAgentImplementation, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
import { IChatFollowup, IChatProgress, IChatService, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
import { EditMode, IInlineChatBulkEditResponse, IInlineChatProgressItem, IInlineChatRequest, IInlineChatResponse, IInlineChatService, IInlineChatSession, InlineChatResponseFeedbackKind, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput';
import { EmptyResponse, ErrorResponse, HunkData, ReplyResponse, Session, SessionExchange, SessionWholeRange, StashedSession, TelemetryData, TelemetryDataClassification } from './inlineChatSession';
import { IInlineChatSessionEndEvent, IInlineChatSessionEvent, IInlineChatSessionService, ISessionKeyComputer, Recording } from './inlineChatSessionService';
class BridgeAgent implements IChatAgentImplementation {
@ -299,34 +298,6 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService {
this._store.add(this._chatAgentService.onDidChangeAgents(() => addOrRemoveBridgeAgent()));
const brigdeAgent = this._store.add(new MutableDisposable());
addOrRemoveBridgeAgent();
// MARK: register fake chat provider
const mapping = this._store.add(new DisposableMap<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() {
@ -335,10 +306,6 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService {
this._sessions.clear();
}
private _asChatProviderBrigdeName(provider: IInlineChatSessionProvider) {
return `inlinechat:${provider.label}:${ExtensionIdentifier.toKey(provider.extensionId)}`;
}
async createSession(editor: IActiveCodeEditor, options: { editMode: EditMode; wholeRange?: Range }, token: CancellationToken): Promise<Session | undefined> {
const provider = Iterable.first(this._inlineChatService.getAllProvider());
@ -347,7 +314,7 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService {
return undefined;
}
const chatModel = this._chatService.startSession(this._asChatProviderBrigdeName(provider), token);
const chatModel = this._chatService.startSession(token);
if (!chatModel) {
this._logService.trace('[IE] NO chatModel found');
return undefined;

View File

@ -93,7 +93,6 @@ export interface IInlineChatWidgetConstructionOptions {
export interface IInlineChatMessage {
message: IMarkdownString;
requestId: string;
providerId: string;
}
export interface IInlineChatMessageAppender {
@ -302,9 +301,9 @@ export class InlineChatWidget {
// LEGACY - default chat model
// this is only here for as long as we offer updateChatMessage
this._defaultChatModel = this._store.add(this._instantiationService.createInstance(ChatModel, `inlineChatDefaultModel/${location}`, undefined));
this._defaultChatModel = this._store.add(this._instantiationService.createInstance(ChatModel, undefined));
this._defaultChatModel.startInitialize();
this._defaultChatModel.initialize({ id: 1 }, undefined);
this._defaultChatModel.initialize(undefined);
this.setChatModel(this._defaultChatModel);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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