Path: blob/main/extensions/copilot/src/extension/intents/node/testIntent/userQueryParser.tsx
13405 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 { PromptElement, PromptElementProps, PromptSizing, SystemMessage, UserMessage } from '@vscode/prompt-tsx';6import { ChatLocation } from '../../../../platform/chat/common/commonTypes';7import { IEndpointProvider } from '../../../../platform/endpoint/common/endpointProvider';8import { ILogService } from '../../../../platform/log/common/logService';9import { CancellationToken } from '../../../../util/vs/base/common/cancellation';10import { IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation';11import { PromptRenderer } from '../../../prompts/node/base/promptRenderer';121314type ParsedUserQuery = {15/**16* File reference to test.17*/18fileToTest?: string;19/**20* Symbols in {fileToTest} to generate tests for.21* Can be undefined if cannot be identified from user query.22*/23symbolsToTest?: string[];24};2526export class UserQueryParser {27constructor(28@IEndpointProvider private readonly endpointProvider: IEndpointProvider,29@IInstantiationService private readonly instantiationService: IInstantiationService,30@ILogService private readonly logService: ILogService,31) { }3233public async parse(query: string): Promise<ParsedUserQuery | null> {34const endpoint = await this.endpointProvider.getChatEndpoint('copilot-fast');35const promptRenderer = PromptRenderer.create(36this.instantiationService,37endpoint,38Prompt,39{ query }40);41const renderResult = await promptRenderer.render();42const r = await endpoint.makeChatRequest(43'testGenParseUserQuery',44renderResult.messages,45undefined,46CancellationToken.None,47ChatLocation.Other48);49return r.type === 'success' ? this.processResponse(r.value) : null;50}5152private processResponse(response: string) {5354// remove first (1-based) and last lines of response if they're backticks (```)55const lines = response.split(/\r\n|\r|\n/).filter(s => s !== '');56if (lines.at(0) !== '```') {57lines.splice(0, 1);58if (lines.at(-1) === '```') {59lines.splice(lines.length - 1, 1);60}61response = lines.join('\n');62}6364let parsedJson: unknown;65try {66parsedJson = JSON.parse(response);67} catch (e) {68this.logService.error(`Failed to parse user query response\nResponse:\n${response}\nError:\n${e}`);69return null;70}71return this.isParsedUserQuery(parsedJson) ? parsedJson : null;72}7374private isParsedUserQuery(obj: unknown): obj is ParsedUserQuery {75if (typeof obj !== 'object' || obj === null) {76return false;77}7879const parsedUserQuery = obj as ParsedUserQuery;8081if (parsedUserQuery.fileToTest !== undefined && typeof parsedUserQuery.fileToTest !== 'string') {82return false;83}8485if (parsedUserQuery.symbolsToTest !== undefined) {86if (!Array.isArray(parsedUserQuery.symbolsToTest)) {87return false;88}89for (const symbol of parsedUserQuery.symbolsToTest) {90if (typeof symbol !== 'string') {91return false;92}93}94}9596return true;97}9899}100101type Props = PromptElementProps<{102query: string;103}>;104105class Prompt extends PromptElement<Props> {106107constructor(108props: PromptElementProps<{ query: string }>,109) {110super(props);111}112113override render(state: void, sizing: PromptSizing) {114115const { query } = this.props;116117const format = `118You are a helpful assistant that parses user queries.119The user is a software developer that is asking an AI programming assistant to generate tests.120Your job is to parse the user query into a JSON object of the following shape:121122\`\`\`typescript123{124/**125* File reference to test.126*/127fileToTest?: string;128/**129* Symbols in {fileToTest} to generate tests for.130* Can be undefined if cannot be identified from user query.131*/132symbolsToTest?: string[];133}134\`\`\`135136You must return a JSON object of the given shape.137`;138return (<>139<SystemMessage>140{format}141</SystemMessage>142<UserMessage>143User query: {query}<br />144Parsed query:<br />145</UserMessage>146</>);147}148}149150151