Skip to content

Commit 9a5ce80

Browse files
authored
feat: consume chat participant detection provider (microsoft#224661)
* feat: consume chat participant detection provider * Fix tests * Fix more tests There is now an async call between when a response is first added into the UI and when the agent for that response is identified with intent detection and invoked. This causes a race condition in the test code to show up where waiting for `State.SHOW_REQUEST` is insufficient to ensure that the agent has been invoked. Working around this with another timeout for now.
1 parent ee47c51 commit 9a5ce80

File tree

4 files changed

+65
-32
lines changed

4 files changed

+65
-32
lines changed

src/vs/workbench/contrib/chat/common/chatAgents.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,9 @@ export interface IChatAgentService {
188188
registerAgentImplementation(id: string, agent: IChatAgentImplementation): IDisposable;
189189
registerDynamicAgent(data: IChatAgentData, agentImpl: IChatAgentImplementation): IDisposable;
190190
registerAgentCompletionProvider(id: string, provider: (query: string, token: CancellationToken) => Promise<IChatAgentCompletionItem[]>): IDisposable;
191-
registerChatParticipantDetectionProvider(handle: number, provider: IChatParticipantDetectionProvider): IDisposable;
192191
getAgentCompletionItems(id: string, query: string, token: CancellationToken): Promise<IChatAgentCompletionItem[]>;
192+
registerChatParticipantDetectionProvider(handle: number, provider: IChatParticipantDetectionProvider): IDisposable;
193+
detectAgentOrCommand(request: IChatAgentRequest, history: IChatAgentHistoryEntry[], options: { location: ChatAgentLocation }, token: CancellationToken): Promise<{ agent: IChatAgentData; command?: IChatAgentCommand } | undefined>;
193194
invokeAgent(agent: string, request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatAgentResult>;
194195
getFollowups(id: string, request: IChatAgentRequest, result: IChatAgentResult, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatFollowup[]>;
195196
getAgent(id: string): IChatAgentData | undefined;

src/vs/workbench/contrib/chat/common/chatServiceImpl.ts

Lines changed: 54 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,15 @@ import { revive } from 'vs/base/common/marshalling';
1515
import { StopWatch } from 'vs/base/common/stopwatch';
1616
import { URI, UriComponents } from 'vs/base/common/uri';
1717
import { localize } from 'vs/nls';
18+
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
1819
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
1920
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
2021
import { ILogService } from 'vs/platform/log/common/log';
2122
import { Progress } from 'vs/platform/progress/common/progress';
2223
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
2324
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
2425
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
25-
import { ChatAgentLocation, IChatAgent, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
26+
import { ChatAgentLocation, IChatAgent, IChatAgentCommand, IChatAgentData, IChatAgentRequest, IChatAgentResult, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
2627
import { CONTEXT_VOTE_UP_ENABLED } from 'vs/workbench/contrib/chat/common/chatContextKeys';
2728
import { ChatModel, ChatRequestModel, ChatRequestRemovalReason, ChatWelcomeMessageModel, IChatModel, IChatRequestModel, IChatRequestVariableData, IChatResponseModel, IExportableChatData, ISerializableChatData, ISerializableChatsData, getHistoryEntriesFromModel, updateRanges } from 'vs/workbench/contrib/chat/common/chatModel';
2829
import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, IParsedChatRequest, chatAgentLeader, chatSubcommandLeader, getPromptText } from 'vs/workbench/contrib/chat/common/chatParserTypes';
@@ -111,7 +112,8 @@ export class ChatService extends Disposable implements IChatService {
111112
@IChatVariablesService private readonly chatVariablesService: IChatVariablesService,
112113
@IChatAgentService private readonly chatAgentService: IChatAgentService,
113114
@IWorkbenchAssignmentService workbenchAssignmentService: IWorkbenchAssignmentService,
114-
@IContextKeyService contextKeyService: IContextKeyService
115+
@IContextKeyService contextKeyService: IContextKeyService,
116+
@IConfigurationService private readonly configurationService: IConfigurationService
115117
) {
116118
super();
117119

@@ -549,35 +551,60 @@ export class ChatService extends Disposable implements IChatService {
549551
let rawResult: IChatAgentResult | null | undefined;
550552
let agentOrCommandFollowups: Promise<IChatFollowup[] | undefined> | undefined = undefined;
551553

554+
552555
if (agentPart || (defaultAgent && !commandPart)) {
553-
const agent = (agentPart?.agent ?? defaultAgent)!;
556+
const prepareChatAgentRequest = async (agent: IChatAgentData, command?: IChatAgentCommand, chatRequest?: ChatRequestModel) => {
557+
const initVariableData: IChatRequestVariableData = { variables: [] };
558+
request = chatRequest ?? model.addRequest(parsedRequest, initVariableData, attempt, agent, command, options?.confirmation);
559+
560+
// Variables may have changed if the agent and slash command changed, so resolve them again even if we already had a chatRequest
561+
const variableData = await this.chatVariablesService.resolveVariables(parsedRequest, options?.attachedContext, model, progressCallback, token);
562+
model.updateRequest(request, variableData);
563+
const promptTextResult = getPromptText(request.message);
564+
const updatedVariableData = updateRanges(variableData, promptTextResult.diff); // TODO bit of a hack
565+
566+
return {
567+
sessionId,
568+
requestId: request.id,
569+
agentId: agent.id,
570+
message: promptTextResult.message,
571+
command: command?.name,
572+
variables: updatedVariableData,
573+
enableCommandDetection,
574+
attempt,
575+
location,
576+
locationData: options?.locationData,
577+
acceptedConfirmationData: options?.acceptedConfirmationData,
578+
rejectedConfirmationData: options?.rejectedConfirmationData,
579+
} satisfies IChatAgentRequest;
580+
};
581+
582+
let detectedAgent: IChatAgentData | undefined;
583+
let detectedCommand: IChatAgentCommand | undefined;
584+
if (this.configurationService.getValue('chat.experimental.detectParticipant.enabled') && !agentPart && !commandPart) {
585+
// We have no agent or command to scope history with, pass the full history to the participant detection provider
586+
const defaultAgentHistory = getHistoryEntriesFromModel(model, defaultAgent.id);
587+
588+
// Prepare the request object that we will send to the participant detection provider
589+
const chatAgentRequest = await prepareChatAgentRequest(defaultAgent, agentSlashCommandPart?.command);
590+
591+
const result = await this.chatAgentService.detectAgentOrCommand(chatAgentRequest, defaultAgentHistory, { location }, token);
592+
if (result) {
593+
// Update the response in the ChatModel to reflect the detected agent and command
594+
request.response?.setAgent(result.agent, result.command);
595+
detectedAgent = result.agent;
596+
detectedCommand = result.command;
597+
}
598+
}
599+
600+
const agent = (detectedAgent ?? agentPart?.agent ?? defaultAgent)!;
601+
const command = detectedCommand ?? agentSlashCommandPart?.command;
554602
await this.extensionService.activateByEvent(`onChatParticipant:${agent.id}`);
555-
const history = getHistoryEntriesFromModel(model, agentPart?.agent.id);
556603

557-
const initVariableData: IChatRequestVariableData = { variables: [] };
558-
request = model.addRequest(parsedRequest, initVariableData, attempt, agent, agentSlashCommandPart?.command, options?.confirmation);
604+
// Recompute history in case the agent or command changed
605+
const history = getHistoryEntriesFromModel(model, agent.id);
606+
const requestProps = await prepareChatAgentRequest(agent, command, request /* Reuse the request object if we already created it for participant detection */);
559607
completeResponseCreated();
560-
const variableData = await this.chatVariablesService.resolveVariables(parsedRequest, options?.attachedContext, model, progressCallback, token);
561-
model.updateRequest(request, variableData);
562-
563-
const promptTextResult = getPromptText(request.message);
564-
const updatedVariableData = updateRanges(variableData, promptTextResult.diff); // TODO bit of a hack
565-
566-
const requestProps: IChatAgentRequest = {
567-
sessionId,
568-
requestId: request.id,
569-
agentId: agent.id,
570-
message: promptTextResult.message,
571-
command: agentSlashCommandPart?.command.name,
572-
variables: updatedVariableData,
573-
enableCommandDetection,
574-
attempt,
575-
location,
576-
locationData: options?.locationData,
577-
acceptedConfirmationData: options?.acceptedConfirmationData,
578-
rejectedConfirmationData: options?.rejectedConfirmationData,
579-
};
580-
581608
const agentResult = await this.chatAgentService.invokeAgent(agent.id, requestProps, progressCallback, history, token);
582609
rawResult = agentResult;
583610
agentOrCommandFollowups = this.chatAgentService.getFollowups(agent.id, requestProps, agentResult, history, followupsCancelToken);

src/vs/workbench/contrib/chat/test/common/chatService.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { URI } from 'vs/base/common/uri';
99
import { assertSnapshot } from 'vs/base/test/common/snapshot';
1010
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
1111
import { Range } from 'vs/editor/common/core/range';
12+
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
13+
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
1214
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
1315
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
1416
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
@@ -85,6 +87,7 @@ suite('ChatService', () => {
8587
instantiationService.stub(IViewsService, new TestExtensionService());
8688
instantiationService.stub(IWorkspaceContextService, new TestContextService());
8789
instantiationService.stub(IChatSlashCommandService, testDisposables.add(instantiationService.createInstance(ChatSlashCommandService)));
90+
instantiationService.stub(IConfigurationService, new TestConfigurationService());
8891
instantiationService.stub(IChatService, new MockChatService());
8992

9093
chatAgentService = instantiationService.createInstance(ChatAgentService);

src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,6 @@ import { InlineChatController, InlineChatRunOptions, State } from 'vs/workbench/
3737
import { Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession';
3838
import { CTX_INLINE_CHAT_USER_DID_EDIT, EditMode, InlineChatConfigKeys } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
3939
import { TestViewsService, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices';
40-
import { IInlineChatSavingService } from '../../browser/inlineChatSavingService';
41-
import { IInlineChatSessionService } from '../../browser/inlineChatSessionService';
42-
import { InlineChatSessionServiceImpl } from '../../browser/inlineChatSessionServiceImpl';
43-
import { TestWorkerService } from './testWorkerService';
4440
import { IExtensionService, nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
4541
import { IChatProgress, IChatService } from 'vs/workbench/contrib/chat/common/chatService';
4642
import { ChatService } from 'vs/workbench/contrib/chat/common/chatServiceImpl';
@@ -65,6 +61,10 @@ import { CancellationToken } from 'vs/base/common/cancellation';
6561
import { assertType } from 'vs/base/common/types';
6662
import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService';
6763
import { NullWorkbenchAssignmentService } from 'vs/workbench/services/assignment/test/common/nullAssignmentService';
64+
import { IInlineChatSavingService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSavingService';
65+
import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionService';
66+
import { InlineChatSessionServiceImpl } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl';
67+
import { TestWorkerService } from 'vs/workbench/contrib/inlineChat/test/browser/testWorkerService';
6868

6969
suite('InteractiveChatController', function () {
7070

@@ -768,6 +768,7 @@ suite('InteractiveChatController', function () {
768768
const p = ctrl.awaitStates([...TestController.INIT_SEQUENCE, State.SHOW_REQUEST]);
769769
ctrl.run({ message: 'Hello-', autoSend: true });
770770
assert.strictEqual(await p, undefined);
771+
await timeout(10);
771772
assert.deepStrictEqual(attempts, [0]);
772773

773774
// RERUN (cancel, undo, redo)
@@ -806,6 +807,7 @@ suite('InteractiveChatController', function () {
806807
// REQUEST 1
807808
const p = ctrl.awaitStates([...TestController.INIT_SEQUENCE, State.SHOW_REQUEST]);
808809
ctrl.run({ message: 'Hello', autoSend: true });
810+
await timeout(10);
809811
assert.strictEqual(await p, undefined);
810812

811813
assertType(progress);

0 commit comments

Comments
 (0)