Path: blob/main/extensions/copilot/src/extension/intents/node/testIntent/setupTestsFrameworkQueryInvocation.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 * as l10n from '@vscode/l10n';6import { BasePromptElementProps, PromptElement, PromptPiece, PromptSizing, RenderPromptResult, SystemMessage, UserMessage } from '@vscode/prompt-tsx';7import type * as vscode from 'vscode';8import { IResponsePart } from '../../../../platform/chat/common/chatMLFetcher';9import { ChatLocation } from '../../../../platform/chat/common/commonTypes';10import { IRunCommandExecutionService } from '../../../../platform/commands/common/runCommandExecutionService';11import { TextDocumentSnapshot } from '../../../../platform/editing/common/textDocumentSnapshot';12import { IChatEndpoint } from '../../../../platform/networking/common/networking';13import { testExtensionsForLanguage } from '../../../../platform/testing/common/setupTestExtensions';14import { CancellationToken } from '../../../../util/vs/base/common/cancellation';15import { IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation';16import { Intent } from '../../../common/constants';17import { IBuildPromptContext } from '../../../prompt/common/intents';18import { IToken, StreamingGrammar } from '../../../prompt/common/streamingGrammar';19import { IDocumentContext } from '../../../prompt/node/documentContext';20import { IIntent, IIntentInvocation, IResponseProcessorContext } from '../../../prompt/node/intents';21import { CopilotIdentityRules } from '../../../prompts/node/base/copilotIdentity';22import { PromptRenderer } from '../../../prompts/node/base/promptRenderer';23import { ResponseTranslationRules } from '../../../prompts/node/base/responseTranslationRules';24import { SafetyRules } from '../../../prompts/node/base/safetyRules';25import { ChatVariablesAndQuery } from '../../../prompts/node/panel/chatVariables';26import { EditorIntegrationRules } from '../../../prompts/node/panel/editorIntegrationRules';27import { WorkspaceStructure } from '../../../prompts/node/panel/workspace/workspaceStructure';2829export class SetupTestsFrameworkQueryInvocationRaw {30constructor(31public readonly endpoint: IChatEndpoint,32private readonly documentContext: IDocumentContext | undefined,33@IInstantiationService private readonly instantiationService: IInstantiationService,34@IRunCommandExecutionService private readonly commandService: IRunCommandExecutionService,35) {36}37public async buildPrompt(38context: IBuildPromptContext,39progress: vscode.Progress<vscode.ChatResponseReferencePart | vscode.ChatResponseProgressPart> | undefined,40token: vscode.CancellationToken,41): Promise<RenderPromptResult> {42const renderer = PromptRenderer.create(this.instantiationService, this.endpoint, SetupTestsPrompt, {43endpoint: this.endpoint,44promptContext: context,45document: this.documentContext?.document,46selection: this.documentContext?.selection,47});4849return renderer.render(progress, token);50}5152public async processResponse(context: IResponseProcessorContext, inputStream: AsyncIterable<IResponsePart>, outputStream: vscode.ChatResponseStream, token: CancellationToken): Promise<void> {53const enum State {54Reasoning,55Frameworks,56}5758const pushTokens = (tokens: Iterable<IToken<State>>) => {59for (const token of tokens) {60if (token.state === State.Reasoning && token.transitionTo === undefined) {61outputStream.markdown(token.token);62}63}64};6566const grammar = new StreamingGrammar(State.Reasoning, {67[State.Reasoning]: { [frameworkPrefix]: State.Frameworks },68});6970for await (const { delta } of inputStream) {71if (token.isCancellationRequested) {72return;73}7475pushTokens(grammar.append(delta.text));76}77pushTokens(grammar.flush());7879const frameworks = grammar.accumulate(undefined, undefined, State.Frameworks)80.split('\n')81.map(line => line.replace(frameworkPrefix, '').trim())82.filter(l => !!l);8384if (frameworks.length) {85outputStream.confirmation(86l10n.t('Pick a testing framework'),87l10n.t('Pick from these options, or use chat to tell me what you\'d prefer:'),88undefined,89frameworks,90);91} else {92outputStream.markdown(l10n.t('Use chat to tell me which framework you\'d prefer.'));93}9495await this.commandService.executeCommand('workbench.action.chat.open', {96query: `/${Intent.SetupTests} `,97isPartialQuery: true,98});99}100}101102/**103* Asks the user what framework they want to use to set up their tests.104*/105export class SetupTestsFrameworkQueryInvocation extends SetupTestsFrameworkQueryInvocationRaw implements IIntentInvocation {106constructor(107public readonly intent: IIntent,108endpoint: IChatEndpoint,109public readonly location: ChatLocation,110documentContext: IDocumentContext | undefined,111@IInstantiationService instantiationService: IInstantiationService,112@IRunCommandExecutionService commandService: IRunCommandExecutionService,113) {114super(endpoint, documentContext, instantiationService, commandService);115}116}117118const frameworkPrefix = 'FRAMEWORK: ';119120interface WorkspacePromptProps extends BasePromptElementProps {121promptContext: IBuildPromptContext;122document?: TextDocumentSnapshot;123selection?: vscode.Selection;124endpoint: IChatEndpoint;125}126127class SetupTestsPrompt extends PromptElement<WorkspacePromptProps> {128override render(state: void, sizing: PromptSizing): PromptPiece<any, any> | undefined {129const { query, chatVariables } = this.props.promptContext;130return <>131<SystemMessage priority={1000}>132You are a software engineer with expert knowledge around software testing frameworks.<br />133<br />134<CopilotIdentityRules />135<SafetyRules />136<EditorIntegrationRules />137<ResponseTranslationRules />138# Additional Rules<br />1391. Examine the workspace structure the user is giving you.<br />1402. Determine the best testing frameworks that should be used for the project.<br />1413. Give a brief explanation why a user would choose one framework over the other, but be concise and never give the user steps to set up the framework.<br />1424. If you're unsure which specific framework is best, you can suggest multiple frameworks.<br />1435. Suggest only frameworks that are used to run tests. Do not suggest things like assertion libraries or build tools.<br />1446. After determining the best framework to use, write out the name of 1 to 3 suggested frameworks prefixed by the phrase "{frameworkPrefix}", for example: "{frameworkPrefix}vitest".<br />145<br />146DO NOT mention that you cannot read files in the workspace.<br />147DO NOT ask the user to provide additional information about files in the workspace.<br />148<br />149# Example<br />150## Question:<br />151I am working in a workspace that has the following structure:<br />{`\`\`\`152src/153index.ts154package.json155tsconfig.json156vite.config.ts157\`\`\``}158<br />159## Response:<br />160Because you have a `vite.config.ts` file, it looks like you're working on a browser or Node.js application. If you're working on a browser application, I recommend using Playwright. Otherwise, Vitest is a good choice for Node.js.<br />161{frameworkPrefix}playwright<br />162{frameworkPrefix}vitest<br />163</SystemMessage>164{this.props.document && <PreferredExtensions document={this.props.document} />}165<UserMessage flexGrow={2}>166<SetupWorkspaceStructure />167</UserMessage>168<ChatVariablesAndQuery flexGrow={2} priority={900} chatVariables={chatVariables} query={query} embeddedInsideUserMessage={false} />169</>;170}171}172173class SetupWorkspaceStructure extends PromptElement {174override render(_state: void, sizing: PromptSizing): PromptPiece {175return <WorkspaceStructure maxSize={(sizing.tokenBudget * 4) / 3} />;176}177}178179class PreferredExtensions extends PromptElement<{ document: TextDocumentSnapshot } & BasePromptElementProps> {180override render(): PromptPiece | undefined {181const extensions = testExtensionsForLanguage.get(this.props.document.languageId);182if (!extensions?.perFramework) {183return;184}185186return <SystemMessage priority={600}>187These are the preferred test frameworks for {this.props.document.languageId}:<br />188<br />189{[...extensions.perFramework.keys()].map(f => `- ${f}`).join('\n')}<br />190</SystemMessage>;191}192}193194195