Add chatParticipant contribution point (#206474)

* Add package.json registration for chat agents

* Update for tests

* Separate static and dynamic chat agent parts

* Handle participant registration correctly

* Fix tests

* Fix test

* Remove commented code

* Fix more tests

* Pluralize

* Pluralize test contribution
This commit is contained in:
Rob Lourens 2024-02-29 11:52:12 -03:00 committed by GitHub
parent 4e81df7ea9
commit 00abefa3e2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 477 additions and 356 deletions

View file

@ -64,6 +64,19 @@
},
"icon": "media/icon.png",
"contributes": {
"chatParticipants": [
{
"name": "participant",
"description": "test",
"isDefault": true,
"commands": [
{
"name": "hello",
"description": "Hello"
}
]
}
],
"configuration": {
"type": "object",
"title": "Test Config",

View file

@ -45,11 +45,6 @@ suite('chat', () => {
return null;
});
participant.isDefault = true;
participant.commandProvider = {
provideCommands: (_token) => {
return [{ name: 'hello', description: 'Hello' }];
}
};
disposables.push(participant);
return emitter.event;
}
@ -102,11 +97,6 @@ suite('chat', () => {
return { metadata: { key: 'value' } };
});
participant.isDefault = true;
participant.commandProvider = {
provideCommands: (_token) => {
return [{ name: 'hello', description: 'Hello' }];
}
};
participant.followupProvider = {
provideFollowups(result, _token) {
deferred.complete(result);

View file

@ -19,8 +19,8 @@ import { ExtHostChatAgentsShape2, ExtHostContext, IChatProgressDto, IExtensionCh
import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat';
import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart';
import { AddDynamicVariableAction, IAddDynamicVariableContext } from 'vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables';
import { IChatAgentCommand, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
import { IChatModel } from 'vs/workbench/contrib/chat/common/chatModel';
import { IChatAgentImplementation, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService';
import { ChatRequestAgentPart } from 'vs/workbench/contrib/chat/common/chatParserTypes';
import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser';
import { IChatFollowup, IChatProgress, IChatService } from 'vs/workbench/contrib/chat/common/chatService';
@ -29,7 +29,6 @@ import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/ext
type AgentData = {
dispose: () => void;
name: string;
hasSlashCommands?: boolean;
hasFollowups?: boolean;
};
@ -49,6 +48,7 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA
@ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService,
@IChatWidgetService private readonly _chatWidgetService: IChatWidgetService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IChatContributionService private readonly _chatContributionService: IChatContributionService,
) {
super();
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatAgents2);
@ -76,12 +76,13 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA
this._agents.deleteAndDispose(handle);
}
$registerAgent(handle: number, extension: ExtensionIdentifier, name: string, metadata: IExtensionChatAgentMetadata): void {
const lastSlashCommands: WeakMap<IChatModel, IChatAgentCommand[]> = new WeakMap();
const d = this._chatAgentService.registerAgent({
id: name,
extensionId: extension,
metadata: revive(metadata),
$registerAgent(handle: number, extension: ExtensionIdentifier, name: string, metadata: IExtensionChatAgentMetadata, allowDynamic: boolean): void {
const staticAgentRegistration = this._chatContributionService.registeredParticipants.find(p => p.extensionId.value === extension.value && p.name === name);
if (!staticAgentRegistration && !allowDynamic) {
throw new Error(`chatParticipant must be declared in package.json: ${name}`);
}
const impl: IChatAgentImplementation = {
invoke: async (request, progress, history, token) => {
this._pendingProgress.set(request.requestId, progress);
try {
@ -97,31 +98,31 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA
return this._proxy.$provideFollowups(request, handle, result, token);
},
getLastSlashCommands: (model: IChatModel) => {
return lastSlashCommands.get(model);
},
provideSlashCommands: async (model, history, token) => {
if (!this._agents.get(handle)?.hasSlashCommands) {
return []; // save an IPC call
}
const commands = await this._proxy.$provideSlashCommands(handle, { history }, token);
if (model) {
lastSlashCommands.set(model, commands);
}
return commands;
},
provideWelcomeMessage: (token: CancellationToken) => {
return this._proxy.$provideWelcomeMessage(handle, token);
},
provideSampleQuestions: (token: CancellationToken) => {
return this._proxy.$provideSampleQuestions(handle, token);
}
});
};
let disposable: IDisposable;
if (!staticAgentRegistration && allowDynamic) {
disposable = this._chatAgentService.registerDynamicAgent(
{
id: name,
extensionId: extension,
metadata: revive(metadata),
slashCommands: [],
},
impl);
} else {
disposable = this._chatAgentService.registerAgent(name, impl);
}
this._agents.set(handle, {
name,
dispose: d.dispose,
hasSlashCommands: metadata.hasSlashCommands,
dispose: disposable.dispose,
hasFollowups: metadata.hasFollowups
});
}
@ -131,7 +132,6 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA
if (!data) {
throw new Error(`No agent with handle ${handle} registered`);
}
data.hasSlashCommands = metadataUpdate.hasSlashCommands;
data.hasFollowups = metadataUpdate.hasFollowups;
this._chatAgentService.updateAgent(data.name, revive(metadataUpdate));
}

View file

@ -50,12 +50,12 @@ import * as tasks from 'vs/workbench/api/common/shared/tasks';
import { SaveReason } from 'vs/workbench/common/editor';
import { IRevealOptions, ITreeItem, IViewBadge } from 'vs/workbench/common/views';
import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
import { IChatAgentCommand, IChatAgentMetadata, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents';
import { IChatAgentMetadata, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents';
import { IChatProgressResponseContent } from 'vs/workbench/contrib/chat/common/chatModel';
import { IChatMessage, IChatResponseFragment, ILanguageModelChatMetadata } from 'vs/workbench/contrib/chat/common/languageModels';
import { IChatDynamicRequest, IChatProgress, IChatResponseErrorDetails, IChatUserActionEvent, InteractiveSessionVoteDirection, IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatDynamicRequest, IChatFollowup, IChatProgress, IChatResponseErrorDetails, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatRequestVariableValue, IChatVariableData, IChatVariableResolverProgress } from 'vs/workbench/contrib/chat/common/chatVariables';
import { DebugConfigurationProviderTriggerKind, MainThreadDebugVisualization, IAdapterDescriptor, IConfig, IDebugSessionReplMode, IDebugVisualization, IDebugVisualizationContext, IDebugVisualizationTreeItem } from 'vs/workbench/contrib/debug/common/debug';
import { IChatMessage, IChatResponseFragment, ILanguageModelChatMetadata } from 'vs/workbench/contrib/chat/common/languageModels';
import { DebugConfigurationProviderTriggerKind, IAdapterDescriptor, IConfig, IDebugSessionReplMode, IDebugVisualization, IDebugVisualizationContext, IDebugVisualizationTreeItem, MainThreadDebugVisualization } from 'vs/workbench/contrib/debug/common/debug';
import { IInlineChatBulkEditResponse, IInlineChatEditResponse, IInlineChatFollowup, IInlineChatProgressItem, IInlineChatRequest, IInlineChatSession, InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
import * as notebookCommon from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellExecutionUpdateType } from 'vs/workbench/contrib/notebook/common/notebookExecutionService';
@ -1196,12 +1196,11 @@ export interface ExtHostLanguageModelsShape {
}
export interface IExtensionChatAgentMetadata extends Dto<IChatAgentMetadata> {
hasSlashCommands?: boolean;
hasFollowups?: boolean;
}
export interface MainThreadChatAgentsShape2 extends IDisposable {
$registerAgent(handle: number, extension: ExtensionIdentifier, name: string, metadata: IExtensionChatAgentMetadata): void;
$registerAgent(handle: number, extension: ExtensionIdentifier, name: string, metadata: IExtensionChatAgentMetadata, allowDynamic: boolean): void;
$registerAgentCompletionsProvider(handle: number, triggerCharacters: string[]): void;
$unregisterAgentCompletionsProvider(handle: number): void;
$updateAgent(handle: number, metadataUpdate: IExtensionChatAgentMetadata): void;
@ -1228,7 +1227,6 @@ export type IChatAgentHistoryEntryDto = {
export interface ExtHostChatAgentsShape2 {
$invokeAgent(handle: number, request: IChatAgentRequest, context: { history: IChatAgentHistoryEntryDto[] }, token: CancellationToken): Promise<IChatAgentResult | undefined>;
$provideSlashCommands(handle: number, context: { history: IChatAgentHistoryEntryDto[] }, token: CancellationToken): Promise<IChatAgentCommand[]>;
$provideFollowups(request: IChatAgentRequest, handle: number, result: IChatAgentResult, token: CancellationToken): Promise<IChatFollowup[]>;
$acceptFeedback(handle: number, result: IChatAgentResult, vote: InteractiveSessionVoteDirection, reportIssue?: boolean): void;
$acceptAction(handle: number, result: IChatAgentResult, action: IChatUserActionEvent): void;

View file

@ -21,7 +21,7 @@ import { ExtHostChatAgentsShape2, IChatAgentCompletionItem, IChatAgentHistoryEnt
import { CommandsConverter, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters';
import * as extHostTypes from 'vs/workbench/api/common/extHostTypes';
import { IChatAgentCommand, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents';
import { IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents';
import { IChatFollowup, IChatProgress, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
import { checkProposedApiEnabled, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
import { Dto } from 'vs/workbench/services/extensions/common/proxyIdentifier';
@ -171,7 +171,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 {
const agent = new ExtHostChatAgent(extension, name, this._proxy, handle, handler);
this._agents.set(handle, agent);
this._proxy.$registerAgent(handle, extension.identifier, name, {});
this._proxy.$registerAgent(handle, extension.identifier, name, {}, isProposedApiEnabled(extension, 'chatParticipantAdditions'));
return agent.apiAgent;
}
@ -245,23 +245,6 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 {
this._sessionDisposables.deleteAndDispose(sessionId);
}
async $provideSlashCommands(handle: number, context: { history: IChatAgentHistoryEntryDto[] }, token: CancellationToken): Promise<IChatAgentCommand[]> {
const agent = this._agents.get(handle);
if (!agent) {
// this is OK, the agent might have disposed while the request was in flight
return [];
}
const convertedHistory = await this.prepareHistoryTurns(agent.id, context);
try {
return await agent.provideSlashCommands({ history: convertedHistory }, token);
} catch (err) {
const msg = toErrorMessage(err);
this._logService.error(`[${agent.extension.identifier.value}] [@${agent.id}] Error while providing slash commands: ${msg}`);
return [];
}
}
async $provideFollowups(request: IChatAgentRequest, handle: number, result: IChatAgentResult, token: CancellationToken): Promise<IChatFollowup[]> {
const agent = this._agents.get(handle);
if (!agent) {
@ -276,7 +259,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 {
this._agents.values(),
a => a.id === f.participant && ExtensionIdentifier.equals(a.extension.identifier, agent.extension.identifier));
if (!isValid) {
this._logService.warn(`[@${agent.id}] ChatFollowup refers to an invalid participant: ${f.participant}`);
this._logService.warn(`[@${agent.id}] ChatFollowup refers to an unknown participant: ${f.participant}`);
}
return isValid;
})
@ -352,7 +335,6 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 {
class ExtHostChatAgent {
private _commandProvider: vscode.ChatCommandProvider | undefined;
private _followupProvider: vscode.ChatFollowupProvider | undefined;
private _description: string | undefined;
private _fullName: string | undefined;
@ -394,30 +376,6 @@ class ExtHostChatAgent {
return await this._agentVariableProvider.provider.provideCompletionItems(query, token) ?? [];
}
async provideSlashCommands(context: vscode.ChatContext, token: CancellationToken): Promise<IChatAgentCommand[]> {
if (!this._commandProvider) {
return [];
}
const result = await this._commandProvider.provideCommands(context, token);
if (!result) {
return [];
}
return result
.map(c => {
if ('isSticky2' in c) {
checkProposedApiEnabled(this.extension, 'chatParticipantAdditions');
}
return {
name: c.name,
description: c.description ?? '',
followupPlaceholder: c.isSticky2?.placeholder,
isSticky: c.isSticky2?.isSticky ?? c.isSticky,
sampleRequest: c.sampleRequest
} satisfies IChatAgentCommand;
});
}
async provideFollowups(result: vscode.ChatResult, token: CancellationToken): Promise<vscode.ChatFollowup[]> {
if (!this._followupProvider) {
return [];
@ -485,9 +443,7 @@ class ExtHostChatAgent {
'dark' in this._iconPath ? this._iconPath.dark :
undefined,
themeIcon: this._iconPath instanceof extHostTypes.ThemeIcon ? this._iconPath : undefined,
hasSlashCommands: this._commandProvider !== undefined,
hasFollowups: this._followupProvider !== undefined,
isDefault: this._isDefault,
isSecondary: this._isSecondary,
helpTextPrefix: (!this._helpTextPrefix || typeof this._helpTextPrefix === 'string') ? this._helpTextPrefix : typeConvert.MarkdownString.from(this._helpTextPrefix),
helpTextVariablesPrefix: (!this._helpTextVariablesPrefix || typeof this._helpTextVariablesPrefix === 'string') ? this._helpTextVariablesPrefix : typeConvert.MarkdownString.from(this._helpTextVariablesPrefix),
@ -535,13 +491,6 @@ class ExtHostChatAgent {
assertType(typeof v === 'function', 'Invalid request handler');
that._requestHandler = v;
},
get commandProvider() {
return that._commandProvider;
},
set commandProvider(v) {
that._commandProvider = v;
updateMetadataSoon();
},
get followupProvider() {
return that._followupProvider;
},
@ -564,10 +513,6 @@ class ExtHostChatAgent {
},
set helpTextPrefix(v) {
checkProposedApiEnabled(that.extension, 'defaultChatParticipant');
if (!that._isDefault) {
throw new Error('helpTextPrefix is only available on the default chat agent');
}
that._helpTextPrefix = v;
updateMetadataSoon();
},
@ -577,10 +522,6 @@ class ExtHostChatAgent {
},
set helpTextVariablesPrefix(v) {
checkProposedApiEnabled(that.extension, 'defaultChatParticipant');
if (!that._isDefault) {
throw new Error('helpTextVariablesPrefix is only available on the default chat agent');
}
that._helpTextVariablesPrefix = v;
updateMetadataSoon();
},
@ -590,10 +531,6 @@ class ExtHostChatAgent {
},
set helpTextPostfix(v) {
checkProposedApiEnabled(that.extension, 'defaultChatParticipant');
if (!that._isDefault) {
throw new Error('helpTextPostfix is only available on the default chat agent');
}
that._helpTextPostfix = v;
updateMetadataSoon();
},
@ -664,7 +601,6 @@ class ExtHostChatAgent {
},
dispose() {
disposed = true;
that._commandProvider = undefined;
that._followupProvider = undefined;
that._onDidReceiveFeedback.dispose();
that._proxy.$unregisterAgent(that._handle);

View file

@ -3,62 +3,61 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IMarkdownString, MarkdownString, isMarkdownString } from 'vs/base/common/htmlContent';
import { Disposable } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import { isMacintosh } from 'vs/base/common/platform';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import * as nls from 'vs/nls';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Registry } from 'vs/platform/registry/common/platform';
import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor';
import { IWorkbenchContributionsRegistry, WorkbenchPhase, Extensions as WorkbenchExtensions, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, WorkbenchPhase, registerWorkbenchContribution2 } from 'vs/workbench/common/contributions';
import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/editor';
import { AccessibilityVerbositySettingId, AccessibleViewProviderId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration';
import { alertFocusChange } from 'vs/workbench/contrib/accessibility/browser/accessibilityContributions';
import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView';
import { AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions';
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 } from 'vs/workbench/contrib/chat/browser/actions/chatCodeblockActions';
import { registerChatCopyActions } from 'vs/workbench/contrib/chat/browser/actions/chatCopyActions';
import { IChatExecuteActionContext, SubmitAction, registerChatExecuteActions } from 'vs/workbench/contrib/chat/browser/actions/chatExecuteActions';
import { registerChatFileTreeActions } from 'vs/workbench/contrib/chat/browser/actions/chatFileTreeActions';
import { registerChatExportActions } from 'vs/workbench/contrib/chat/browser/actions/chatImportExport';
import { registerMoveActions } from 'vs/workbench/contrib/chat/browser/actions/chatMoveActions';
import { registerQuickChatActions } from 'vs/workbench/contrib/chat/browser/actions/chatQuickInputActions';
import { registerChatTitleActions } from 'vs/workbench/contrib/chat/browser/actions/chatTitleActions';
import { registerChatExportActions } from 'vs/workbench/contrib/chat/browser/actions/chatImportExport';
import { IChatAccessibilityService, 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 'vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib';
import 'vs/workbench/contrib/chat/browser/contrib/chatHistoryVariables';
import 'vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib';
import { 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';
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';
import { isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel';
import { ChatWidgetHistoryService, IChatWidgetHistoryService } from 'vs/workbench/contrib/chat/common/chatWidgetHistoryService';
import { ILanguageModelsService, LanguageModelsService } from 'vs/workbench/contrib/chat/common/languageModels';
import { IVoiceChatService, VoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChat';
import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import '../common/chatColors';
import { registerMoveActions } from 'vs/workbench/contrib/chat/browser/actions/chatMoveActions';
import { ACTION_ID_NEW_CHAT, registerNewChatActions } from 'vs/workbench/contrib/chat/browser/actions/chatClearActions';
import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView';
import { isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel';
import { CONTEXT_IN_CHAT_SESSION } from 'vs/workbench/contrib/chat/common/chatContextKeys';
import { ChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chatAccessibilityService';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { AccessibilityVerbositySettingId, AccessibleViewProviderId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration';
import { ChatWelcomeMessageModel } from 'vs/workbench/contrib/chat/common/chatModel';
import { IMarkdownString, MarkdownString, isMarkdownString } from 'vs/base/common/htmlContent';
import { LanguageModelsService, ILanguageModelsService } from 'vs/workbench/contrib/chat/common/languageModels';
import { ChatSlashCommandService, IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands';
import { alertFocusChange } from 'vs/workbench/contrib/accessibility/browser/accessibilityContributions';
import { AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables';
import { registerChatFileTreeActions } from 'vs/workbench/contrib/chat/browser/actions/chatFileTreeActions';
import { QuickChatService } from 'vs/workbench/contrib/chat/browser/chatQuick';
import { ChatAgentService, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
import { ChatVariablesService } from 'vs/workbench/contrib/chat/browser/chatVariables';
import { chatAgentLeader, chatSubcommandLeader, chatVariableLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IVoiceChatService, VoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChat';
// Register configuration
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
@ -246,7 +245,7 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable {
executeImmediately: true
}, async (prompt, progress) => {
const defaultAgent = chatAgentService.getDefaultAgent();
const agents = chatAgentService.getAgents();
const agents = chatAgentService.getRegisteredAgents();
// Report prefix
if (defaultAgent?.metadata.helpTextPrefix) {
@ -266,8 +265,7 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable {
const actionArg: IChatExecuteActionContext = { inputValue: `${agentWithLeader} ${a.metadata.sampleRequest}` };
const urlSafeArg = encodeURIComponent(JSON.stringify(actionArg));
const agentLine = `* [\`${agentWithLeader}\`](command:${SubmitAction.ID}?${urlSafeArg}) - ${a.metadata.description}`;
const commands = await a.provideSlashCommands(undefined, [], CancellationToken.None);
const commandText = commands.map(c => {
const commandText = a.slashCommands.map(c => {
const actionArg: IChatExecuteActionContext = { inputValue: `${agentWithLeader} ${chatSubcommandLeader}${c.name} ${c.sampleRequest ?? ''}` };
const urlSafeArg = encodeURIComponent(JSON.stringify(actionArg));
return `\t* [\`${chatSubcommandLeader}${c.name}\`](command:${SubmitAction.ID}?${urlSafeArg}) - ${c.description}`;

View file

@ -18,10 +18,9 @@ import { getNewChatAction } from 'vs/workbench/contrib/chat/browser/actions/chat
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 { IChatContributionService, IChatProviderContribution, IRawChatProviderContribution } from 'vs/workbench/contrib/chat/common/chatContributionService';
import { IChatContributionService, IChatParticipantContribution, IChatProviderContribution, IRawChatParticipantContribution, IRawChatProviderContribution } from 'vs/workbench/contrib/chat/common/chatContributionService';
import * as extensionsRegistry from 'vs/workbench/services/extensions/common/extensionsRegistry';
const chatExtensionPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint<IRawChatProviderContribution[]>({
extensionPoint: 'interactiveSession',
jsonSchema: {
@ -59,6 +58,71 @@ const chatExtensionPoint = extensionsRegistry.ExtensionsRegistry.registerExtensi
},
});
const chatParticipantExtensionPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint<IRawChatParticipantContribution[]>({
extensionPoint: 'chatParticipants',
jsonSchema: {
description: localize('vscode.extension.contributes.chatParticipant', 'Contributes a Chat Participant'),
type: 'array',
items: {
additionalProperties: false,
type: 'object',
defaultSnippets: [{ body: { name: '', description: '' } }],
required: ['name'],
properties: {
name: {
description: localize('chatParticipantName', "Unique name for this Chat Participant."),
type: 'string'
},
description: {
description: localize('chatParticipantDescription', "A description of this Chat Participant, shown in the UI."),
type: 'string'
},
isDefault: {
markdownDescription: localize('chatParticipantIsDefaultDescription', "**Only** allowed for extensions that have the `defaultChatParticipant` proposal."),
type: 'boolean',
},
commands: {
markdownDescription: localize('chatCommandsDescription', "Commands available for this Chat Participant, which the user can invoke with a `/`."),
type: 'array',
items: {
additionalProperties: false,
type: 'object',
defaultSnippets: [{ body: { name: '', description: '' } }],
required: ['name', 'description'],
properties: {
name: {
description: localize('chatCommand', "A short name by which this command is referred to in the UI, e.g. `fix` or * `explain` for commands that fix an issue or explain code. The name should be unique among the commands provided by this participant."),
type: 'string'
},
description: {
description: localize('chatCommandDescription', "A description of this command."),
type: 'string'
},
when: {
description: localize('chatCommandDescription', "A description of this command."),
type: 'string'
},
sampleRequest: {
description: localize('chatCommandDescription', "A description of this command."),
type: 'string'
},
isSticky: {
description: localize('chatCommandDescription', "A description of this command."),
type: 'boolean'
},
}
}
}
}
}
},
activationEventsGenerator: (contributions: IRawChatParticipantContribution[], result: { push(item: string): void }) => {
for (const contrib of contributions) {
result.push(`onChatParticipant:${contrib.name}`);
}
},
});
export class ChatExtensionPointHandler implements IWorkbenchContribution {
static readonly ID = 'workbench.contrib.chatExtensionPointHandler';
@ -96,6 +160,20 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution {
}
}
});
chatParticipantExtensionPoint.setHandler((extensions, delta) => {
for (const extension of delta.added) {
for (const providerDescriptor of extension.value) {
this._chatContributionService.registerChatParticipant({ ...providerDescriptor, extensionId: extension.description.identifier });
}
}
for (const extension of delta.removed) {
for (const providerDescriptor of extension.value) {
this._chatContributionService.deregisterChatParticipant({ ...providerDescriptor, extensionId: extension.description.identifier });
}
}
});
}
private registerViewContainer(): ViewContainer {
@ -156,10 +234,15 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution {
registerWorkbenchContribution2(ChatExtensionPointHandler.ID, ChatExtensionPointHandler, WorkbenchPhase.BlockStartup);
function getParticipantKey(participant: IChatParticipantContribution): string {
return `${participant.extensionId.value}_${participant.name}`;
}
export class ChatContributionService implements IChatContributionService {
declare _serviceBrand: undefined;
private _registeredProviders = new Map<string, IChatProviderContribution>();
private _registeredParticipants = new Map<string, IChatParticipantContribution>();
constructor(
) { }
@ -176,7 +259,19 @@ export class ChatContributionService implements IChatContributionService {
this._registeredProviders.delete(providerId);
}
public registerChatParticipant(participant: IChatParticipantContribution): void {
this._registeredParticipants.set(getParticipantKey(participant), participant);
}
public deregisterChatParticipant(participant: IChatParticipantContribution): void {
this._registeredParticipants.delete(getParticipantKey(participant));
}
public get registeredProviders(): IChatProviderContribution[] {
return Array.from(this._registeredProviders.values());
}
public get registeredParticipants(): IChatParticipantContribution[] {
return Array.from(this._registeredParticipants.values());
}
}

View file

@ -360,7 +360,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
private renderDetail(element: IChatResponseViewModel, templateData: IChatListItemTemplate): void {
let progressMsg: string = '';
if (element.agent && !element.agent.metadata.isDefault) {
if (element.agent && !element.agent.isDefault) {
let usingMsg = chatAgentLeader + element.agent.id;
if (element.slashCommand) {
usingMsg += ` ${chatSubcommandLeader}${element.slashCommand.name}`;
@ -394,7 +394,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
templateData.avatarContainer.replaceChildren(dom.$('.avatar.codicon-avatar', undefined, avatarIcon));
}
if (isResponseVM(element) && element.agent && !element.agent.metadata.isDefault) {
if (isResponseVM(element) && element.agent && !element.agent.isDefault) {
dom.show(templateData.agentAvatarContainer);
const icon = this.getAgentIcon(element.agent.metadata);
if (icon instanceof URI) {

View file

@ -3,7 +3,6 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { raceCancellation } from 'vs/base/common/async';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
@ -27,7 +26,6 @@ import { ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget';
import { SelectAndInsertFileAction, dynamicVariableDecorationType } from 'vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables';
import { IChatAgentCommand, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
import { chatSlashCommandBackground, chatSlashCommandForeground } from 'vs/workbench/contrib/chat/common/chatColors';
import { getHistoryEntriesFromModel } from 'vs/workbench/contrib/chat/common/chatModel';
import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, ChatRequestTextPart, ChatRequestVariablePart, IParsedChatRequestPart, chatAgentLeader, chatSubcommandLeader, chatVariableLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes';
import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser';
import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands';
@ -347,8 +345,8 @@ class AgentCompletions extends Disposable {
return null;
}
const agents = this.chatAgentService.getAgents()
.filter(a => !a.metadata.isDefault);
const agents = this.chatAgentService.getRegisteredAgents()
.filter(a => !a.isDefault);
return <CompletionList>{
suggestions: agents.map((c, i) => {
const withAt = `@${c.id}`;
@ -399,10 +397,8 @@ class AgentCompletions extends Disposable {
}
const usedAgent = parsedRequest[usedAgentIdx] as ChatRequestAgentPart;
const commands = await usedAgent.agent.provideSlashCommands(widget.viewModel.model, getHistoryEntriesFromModel(widget.viewModel.model), token); // Refresh the cache here
return <CompletionList>{
suggestions: commands.map((c, i) => {
suggestions: usedAgent.agent.slashCommands.map((c, i) => {
const withSlash = `/${c.name}`;
return <CompletionItem>{
label: withSlash,
@ -432,16 +428,9 @@ class AgentCompletions extends Disposable {
return null;
}
const agents = this.chatAgentService.getAgents();
const all = agents.map(agent => agent.provideSlashCommands(viewModel.model, getHistoryEntriesFromModel(viewModel.model), token));
const commands = await raceCancellation(Promise.all(all), token);
if (!commands) {
return;
}
const agents = this.chatAgentService.getRegisteredAgents();
const justAgents: CompletionItem[] = agents
.filter(a => !a.metadata.isDefault)
.filter(a => !a.isDefault)
.map(agent => {
const agentLabel = `${chatAgentLeader}${agent.id}`;
return {
@ -456,7 +445,7 @@ class AgentCompletions extends Disposable {
return {
suggestions: justAgents.concat(
agents.flatMap((agent, i) => commands[i].map((c, i) => {
agents.flatMap(agent => agent.slashCommands.map((c, i) => {
const agentLabel = `${chatAgentLeader}${agent.id}`;
const withSlash = `${chatSubcommandLeader}${c.name}`;
return {

View file

@ -11,9 +11,11 @@ import { Disposable, 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 { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IChatModel, IChatProgressResponseContent, IChatRequestVariableData } from 'vs/workbench/contrib/chat/common/chatModel';
import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService';
import { IChatProgressResponseContent, IChatRequestVariableData } from 'vs/workbench/contrib/chat/common/chatModel';
import { IChatFollowup, IChatProgress, IChatResponseErrorDetails } from 'vs/workbench/contrib/chat/common/chatService';
//#region agent service, commands etc
@ -27,18 +29,21 @@ export interface IChatAgentHistoryEntry {
export interface IChatAgentData {
id: string;
extensionId: ExtensionIdentifier;
/** The agent invoked when no agent is specified */
isDefault?: boolean;
metadata: IChatAgentMetadata;
slashCommands: IChatAgentCommand[];
}
export interface IChatAgent extends IChatAgentData {
export interface IChatAgentImplementation {
invoke(request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatAgentResult>;
provideFollowups?(request: IChatAgentRequest, result: IChatAgentResult, token: CancellationToken): Promise<IChatFollowup[]>;
getLastSlashCommands(model: IChatModel): IChatAgentCommand[] | undefined;
provideSlashCommands(model: IChatModel | undefined, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatAgentCommand[]>;
provideWelcomeMessage?(token: CancellationToken): ProviderResult<(string | IMarkdownString)[] | undefined>;
provideSampleQuestions?(token: CancellationToken): ProviderResult<IChatFollowup[] | undefined>;
}
export type IChatAgent = IChatAgentData & IChatAgentImplementation;
export interface IChatAgentCommand {
name: string;
description: string;
@ -68,7 +73,6 @@ export interface IChatAgentCommand {
export interface IChatAgentMetadata {
description?: string;
isDefault?: boolean; // The agent invoked when no agent is specified
helpTextPrefix?: string | IMarkdownString;
helpTextVariablesPrefix?: string | IMarkdownString;
helpTextPostfix?: string | IMarkdownString;
@ -108,17 +112,18 @@ export const IChatAgentService = createDecorator<IChatAgentService>('chatAgentSe
export interface IChatAgentService {
_serviceBrand: undefined;
/**
* undefined when an agent was removed
* undefined when an agent was removed IChatAgent
*/
readonly onDidChangeAgents: Event<IChatAgent | undefined>;
registerAgent(agent: IChatAgent): IDisposable;
registerAgent(name: string, agent: IChatAgentImplementation): IDisposable;
registerDynamicAgent(data: IChatAgentData, agentImpl: IChatAgentImplementation): IDisposable;
invokeAgent(id: string, request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatAgentResult>;
getFollowups(id: string, request: IChatAgentRequest, result: IChatAgentResult, token: CancellationToken): Promise<IChatFollowup[]>;
getAgents(): Array<IChatAgent>;
getAgent(id: string): IChatAgent | undefined;
getRegisteredAgents(): Array<IChatAgentData>;
getActivatedAgents(): Array<IChatAgent>;
getRegisteredAgent(id: string): IChatAgentData | undefined;
getDefaultAgent(): IChatAgent | undefined;
getSecondaryAgent(): IChatAgent | undefined;
hasAgent(id: string): boolean;
getSecondaryAgent(): IChatAgentData | undefined;
updateAgent(id: string, updateMetadata: IChatAgentMetadata): void;
}
@ -128,79 +133,160 @@ export class ChatAgentService extends Disposable implements IChatAgentService {
declare _serviceBrand: undefined;
private readonly _agents = new Map<string, { agent: IChatAgent }>();
private readonly _agents = new Map<string, { data: IChatAgentData; impl?: IChatAgentImplementation }>();
private readonly _onDidChangeAgents = this._register(new Emitter<IChatAgent | undefined>());
readonly onDidChangeAgents: Event<IChatAgent | undefined> = this._onDidChangeAgents.event;
constructor(
@IChatContributionService private chatContributionService: IChatContributionService,
@IContextKeyService private contextKeyService: IContextKeyService,
) {
super();
}
override dispose(): void {
super.dispose();
this._agents.clear();
}
registerAgent(agent: IChatAgent): IDisposable {
if (this._agents.has(agent.id)) {
throw new Error(`Already registered an agent with id ${agent.id}`);
registerAgent(name: string, agentImpl: IChatAgentImplementation): IDisposable {
if (this._agents.has(name)) {
// TODO not keyed by name, dupes allowed between extensions
throw new Error(`Already registered an agent with id ${name}`);
}
this._agents.set(agent.id, { agent });
this._onDidChangeAgents.fire(agent);
const data = this.getRegisteredAgent(name);
if (!data) {
throw new Error(`Unknown agent: ${name}`);
}
const agent = { data: data, impl: agentImpl };
this._agents.set(name, agent);
this._onDidChangeAgents.fire(new MergedChatAgent(data, agentImpl));
return toDisposable(() => {
if (this._agents.delete(agent.id)) {
if (this._agents.delete(name)) {
this._onDidChangeAgents.fire(undefined);
}
});
}
registerDynamicAgent(data: IChatAgentData, agentImpl: IChatAgentImplementation): IDisposable {
const agent = { data, impl: agentImpl };
this._agents.set(data.id, agent);
this._onDidChangeAgents.fire(new MergedChatAgent(data, agentImpl));
return toDisposable(() => {
if (this._agents.delete(data.id)) {
this._onDidChangeAgents.fire(undefined);
}
});
}
updateAgent(id: string, updateMetadata: IChatAgentMetadata): void {
const data = this._agents.get(id);
if (!data) {
throw new Error(`No agent with id ${id} registered`);
const agent = this._agents.get(id);
if (!agent?.impl) {
throw new Error(`No activated agent with id ${id} registered`);
}
data.agent.metadata = { ...data.agent.metadata, ...updateMetadata };
this._onDidChangeAgents.fire(data.agent);
agent.data.metadata = { ...agent.data.metadata, ...updateMetadata };
this._onDidChangeAgents.fire(new MergedChatAgent(agent.data, agent.impl));
}
getDefaultAgent(): IChatAgent | undefined {
return Iterable.find(this._agents.values(), a => !!a.agent.metadata.isDefault)?.agent;
return this.getActivatedAgents().find(a => !!a.isDefault);
}
getSecondaryAgent(): IChatAgent | undefined {
return Iterable.find(this._agents.values(), a => !!a.agent.metadata.isSecondary)?.agent;
getSecondaryAgent(): IChatAgentData | undefined {
// TODO also static
return Iterable.find(this._agents.values(), a => !!a.data.metadata.isSecondary)?.data;
}
getAgents(): Array<IChatAgent> {
return Array.from(this._agents.values(), v => v.agent);
getRegisteredAgents(): Array<IChatAgentData> {
const that = this;
return this.chatContributionService.registeredParticipants.map(p => (
{
extensionId: p.extensionId,
id: p.name,
metadata: { description: p.description },
isDefault: p.isDefault,
get slashCommands() {
const commands = p.commands ?? [];
return commands.filter(c => !c.when || that.contextKeyService.contextMatchesRules(ContextKeyExpr.deserialize(c.when)));
}
} satisfies IChatAgentData));
}
hasAgent(id: string): boolean {
return this._agents.has(id);
getActivatedAgents(): IChatAgent[] {
return Array.from(this._agents.values())
.filter(a => !!a.impl)
.map(a => new MergedChatAgent(a.data, a.impl!));
}
getAgent(id: string): IChatAgent | undefined {
const data = this._agents.get(id);
return data?.agent;
getRegisteredAgent(id: string): IChatAgentData | undefined {
return this.getRegisteredAgents().find(a => a.id === id);
}
async invokeAgent(id: string, request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatAgentResult> {
const data = this._agents.get(id);
if (!data) {
throw new Error(`No agent with id ${id}`);
if (!data?.impl) {
throw new Error(`No activated agent with id ${id}`);
}
return await data.agent.invoke(request, progress, history, token);
return await data.impl.invoke(request, progress, history, token);
}
async getFollowups(id: string, request: IChatAgentRequest, result: IChatAgentResult, token: CancellationToken): Promise<IChatFollowup[]> {
const data = this._agents.get(id);
if (!data) {
throw new Error(`No agent with id ${id}`);
if (!data?.impl) {
throw new Error(`No activated agent with id ${id}`);
}
if (!data.agent.provideFollowups) {
if (!data.impl?.provideFollowups) {
return [];
}
return data.agent.provideFollowups(request, result, token);
return data.impl.provideFollowups(request, result, token);
}
}
export class MergedChatAgent implements IChatAgent {
constructor(
private readonly data: IChatAgentData,
private readonly impl: IChatAgentImplementation
) { }
get id(): string { return this.data.id; }
get extensionId(): ExtensionIdentifier { return this.data.extensionId; }
get isDefault(): boolean | undefined { return this.data.isDefault; }
get metadata(): IChatAgentMetadata { return this.data.metadata; }
get slashCommands(): IChatAgentCommand[] { return this.data.slashCommands; }
async invoke(request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatAgentResult> {
return this.impl.invoke(request, progress, history, token);
}
async provideFollowups(request: IChatAgentRequest, result: IChatAgentResult, token: CancellationToken): Promise<IChatFollowup[]> {
if (this.impl.provideFollowups) {
return this.impl.provideFollowups(request, result, token);
}
return [];
}
provideWelcomeMessage(token: CancellationToken): ProviderResult<(string | IMarkdownString)[] | undefined> {
if (this.impl.provideWelcomeMessage) {
return this.impl.provideWelcomeMessage(token);
}
return undefined;
}
provideSampleQuestions(token: CancellationToken): ProviderResult<IChatFollowup[] | undefined> {
if (this.impl.provideSampleQuestions) {
return this.impl.provideSampleQuestions(token);
}
return undefined;
}
}

View file

@ -3,6 +3,7 @@
* 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 {
@ -18,6 +19,10 @@ export interface IChatContributionService {
registerChatProvider(provider: IChatProviderContribution): void;
deregisterChatProvider(providerId: string): void;
getViewIdForProvider(providerId: string): string;
readonly registeredParticipants: IChatParticipantContribution[];
registerChatParticipant(participant: IChatParticipantContribution): void;
deregisterChatParticipant(participant: IChatParticipantContribution): void;
}
export interface IRawChatProviderContribution {
@ -26,3 +31,23 @@ export interface IRawChatProviderContribution {
icon?: string;
when?: string;
}
export interface IRawChatCommandContribution {
name: string;
description: string;
sampleRequest?: string;
isSticky?: boolean;
when?: string;
}
export interface IRawChatParticipantContribution {
name: string;
description?: string;
isDefault?: boolean;
commands?: IRawChatCommandContribution[];
}
export interface IChatParticipantContribution extends IRawChatParticipantContribution {
// Participant id is extensionId + name
extensionId: ExtensionIdentifier;
}

View file

@ -683,7 +683,7 @@ export class ChatModel extends Disposable implements IChatModel {
} else if (progress.kind === 'usedContext' || progress.kind === 'reference') {
request.response.applyReference(progress);
} else if (progress.kind === 'agentDetection') {
const agent = this.chatAgentService.getAgent(progress.agentName);
const agent = this.chatAgentService.getRegisteredAgent(progress.agentName);
if (agent) {
request.response.setAgent(agent, progress.command);
}
@ -780,7 +780,10 @@ export class ChatModel extends Disposable implements IChatModel {
followups: r.response?.followups,
isCanceled: r.response?.isCanceled,
vote: r.response?.vote,
agent: r.response?.agent ? { id: r.response.agent.id, extensionId: r.response.agent.extensionId, metadata: r.response.agent.metadata } : undefined, // May actually be the full IChatAgent instance, just take the data props
agent: r.response?.agent ?
// May actually be the full IChatAgent instance, just take the data props. slashCommands don't matter here.
{ id: r.response.agent.id, extensionId: r.response.agent.extensionId, metadata: r.response.agent.metadata, slashCommands: [] }
: undefined,
slashCommand: r.response?.slashCommand,
usedContext: r.response?.usedContext,
contentReferences: r.response?.contentReferences

View file

@ -6,7 +6,7 @@
import { revive } from 'vs/base/common/marshalling';
import { IOffsetRange, OffsetRange } from 'vs/editor/common/core/offsetRange';
import { IRange } from 'vs/editor/common/core/range';
import { IChatAgent, IChatAgentCommand } from 'vs/workbench/contrib/chat/common/chatAgents';
import { IChatAgentCommand, IChatAgentData } from 'vs/workbench/contrib/chat/common/chatAgents';
import { IChatSlashData } from 'vs/workbench/contrib/chat/common/chatSlashCommands';
import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables';
@ -72,7 +72,7 @@ export class ChatRequestVariablePart implements IParsedChatRequestPart {
export class ChatRequestAgentPart implements IParsedChatRequestPart {
static readonly Kind = 'agent';
readonly kind = ChatRequestAgentPart.Kind;
constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly agent: IChatAgent) { }
constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly agent: IChatAgentData) { }
get text(): string {
return `${chatAgentLeader}${this.agent.id}`;

View file

@ -99,7 +99,7 @@ export class ChatRequestParser {
const varRange = new OffsetRange(offset, offset + full.length);
const varEditorRange = new Range(position.lineNumber, position.column, position.lineNumber, position.column + full.length);
const agent = this.agentService.getAgent(name);
const agent = this.agentService.getRegisteredAgent(name);
if (!agent) {
return;
}
@ -171,8 +171,7 @@ export class ChatRequestParser {
return;
}
const subCommands = usedAgent.agent.getLastSlashCommands(model);
const subCommand = subCommands?.find(c => c.name === command);
const subCommand = usedAgent.agent.slashCommands.find(c => c.name === command);
if (subCommand) {
// Valid agent subcommand
return new ChatRequestAgentSubcommandPart(slashRange, slashEditorRange, subCommand);

View file

@ -20,15 +20,15 @@ import { Progress } from 'vs/platform/progress/common/progress';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IChatAgent, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
import { 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, 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 { ChatMessageRole, IChatMessage } from 'vs/workbench/contrib/chat/common/languageModels';
import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser';
import { ChatCopyKind, IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, 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';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
const serializedChatKey = 'interactive.sessions';
@ -193,12 +193,6 @@ export class ChatService extends Disposable implements IChatService {
}
this._register(storageService.onWillSaveState(() => this.saveState()));
this._register(Event.debounce(this.chatAgentService.onDidChangeAgents, () => { }, 500)(() => {
for (const model of this._sessionModels.values()) {
this.warmSlashCommandCache(model);
}
}));
}
private saveState(): void {
@ -364,15 +358,9 @@ export class ChatService extends Disposable implements IChatService {
this.initializeSession(model, CancellationToken.None);
}
private warmSlashCommandCache(model: IChatModel, agent?: IChatAgent) {
const agents = agent ? [agent] : this.chatAgentService.getAgents();
agents.forEach(agent => agent.provideSlashCommands(model, [], CancellationToken.None));
}
private async initializeSession(model: ChatModel, token: CancellationToken): Promise<void> {
try {
this.trace('initializeSession', `Initialize session ${model.sessionId}`);
this.warmSlashCommandCache(model);
model.startInitialize();
await this.extensionService.activateByEvent(`onInteractiveSession:${model.providerId}`);
@ -543,6 +531,7 @@ export class ChatService extends Disposable implements IChatService {
const defaultAgent = this.chatAgentService.getDefaultAgent();
if (agentPart || (defaultAgent && !commandPart)) {
const agent = (agentPart?.agent ?? defaultAgent)!;
await this.extensionService.activateByEvent(`onChatParticipant:${agent.id}`);
const history = getHistoryEntriesFromModel(model);
const initVariableData: IChatRequestVariableData = { variables: [] };

View file

@ -87,19 +87,16 @@ export class VoiceChatService extends Disposable implements IVoiceChatService {
private createPhrases(model?: IChatModel): Map<string, IPhraseValue> {
const phrases = new Map<string, IPhraseValue>();
for (const agent of this.chatAgentService.getAgents()) {
for (const agent of this.chatAgentService.getActivatedAgents()) {
const agentPhrase = `${VoiceChatService.PHRASES_LOWER[VoiceChatService.AGENT_PREFIX]} ${VoiceChatService.CHAT_AGENT_ALIAS.get(agent.id) ?? agent.id}`.toLowerCase();
phrases.set(agentPhrase, { agent: agent.id });
const commands = model && agent.getLastSlashCommands(model);
if (commands) {
for (const slashCommand of commands) {
const slashCommandPhrase = `${VoiceChatService.PHRASES_LOWER[VoiceChatService.COMMAND_PREFIX]} ${slashCommand.name}`.toLowerCase();
phrases.set(slashCommandPhrase, { agent: agent.id, command: slashCommand.name });
for (const slashCommand of agent.slashCommands) {
const slashCommandPhrase = `${VoiceChatService.PHRASES_LOWER[VoiceChatService.COMMAND_PREFIX]} ${slashCommand.name}`.toLowerCase();
phrases.set(slashCommandPhrase, { agent: agent.id, command: slashCommand.name });
const agentSlashCommandPhrase = `${agentPhrase} ${slashCommandPhrase}`.toLowerCase();
phrases.set(agentSlashCommandPhrase, { agent: agent.id, command: slashCommand.name });
}
const agentSlashCommandPhrase = `${agentPhrase} ${slashCommandPhrase}`.toLowerCase();
phrases.set(agentSlashCommandPhrase, { agent: agent.id, command: slashCommand.name });
}
}

View file

@ -28,8 +28,12 @@
agent: {
id: "agent",
metadata: { description: "" },
provideSlashCommands: [Function provideSlashCommands],
getLastSlashCommands: [Function getLastSlashCommands]
slashCommands: [
{
name: "subCommand",
description: ""
}
]
},
kind: "agent"
},

View file

@ -28,8 +28,12 @@
agent: {
id: "agent",
metadata: { description: "" },
provideSlashCommands: [Function provideSlashCommands],
getLastSlashCommands: [Function getLastSlashCommands]
slashCommands: [
{
name: "subCommand",
description: ""
}
]
},
kind: "agent"
},

View file

@ -14,8 +14,12 @@
agent: {
id: "agent",
metadata: { description: "" },
provideSlashCommands: [Function provideSlashCommands],
getLastSlashCommands: [Function getLastSlashCommands]
slashCommands: [
{
name: "subCommand",
description: ""
}
]
},
kind: "agent"
},

View file

@ -14,8 +14,12 @@
agent: {
id: "agent",
metadata: { description: "" },
provideSlashCommands: [Function provideSlashCommands],
getLastSlashCommands: [Function getLastSlashCommands]
slashCommands: [
{
name: "subCommand",
description: ""
}
]
},
kind: "agent"
},

View file

@ -14,8 +14,12 @@
agent: {
id: "agent",
metadata: { description: "" },
provideSlashCommands: [Function provideSlashCommands],
getLastSlashCommands: [Function getLastSlashCommands]
slashCommands: [
{
name: "subCommand",
description: ""
}
]
},
kind: "agent"
},

View file

@ -14,8 +14,12 @@
agent: {
id: "agent",
metadata: { description: "" },
provideSlashCommands: [Function provideSlashCommands],
getLastSlashCommands: [Function getLastSlashCommands]
slashCommands: [
{
name: "subCommand",
description: ""
}
]
},
kind: "agent"
},

View file

@ -14,8 +14,12 @@
agent: {
id: "agent",
metadata: { description: "" },
provideSlashCommands: [Function provideSlashCommands],
getLastSlashCommands: [Function getLastSlashCommands]
slashCommands: [
{
name: "subCommand",
description: ""
}
]
},
kind: "agent"
},

View file

@ -23,7 +23,7 @@
},
agent: {
id: "ChatProviderWithUsedContext",
metadata: { }
metadata: { description: undefined }
}
},
{
@ -54,7 +54,8 @@
value: "nullExtensionDescription",
_lower: "nullextensiondescription"
},
metadata: { }
metadata: { description: undefined },
slashCommands: [ ]
},
slashCommand: undefined,
usedContext: {

View file

@ -22,7 +22,7 @@
},
agent: {
id: "ChatProviderWithUsedContext",
metadata: { }
metadata: { description: undefined }
}
},
{
@ -54,7 +54,8 @@
value: "nullExtensionDescription",
_lower: "nullextensiondescription"
},
metadata: { }
metadata: { description: undefined },
slashCommands: [ ]
},
slashCommand: undefined,
usedContext: {

View file

@ -9,7 +9,7 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/uti
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { ILogService, NullLogService } from 'vs/platform/log/common/log';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { ChatAgentService, IChatAgent, IChatAgentCommand, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
import { ChatAgentService, IChatAgentCommand, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser';
import { IChatService } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands';
@ -112,12 +112,12 @@ suite('ChatRequestParser', () => {
});
const getAgentWithSlashCommands = (slashCommands: IChatAgentCommand[]) => {
return <Partial<IChatAgent>>{ id: 'agent', metadata: { description: '' }, provideSlashCommands: async () => [], getLastSlashCommands: () => slashCommands };
return <IChatAgentData>{ id: 'agent', metadata: { description: '' }, slashCommands };
};
test('agent with subcommand after text', async () => {
const agentsService = mockObject<IChatAgentService>()({});
agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }]));
agentsService.getRegisteredAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }]));
instantiationService.stub(IChatAgentService, agentsService as any);
parser = instantiationService.createInstance(ChatRequestParser);
@ -127,7 +127,7 @@ suite('ChatRequestParser', () => {
test('agents, subCommand', async () => {
const agentsService = mockObject<IChatAgentService>()({});
agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }]));
agentsService.getRegisteredAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }]));
instantiationService.stub(IChatAgentService, agentsService as any);
parser = instantiationService.createInstance(ChatRequestParser);
@ -137,7 +137,7 @@ suite('ChatRequestParser', () => {
test('agent with question mark', async () => {
const agentsService = mockObject<IChatAgentService>()({});
agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }]));
agentsService.getRegisteredAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }]));
instantiationService.stub(IChatAgentService, agentsService as any);
parser = instantiationService.createInstance(ChatRequestParser);
@ -147,7 +147,7 @@ suite('ChatRequestParser', () => {
test('agent and subcommand with leading whitespace', async () => {
const agentsService = mockObject<IChatAgentService>()({});
agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }]));
agentsService.getRegisteredAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }]));
instantiationService.stub(IChatAgentService, agentsService as any);
parser = instantiationService.createInstance(ChatRequestParser);
@ -157,7 +157,7 @@ suite('ChatRequestParser', () => {
test('agent and subcommand after newline', async () => {
const agentsService = mockObject<IChatAgentService>()({});
agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }]));
agentsService.getRegisteredAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }]));
instantiationService.stub(IChatAgentService, agentsService as any);
parser = instantiationService.createInstance(ChatRequestParser);
@ -167,7 +167,7 @@ suite('ChatRequestParser', () => {
test('agent not first', async () => {
const agentsService = mockObject<IChatAgentService>()({});
agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }]));
agentsService.getRegisteredAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }]));
instantiationService.stub(IChatAgentService, agentsService as any);
parser = instantiationService.createInstance(ChatRequestParser);
@ -177,7 +177,7 @@ suite('ChatRequestParser', () => {
test('agents and variables and multiline', async () => {
const agentsService = mockObject<IChatAgentService>()({});
agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }]));
agentsService.getRegisteredAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }]));
instantiationService.stub(IChatAgentService, agentsService as any);
varService.hasVariable.returns(true);
@ -189,7 +189,7 @@ suite('ChatRequestParser', () => {
test('agents and variables and multiline, part2', async () => {
const agentsService = mockObject<IChatAgentService>()({});
agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }]));
agentsService.getRegisteredAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }]));
instantiationService.stub(IChatAgentService, agentsService as any);
varService.hasVariable.returns(true);

View file

@ -21,7 +21,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IViewsService } from 'vs/workbench/services/views/common/viewsService';
import { ChatAgentService, IChatAgent, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
import { 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';
@ -32,6 +32,7 @@ import { MockChatVariablesService } from 'vs/workbench/contrib/chat/test/common/
import { IExtensionService, nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
import { TestContextService, TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices';
import { MockChatService } from 'vs/workbench/contrib/chat/test/common/mockChatService';
import { MockChatContributionService } from 'vs/workbench/contrib/chat/test/common/mockChatContributionService';
class SimpleTestProvider extends Disposable implements IChatProvider {
private static sessionId = 0;
@ -60,12 +61,7 @@ const chatAgentWithUsedContext: IChatAgent = {
id: chatAgentWithUsedContextId,
extensionId: nullExtensionDescription.identifier,
metadata: {},
getLastSlashCommands() {
return undefined;
},
async provideSlashCommands() {
return [];
},
slashCommands: [],
async invoke(request, progress, history, token) {
progress({
documents: [
@ -109,25 +105,21 @@ suite('Chat', () => {
instantiationService.stub(IWorkspaceContextService, new TestContextService());
instantiationService.stub(IChatSlashCommandService, testDisposables.add(instantiationService.createInstance(ChatSlashCommandService)));
instantiationService.stub(IChatService, new MockChatService());
instantiationService.stub(IChatContributionService, new MockChatContributionService(
[
{ extensionId: nullExtensionDescription.identifier, name: 'testAgent', isDefault: true },
{ extensionId: nullExtensionDescription.identifier, name: chatAgentWithUsedContextId, isDefault: true },
]));
chatAgentService = testDisposables.add(instantiationService.createInstance(ChatAgentService));
instantiationService.stub(IChatAgentService, chatAgentService);
const agent = {
id: 'testAgent',
extensionId: nullExtensionDescription.identifier,
metadata: { isDefault: true },
async invoke(request, progress, history, token) {
return {};
},
getLastSlashCommands() {
return undefined;
},
async provideSlashCommands(token) {
return [];
},
} as IChatAgent;
testDisposables.add(chatAgentService.registerAgent(agent));
} satisfies IChatAgentImplementation;
testDisposables.add(chatAgentService.registerAgent('testAgent', agent));
});
test('retrieveSession', async () => {
@ -229,7 +221,7 @@ suite('Chat', () => {
});
test('can serialize', async () => {
testDisposables.add(chatAgentService.registerAgent(chatAgentWithUsedContext));
testDisposables.add(chatAgentService.registerAgent(chatAgentWithUsedContext.id, chatAgentWithUsedContext));
const testService = testDisposables.add(instantiationService.createInstance(ChatService));
testDisposables.add(testService.registerProvider(testDisposables.add(new SimpleTestProvider('testProvider'))));
@ -249,7 +241,7 @@ suite('Chat', () => {
test('can deserialize', async () => {
let serializedChatData: ISerializableChatData;
testDisposables.add(chatAgentService.registerAgent(chatAgentWithUsedContext));
testDisposables.add(chatAgentService.registerAgent(chatAgentWithUsedContext.id, chatAgentWithUsedContext));
// create the first service, send request, get response, and serialize the state
{ // serapate block to not leak variables in outer scope

View file

@ -0,0 +1,32 @@
/*---------------------------------------------------------------------------------------------
* 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(
public readonly registeredParticipants: IChatParticipantContribution[] = []
) { }
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

@ -11,7 +11,7 @@ import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifec
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
import { ProviderResult } from 'vs/editor/common/languages';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { IChatAgent, IChatAgentCommand, IChatAgentHistoryEntry, IChatAgentMetadata, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
import { IChatAgent, IChatAgentCommand, IChatAgentData, IChatAgentHistoryEntry, IChatAgentImplementation, IChatAgentMetadata, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
import { IChatModel } from 'vs/workbench/contrib/chat/common/chatModel';
import { IChatProgress, IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService';
import { IVoiceChatSessionOptions, IVoiceChatTextEvent, VoiceChatService } from 'vs/workbench/contrib/chat/common/voiceChat';
@ -28,10 +28,8 @@ suite('VoiceChat', () => {
extensionId: ExtensionIdentifier = nullExtensionDescription.identifier;
constructor(readonly id: string, private readonly lastSlashCommands: IChatAgentCommand[]) { }
getLastSlashCommands(model: IChatModel): IChatAgentCommand[] | undefined { return this.lastSlashCommands; }
constructor(readonly id: string, readonly slashCommands: IChatAgentCommand[]) { }
invoke(request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatAgentResult> { throw new Error('Method not implemented.'); }
provideSlashCommands(model: IChatModel | undefined, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatAgentCommand[]> { throw new Error('Method not implemented.'); }
provideWelcomeMessage?(token: CancellationToken): ProviderResult<(string | IMarkdownString)[] | undefined> { throw new Error('Method not implemented.'); }
metadata = {};
}
@ -49,14 +47,15 @@ suite('VoiceChat', () => {
class TestChatAgentService implements IChatAgentService {
_serviceBrand: undefined;
readonly onDidChangeAgents = Event.None;
registerAgent(agent: IChatAgent): IDisposable { throw new Error(); }
registerAgent(name: string, agent: IChatAgentImplementation): IDisposable { throw new Error(); }
registerDynamicAgent(data: IChatAgentData, agentImpl: IChatAgentImplementation): IDisposable { throw new Error('Method not implemented.'); }
invokeAgent(id: string, request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatAgentResult> { throw new Error(); }
getFollowups(id: string, request: IChatAgentRequest, result: IChatAgentResult, token: CancellationToken): Promise<IChatFollowup[]> { throw new Error(); }
getAgents(): Array<IChatAgent> { return agents; }
getAgent(id: string): IChatAgent | undefined { throw new Error(); }
getRegisteredAgents(): Array<IChatAgent> { return agents; }
getActivatedAgents(): IChatAgent[] { return agents; }
getRegisteredAgent(id: string): IChatAgent | undefined { throw new Error(); }
getDefaultAgent(): IChatAgent | undefined { throw new Error(); }
getSecondaryAgent(): IChatAgent | undefined { throw new Error(); }
hasAgent(id: string): boolean { throw new Error(); }
updateAgent(id: string, updateMetadata: IChatAgentMetadata): void { throw new Error(); }
}

View file

@ -520,8 +520,8 @@ export class InlineChatController implements IEditorContribution {
const withoutSubCommandLeader = input.slice(1);
const cts = new CancellationTokenSource();
this._sessionStore.add(cts);
for (const agent of this._chatAgentService.getAgents()) {
const commands = await agent.provideSlashCommands(undefined, [], cts.token);
for (const agent of this._chatAgentService.getActivatedAgents()) {
const commands = agent.slashCommands;
if (commands.find((command) => withoutSubCommandLeader.startsWith(command.name))) {
massagedInput = `${chatAgentLeader}${agent.id} ${input}`;
break;

View file

@ -143,49 +143,6 @@ declare module 'vscode' {
readonly kind: ChatResultFeedbackKind;
}
export interface ChatCommand {
/**
* A short name by which this command is referred to in the UI, e.g. `fix` or
* `explain` for commands that fix an issue or explain code.
*
* **Note**: The name should be unique among the commands provided by this participant.
*/
readonly name: string;
/**
* Human-readable description explaining what this command does.
*/
readonly description: string;
/**
* When the user clicks this command in `/help`, this text will be submitted to this command
*/
readonly sampleRequest?: string;
/**
* Whether executing the command puts the chat into a persistent mode, where the command is automatically added to the chat input for the next message.
*/
readonly isSticky?: boolean;
}
/**
* A ChatCommandProvider returns {@link ChatCommands commands} that can be invoked on a chat participant using `/`. For example, `@participant /command`.
* These can be used as shortcuts to let the user explicitly invoke different functionalities provided by the participant.
*/
export interface ChatCommandProvider {
/**
* Returns a list of commands that its participant is capable of handling. A command
* can be selected by the user and will then be passed to the {@link ChatRequestHandler handler}
* via the {@link ChatRequest.command command} property.
*
*
* @param token A cancellation token.
* @returns A list of commands. The lack of a result can be signaled by returning `undefined`, `null`, or
* an empty array.
*/
provideCommands(context: ChatContext, token: CancellationToken): ProviderResult<ChatCommand[]>;
}
/**
* A followup question suggested by the participant.
*/
@ -239,11 +196,6 @@ declare module 'vscode' {
*/
readonly name: string;
/**
* A human-readable description explaining what this participant does.
*/
description?: string;
/**
* Icon for the participant shown in UI.
*/
@ -263,11 +215,6 @@ declare module 'vscode' {
*/
requestHandler: ChatRequestHandler;
/**
* This provider will be called to retrieve the participant's commands.
*/
commandProvider?: ChatCommandProvider;
/**
* This provider will be called once after each request to retrieve suggested followup questions.
*/

View file

@ -22,9 +22,18 @@ declare module 'vscode' {
markdownContent: MarkdownString;
}
/**
* Now only used for the "intent detection" API below
*/
export interface ChatCommand {
readonly name: string;
readonly description: string;
}
// TODO@API fit this into the stream
export interface ChatDetectedParticipant {
participant: string;
// TODO@API validate this against statically-declared slash commands?
command?: ChatCommand;
}
@ -231,20 +240,6 @@ declare module 'vscode' {
kind?: string;
}
export interface ChatCommand {
readonly isSticky2?: {
/**
* Indicates that the command should be automatically repopulated.
*/
isSticky: true;
/**
* This can be set to a string to use a different placeholder message in the input box when the command has been repopulated.
*/
placeholder?: string;
};
}
export interface ChatVariableResolverResponseStream {
/**
* Push a progress part to this stream. Short-hand for
@ -285,4 +280,12 @@ declare module 'vscode' {
*/
resolve2?(name: string, context: ChatVariableContext, stream: ChatVariableResolverResponseStream, token: CancellationToken): ProviderResult<ChatVariableValue[]>;
}
export interface ChatParticipant {
/**
* A human-readable description explaining what this participant does.
* Only allow a static description for normal participants. Here where dynamic participants are allowed, the description must be able to be set as well.
*/
description?: string;
}
}