Path: blob/main/extensions/copilot/src/extension/prompt/node/intentDetector.tsx
13399 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { BasePromptElementProps, PromptElement, PromptElementProps, PromptMetadata, Raw, SystemMessage, UserMessage } from '@vscode/prompt-tsx';6import type { CancellationToken, ChatContext, ChatParticipantDetectionProvider, ChatParticipantDetectionResult, ChatParticipantMetadata, ChatRequest, Uri, ChatLocation as VscodeChatLocation } from 'vscode';7import { CHAT_PARTICIPANT_ID_PREFIX, getChatParticipantIdFromName } from '../../../platform/chat/common/chatAgents';8import { ChatFetchResponseType, ChatLocation, ChatResponse } from '../../../platform/chat/common/commonTypes';9import { getTextPart, roleToString } from '../../../platform/chat/common/globalStringUtils';10import { ConfigKey, IConfigurationService } from '../../../platform/configuration/common/configurationService';11import { TextDocumentSnapshot } from '../../../platform/editing/common/textDocumentSnapshot';12import { IEndpointProvider } from '../../../platform/endpoint/common/endpointProvider';13import { IIgnoreService } from '../../../platform/ignore/common/ignoreService';14import { ILogService } from '../../../platform/log/common/logService';15import { ITabsAndEditorsService } from '../../../platform/tabs/common/tabsAndEditorsService';16import { IExperimentationService } from '../../../platform/telemetry/common/nullExperimentationService';17import { ITelemetryService } from '../../../platform/telemetry/common/telemetry';18import { isNotebookCellOrNotebookChatInput } from '../../../util/common/notebooks';19import { isFalsyOrEmpty } from '../../../util/vs/base/common/arrays';20import { IInstantiationService } from '../../../util/vs/platform/instantiation/common/instantiation';21import { Position, Range } from '../../../vscodeTypes';22import { getAgentForIntent, GITHUB_PLATFORM_AGENT, Intent } from '../../common/constants';23import { IIntentService } from '../../intents/node/intentService';24import { UnknownIntent } from '../../intents/node/unknownIntent';25import { InstructionMessage } from '../../prompts/node/base/instructionMessage';26import { PromptRenderer, renderPromptElement } from '../../prompts/node/base/promptRenderer';27import { ChatVariablesAndQuery } from '../../prompts/node/panel/chatVariables';28import { ConversationHistory, HistoryWithInstructions } from '../../prompts/node/panel/conversationHistory';29import { CurrentSelection } from '../../prompts/node/panel/currentSelection';30import { CodeBlock } from '../../prompts/node/panel/safeElements';31import { getToolName } from '../../tools/common/toolNames';32import { CodebaseTool } from '../../tools/node/codebaseTool';33import { ChatVariablesCollection } from '../common/chatVariablesCollection';34import { Turn } from '../common/conversation';35import { addHistoryToConversation } from './chatParticipantRequestHandler';36import { IDocumentContext } from './documentContext';37import { IIntent } from './intents';38import { ConversationalBaseTelemetryData } from './telemetry';3940export class IntentDetector implements ChatParticipantDetectionProvider {4142constructor(43@ILogService private readonly logService: ILogService,44@IEndpointProvider private readonly endpointProvider: IEndpointProvider,45@ITelemetryService private readonly telemetryService: ITelemetryService,46@IConfigurationService private readonly configurationService: IConfigurationService,47@IIntentService private readonly intentService: IIntentService,48@IInstantiationService private readonly instantiationService: IInstantiationService,49@ITabsAndEditorsService private readonly tabsAndEditorsService: ITabsAndEditorsService,50@IExperimentationService private readonly experimentationService: IExperimentationService,51) { }5253async provideParticipantDetection(chatRequest: ChatRequest, context: ChatContext, options: { participants?: ChatParticipantMetadata[]; location: VscodeChatLocation }, token: CancellationToken): Promise<ChatParticipantDetectionResult | null | undefined> {54if ((options.location !== ChatLocation.Panel && options.location !== ChatLocation.Editor) || this.configurationService.getNonExtensionConfig('chat.detectParticipant.enabled') === false) {55return;56}5758const selectedEndpoint = await this.endpointProvider.getChatEndpoint(chatRequest);59// Disable intent detection if the user is requesting their request be completed with o1 since o1 has such a low RPS the cost of an incorrect intent is high60if (selectedEndpoint.family.startsWith('o1')) {61return;62}6364const chatVariables = new ChatVariablesCollection(chatRequest.references);65const { turns } = this.instantiationService.invokeFunction(accessor => addHistoryToConversation(accessor, context.history));66let detectedIntentId: string | ChatParticipantDetectionResult | undefined;67const shouldIncludeGitHub = (chatRequest.toolReferences.length === 0);68const builtinParticipants = options.participants?.filter(p => ((p.participant === GITHUB_PLATFORM_AGENT && shouldIncludeGitHub) || p.participant.startsWith(CHAT_PARTICIPANT_ID_PREFIX)) && p.disambiguation.length) ?? [];69const thirdPartyParticipants = options.participants?.filter(p => p.participant !== GITHUB_PLATFORM_AGENT && !p.participant.startsWith(CHAT_PARTICIPANT_ID_PREFIX) && p.disambiguation.length) ?? [];7071try {7273const detectedIntent = await this.detectIntent(74options.location,75IDocumentContext.inferDocumentContext(chatRequest, this.tabsAndEditorsService.activeTextEditor, turns),76chatRequest.prompt,77token,78undefined,79chatVariables,80builtinParticipants,81undefined,82turns,83);848586if (detectedIntent && 'participant' in detectedIntent) {87if (detectedIntent.participant === getChatParticipantIdFromName('workspace')) {88if (chatRequest.toolReferences.find((ref) => getToolName(ref.name) === CodebaseTool.toolName)) {89return undefined;90}9192if (this.configurationService.getExperimentBasedConfig<boolean>(ConfigKey.TeamInternal.AskAgent, this.experimentationService)93&& chatRequest.model.capabilities.supportsToolCalling) {94return undefined;95}96}9798detectedIntentId = detectedIntent;99return detectedIntent;100} else if (detectedIntent) {101detectedIntentId = detectedIntent.id;102const agent = getAgentForIntent(detectedIntent.id as Intent, options.location);103104if (agent) {105const overrideCommand = agent.agent === Intent.Editor && (agent.command === Intent.Edit || agent.command === Intent.Generate) ? undefined : agent.command;106107return {108participant: getChatParticipantIdFromName(agent.agent),109command: overrideCommand,110};111}112} else if (thirdPartyParticipants.length && options.location === ChatLocation.Panel) {113// If the detected intent is `unknown` and we have 3P participants, try picking from them instead114const detectedIntent = await this.detectIntent(115options.location,116undefined,117chatRequest.prompt,118token,119undefined,120new ChatVariablesCollection(chatRequest.references),121builtinParticipants,122thirdPartyParticipants,123turns,124);125126if (detectedIntent && 'participant' in detectedIntent) {127detectedIntentId = detectedIntent;128return detectedIntent;129}130}131} finally {132if (detectedIntentId) {133// Collect telemetry based on the full unfiltered history, rather than when the request handler is invoked (at which point the conversation history is already scoped)134const doc = this.tabsAndEditorsService.activeTextEditor?.document;135const docSnapshot = doc ? TextDocumentSnapshot.create(doc) : undefined;136this.collectIntentDetectionContextInternal(137chatRequest.prompt,138detectedIntentId,139chatVariables,140options.location,141turns.slice(0, -1),142docSnapshot143);144}145}146}147148private async getPreferredIntent(location: ChatLocation, documentContext: IDocumentContext | undefined, history?: readonly Turn[], messageText?: string) {149let preferredIntent: Intent | undefined;150if (location === ChatLocation.Editor && documentContext && !history?.length) {151if (documentContext.selection.isEmpty && documentContext.document.lineAt(documentContext.selection.start.line).text.trim() === '') {152preferredIntent = Intent.Generate;153} else if (!documentContext.selection.isEmpty && documentContext.selection.start.line !== documentContext.selection.end.line) {154preferredIntent = Intent.Edit;155}156}157// /fixTestFailure was removed, delegate to /fix if there are historical usages of it.158if (messageText?.trimStart().startsWith('/fixTestFailure')) {159preferredIntent = Intent.Fix;160}161162return preferredIntent;163}164165/**166* @param preferredIntent tells the model that this intent is the most likely the developer wants.167* @param currentFilePath file path relative to the workspace root and will be mentioned in the prompt if present168* @param isRerunWithoutIntentDetection for telemetry purposes -- if `undefined`, then intent detection is invoked either from inline chat of an older vscode or from panel chat169*/170async detectIntent(171location: ChatLocation,172documentContext: IDocumentContext | undefined,173messageText: string,174token: CancellationToken,175baseUserTelemetry: ConversationalBaseTelemetryData | undefined,176chatVariables: ChatVariablesCollection,177builtinParticipants: ChatParticipantMetadata[],178thirdPartyParticipants?: ChatParticipantMetadata[],179history?: readonly Turn[],180): Promise<IIntent | ChatParticipantDetectionResult | undefined> {181182this.logService.trace('Building intent detector');183184if (builtinParticipants.length === 0 && (isFalsyOrEmpty(thirdPartyParticipants))) {185this.logService.trace('No participants available for intent detection');186return undefined;187}188189const endpoint = await this.endpointProvider.getChatEndpoint('copilot-fast');190191const preferredIntent = await this.getPreferredIntent(location, documentContext, history, messageText);192193const promptRenderer = PromptRenderer.create(194this.instantiationService,195endpoint,196(location === ChatLocation.Editor197? IntentDetectionPrompt198: GPT4OIntentDetectionPrompt),199{200preferredIntent,201location,202userQuestion: messageText,203documentContext,204history,205chatVariables,206builtinParticipants: builtinParticipants,207thirdPartyParticipants: thirdPartyParticipants,208}209);210211const { messages, metadata } = await promptRenderer.render(undefined, token);212this.logService.trace('Built intent detector');213214const fetchResult = await endpoint.makeChatRequest(215'intentDetection',216messages,217undefined,218token,219location,220undefined,221{222stop: [';'],223max_tokens: 20224}225);226const intent = this.validateResult(fetchResult, baseUserTelemetry, messageText, location, preferredIntent, thirdPartyParticipants ? thirdPartyParticipants : builtinParticipants, documentContext);227const chosenIntent = intent && 'id' in intent ? intent?.id : intent?.participant;228229this.sendTelemetry(230preferredIntent,231chosenIntent,232documentContext?.language.languageId,233undefined,234location235);236237const fileMetadata = metadata.get(DocumentExcerptInfo);238239this.sendInternalTelemetry(240messageText,241preferredIntent,242fileMetadata?.filePath,243fileMetadata?.fileExcerpt,244chosenIntent,245documentContext?.language.languageId,246undefined,247messages.slice(0, -1),248location249);250251return intent;252}253254async collectIntentDetectionContextInternal(255userQuery: string,256assignedIntent: string | ChatParticipantDetectionResult,257chatVariables: ChatVariablesCollection,258location: ChatLocation,259history: Turn[] = [],260document?: TextDocumentSnapshot261) {262const endpoint = await this.endpointProvider.getChatEndpoint('copilot-fast');263264const { messages: currentSelection } = await renderPromptElement(this.instantiationService, endpoint, CurrentSelection, { document });265const { messages: conversationHistory } = await renderPromptElement(this.instantiationService, endpoint, ConversationHistory, { history, priority: 1000 }, undefined, undefined).catch(() => ({ messages: [] }));266267const { history: historyMessages, fileExcerpt, attachedContext, fileExcerptExceedsBudget } = this.prepareInternalTelemetryContext(getTextPart(currentSelection?.[0]?.content), conversationHistory, chatVariables);268269this.telemetryService.sendInternalMSFTTelemetryEvent(270'participantDetectionContext',271{272chatLocation: ChatLocation.toString(location),273userQuery,274history: historyMessages.join(''),275assignedIntent: typeof assignedIntent === 'string' ? assignedIntent : undefined,276assignedThirdPartyChatParticipant: typeof assignedIntent !== 'string' ? assignedIntent.participant : undefined,277assignedThirdPartyChatCommand: typeof assignedIntent !== 'string' ? assignedIntent.command : undefined,278fileExcerpt: fileExcerpt ?? (fileExcerptExceedsBudget ? '<truncated>' : '<none>'),279attachedContext: attachedContext.join(';')280},281{}282);283}284285private validateResult(286fetchResult: ChatResponse,287baseUserTelemetry: ConversationalBaseTelemetryData | undefined,288messageText: string,289location: ChatLocation,290preferredIntent?: Intent,291participants?: ChatParticipantMetadata[],292documentContext?: IDocumentContext | undefined,293): IIntent | ChatParticipantDetectionResult | undefined {294295if (fetchResult.type !== ChatFetchResponseType.Success) {296if (baseUserTelemetry) {297this.sendPromptIntentErrorTelemetry(baseUserTelemetry, fetchResult);298}299return undefined;300}301302let cleanedIntentResponses = [fetchResult.value].map(intentResponse =>303intentResponse304.trimStart()305.split('\n')[0]306.replaceAll('```', '')307.replace(/function id:|response:/i, '')308.trim());309310cleanedIntentResponses = cleanedIntentResponses.filter(i => i !== UnknownIntent.ID);311if (!cleanedIntentResponses.length && preferredIntent) {312cleanedIntentResponses = [preferredIntent];313}314315// Dynamic chat participants316if ((cleanedIntentResponses[0] === 'github_questions') && participants?.find(p => p.participant === GITHUB_PLATFORM_AGENT)) {317return { participant: GITHUB_PLATFORM_AGENT };318}319320const categoryNamesToParticipants = participants?.reduce<{ [categoryName: string]: { participant: string; command?: string } }>((acc, participant) => {321participant.disambiguation.forEach((alias) => {322acc[alias.category] = { participant: participant.participant, command: participant.command };323});324return acc;325}, {});326327let intent = cleanedIntentResponses328.map(r => this.intentService.getIntent(r, location) ?? categoryNamesToParticipants?.[r])329.filter((s): s is (IIntent | { participant: string; command?: string }) => s !== undefined)?.[0];330331const chosenIntent = intent && 'id' in intent ? intent?.id : intent?.participant;332333this.logService.debug(`picked intent "${chosenIntent}" from ${JSON.stringify(fetchResult.value, null, '\t')}`);334335// override /edit in inline chat based on the document context336if (location === ChatLocation.Editor337&& chosenIntent === Intent.Edit338&& documentContext339&& documentContext.selection.isEmpty340&& documentContext.document.lineAt(documentContext.selection.start.line).text.trim() === ''341) {342// the selection is empty and sits on a whitespace only line, we will always detect generate instead of edit343const editIntent = this.intentService.getIntent(Intent.Generate, ChatLocation.Editor);344if (editIntent) {345intent = editIntent;346}347}348349350if (baseUserTelemetry) {351const promptTelemetryData = baseUserTelemetry.extendedBy({352messageText,353promptContext: cleanedIntentResponses.join(),354intent: chosenIntent || 'unknown',355});356this.telemetryService.sendEnhancedGHTelemetryEvent('conversation.promptIntent', promptTelemetryData.raw.properties, promptTelemetryData.raw.measurements);357}358return intent;359}360361private sendPromptIntentErrorTelemetry(362baseUserTelemetry: ConversationalBaseTelemetryData,363fetchResult: { type: string; reason: string; requestId: string }364) {365const telemetryErrorData = baseUserTelemetry.extendedBy({366resultType: fetchResult.type,367reason: fetchResult.reason,368});369this.telemetryService.sendEnhancedGHTelemetryErrorEvent('conversation.promptIntentError', telemetryErrorData.raw.properties, telemetryErrorData.raw.measurements);370}371372private sendTelemetry(373preferredIntent: Intent | undefined,374detectedIntent: string | undefined,375languageId: string | undefined,376isRerunWithoutIntentDetection: boolean | undefined,377location: ChatLocation378) {379/* __GDPR__380"intentDetection" : {381"owner": "ulugbekna",382"comment": "Intent detection telemetry.",383"chatLocation": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Which chat (panel or inline) intent detection is used for." },384"preferredIntent": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Which intent was initially provided as preferred." },385"detectedIntent": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Intent that was detected by Copilot" },386"languageId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Language ID of the document for which intent detection happened." },387"isRerunWithoutIntentDetection": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Whether the user disliked the detected intent and tried to rerun without it." }388}389*/390this.telemetryService.sendMSFTTelemetryEvent(391'intentDetection',392{393chatLocation: ChatLocation.toString(location),394preferredIntent: preferredIntent ?? '<none>',395detectedIntent: detectedIntent ?? '<none>',396languageId: languageId ?? '<none>',397isRerunWithoutIntentDetection: String(isRerunWithoutIntentDetection) ?? '<none>',398}399);400}401402private prepareInternalTelemetryContext(fileExcerpt: string | undefined, historyMessages: Raw.ChatMessage[], attachedContext?: ChatVariablesCollection) {403404// Single internal telemetry size must be less than 8KB405// Be conservative and set the budget to 5KB to account for other properties406let telemetryBudget = 5000;407408const names: string[] = [];409if (attachedContext) {410for (const attachment of attachedContext) {411const nameLength = Buffer.byteLength(attachment.uniqueName, 'utf8');412if (telemetryBudget - nameLength < 0) {413break;414}415telemetryBudget -= nameLength;416names.push(attachment.uniqueName);417}418}419420let fileExcerptExceedsBudget = false;421if (fileExcerpt) {422const fileExcerptSize = Buffer.byteLength(fileExcerpt, 'utf8');423if (fileExcerptSize > telemetryBudget) {424fileExcerptExceedsBudget = true;425fileExcerpt = undefined;426} else {427telemetryBudget -= fileExcerptSize;428}429} else {430fileExcerpt = undefined;431}432433const history: string[] = [];434for (let i = historyMessages.length - 1; i >= 0; i -= 1) {435const message = historyMessages[i];436const text = `${roleToString(message.role).toUpperCase()}: ${message.content}\n\n`;437const textLength = Buffer.byteLength(text, 'utf8');438if (telemetryBudget - textLength < 0) {439break;440}441history.push(text);442telemetryBudget -= textLength;443}444445return { fileExcerpt, fileExcerptExceedsBudget, history: history.reverse(), attachedContext: names };446}447448private sendInternalTelemetry(449request: string,450preferredIntent: Intent | undefined,451currentFilePath: string | undefined,452fileExerpt: string | undefined,453detectedIntent: string | undefined,454languageId: string | undefined,455isRerunWithoutIntentDetection: boolean | undefined,456historyMessages: Raw.ChatMessage[],457location: ChatLocation458) {459const { fileExcerpt, history } = this.prepareInternalTelemetryContext(fileExerpt, historyMessages);460461this.telemetryService.sendInternalMSFTTelemetryEvent(462'intentDetection',463{464chatLocation: ChatLocation.toString(location),465request,466preferredIntent: preferredIntent ?? '<none>',467filePath: currentFilePath ?? '<none>',468fileExerpt: fileExcerpt ?? '<none>',469detectedIntent: detectedIntent ?? '<none>',470languageId: languageId ?? '<none>',471isRerunWithoutIntentDetection: String(isRerunWithoutIntentDetection) ?? '<none>',472history: history.join('')473},474{}475);476}477}478479class DocumentExcerptInfo extends PromptMetadata {480constructor(481readonly fileExcerpt: string | undefined,482readonly filePath: string | undefined,483) {484super();485}486}487488type IntentDetectionPromptProps = PromptElementProps<{489history?: readonly Turn[];490preferredIntent: Intent | undefined;491location: ChatLocation;492userQuestion: string;493documentContext: IDocumentContext | undefined;494chatVariables: ChatVariablesCollection;495builtinParticipants: ChatParticipantMetadata[];496thirdPartyParticipants?: ChatParticipantMetadata[];497}>;498499class IntentDetectionPrompt extends PromptElement<IntentDetectionPromptProps> {500501constructor(502props: IntentDetectionPromptProps,503@IIgnoreService protected readonly _ignoreService: IIgnoreService,504@IConfigurationService protected readonly _configurationService: IConfigurationService,505@IIntentService protected readonly _intentService: IIntentService,506) {507super(props);508}509510async render() {511let {512builtinParticipants,513preferredIntent,514userQuestion,515documentContext,516} = this.props;517518let currentFileUri: Uri | undefined;519let currentFileContext: string | undefined;520let fileExcerptCodeBlock: CodeBlock | undefined;521try {522if (documentContext !== undefined && !(await this._ignoreService.isCopilotIgnored(documentContext.document.uri))) {523524const { document, selection } = documentContext;525526currentFileUri = document.uri;527const range = new Range(528new Position(Math.max(selection.start.line - 5, 0), 0),529new Position(Math.min(selection.end.line + 5, document.lineCount), document.lineAt(selection.end.line).text.length),530);531currentFileContext = document.getText(range);532fileExcerptCodeBlock = currentFileContext.trim().length > 0 ? <CodeBlock uri={currentFileUri} languageId={document.languageId} code={currentFileContext} shouldTrim={false} /> : undefined;533}534} catch (_e) { }535536const fileMetadata = new DocumentExcerptInfo(currentFileContext, currentFileUri?.path);537538if (documentContext !== undefined && isNotebookCellOrNotebookChatInput(documentContext.document.uri)) {539builtinParticipants = builtinParticipants.filter((participant) => participant.command !== 'tests');540}541542543function commands(participant: ChatParticipantMetadata[]) {544const seen = new Set<string>();545546const a = participant.flatMap((p) => p.disambiguation);547548return a.filter(value => {549if (seen.has(value.category)) {550return false;551}552seen.add(value.category);553return true;554});555}556557return (558<>559<meta value={fileMetadata} />560<SystemMessage>561When asked for your name, you must respond with "GitHub Copilot".<br />562Follow the user's requirements carefully & to the letter.<br />563</SystemMessage>564<UserMessage>565A software developer is using an AI chatbot in a code editor{currentFileUri && ` in file ${currentFileUri.path}`}.<br />566{fileExcerptCodeBlock === undefined567? <br />568: <>569Current active file contains following excerpt:<br />570{fileExcerptCodeBlock}<br />571</>}572The developer added the following request to the chat and your goal is to select a function to perform the request.<br />573{preferredIntent && `The developer probably wants Function Id '${preferredIntent}', pick different only if you're certain.`}<br />574Request: {userQuestion}<br />575<br />576Available functions:<br />577{commands(builtinParticipants).map((alias) =>578<>579Function Id: {alias.category}<br />580Function Description: {alias.description}<br />581<br />582</>583)}584<br />585Here are some examples to make the instructions clearer:<br />586{commands(builtinParticipants).map((alias) =>587<>588Request: {alias.examples[0]}<br />589Response: {alias.category}<br />590<br />591</>)592}593Request: {userQuestion}<br />594Response:595</UserMessage>596</>597);598}599600601}602603interface BuiltinParticipantDescriptionsProps extends BasePromptElementProps {604includeDynamicParticipants: boolean;605participants: ChatParticipantMetadata[];606}607608class ParticipantDescriptions extends PromptElement<BuiltinParticipantDescriptionsProps> {609override render() {610return (<>611{this.props.participants.flatMap((p) => {612return p.disambiguation.map((alias) => {613return (614<>615| {alias.category ?? (alias as any).categoryName} | {alias.description} | {alias.examples.length ? alias.examples.map(example => `"${example}"`).join(', ') : '--'} |<br />616</>617);618});619})}620{this.props.includeDynamicParticipants && <>| github_questions | The user is asking about an issue, pull request, branch, commit hash, diff, discussion, repository, or published release on GitHub.com. This category does not include performing local Git operations using the CLI. | "What has been changed in the pull request 1361 in browserify/browserify repo?" |<br /></>}621{this.props.includeDynamicParticipants && <>| web_questions | The user is asking a question that requires current knowledge from a web search engine. Such questions often reference time periods that exceed your knowledge cutoff. | "What is the latest LTS version of Node.js?" |<br /></>}622| unknown | The user's question does not fit exactly one of the categories above, is about a product other than Visual Studio Code or GitHub, or is a general question about code, code errors, or software engineering. | "How do I center a div in CSS?" |<br /></>);623}624}625626export class GPT4OIntentDetectionPrompt extends IntentDetectionPrompt {627628override render() {629const { history, chatVariables, userQuestion } = this.props;630631return (<>632<HistoryWithInstructions history={history || []} passPriority historyPriority={800}>633<InstructionMessage>634You are a helpful AI programming assistant to a user who is a software engineer, acting on behalf of the Visual Studio Code editor. Your task is to choose one category from the Markdown table of categories below that matches the user's question. Carefully review the user's question, any previous messages, and any provided context such as code snippets. Respond with just the category name. Your chosen category will help Visual Studio Code provide the user with a higher-quality response, and choosing incorrectly will degrade the user's experience of using Visual Studio Code, so you must choose wisely. If you cannot choose just one category, or if none of the categories seem like they would provide the user with a better result, you must always respond with "unknown".<br />635<br />636| Category name | Category description | Example of matching question |<br />637| -- | -- | -- |<br />638<ParticipantDescriptions participants={this.props.thirdPartyParticipants ? this.props.thirdPartyParticipants : this.props.builtinParticipants} includeDynamicParticipants={!this.props.thirdPartyParticipants} />639</InstructionMessage>640</HistoryWithInstructions>641{<ChatVariablesAndQuery query={userQuestion} chatVariables={chatVariables} priority={900} embeddedInsideUserMessage={false} />}642</>643);644}645}646647648