From deecab3d60893dd6dd546b459212ef44e8bec57d Mon Sep 17 00:00:00 2001 From: Beatrix Date: Wed, 3 Jan 2024 14:33:30 -0800 Subject: [PATCH] remove commands recipe --- lib/shared/src/chat/recipes/custom-prompt.ts | 191 ------------------ lib/shared/src/chat/recipes/recipe.ts | 1 - lib/shared/src/editor/index.ts | 5 - vscode/src/chat/MessageProvider.ts | 30 +-- vscode/src/chat/chat-view/ChatManager.ts | 6 - .../chat/chat-view/SimpleChatPanelProvider.ts | 18 +- vscode/src/commands/CommandsController.ts | 96 ++------- vscode/src/extension.web.ts | 2 - vscode/src/main.ts | 13 +- 9 files changed, 42 insertions(+), 320 deletions(-) delete mode 100644 lib/shared/src/chat/recipes/custom-prompt.ts diff --git a/lib/shared/src/chat/recipes/custom-prompt.ts b/lib/shared/src/chat/recipes/custom-prompt.ts deleted file mode 100644 index c3f897bd01c..00000000000 --- a/lib/shared/src/chat/recipes/custom-prompt.ts +++ /dev/null @@ -1,191 +0,0 @@ -import { CodebaseContext } from '../../codebase-context' -import { ContextFile, ContextMessage } from '../../codebase-context/messages' -import { ActiveTextEditorSelection, Editor } from '../../editor' -import { MAX_HUMAN_INPUT_TOKENS, NUM_CODE_RESULTS, NUM_TEXT_RESULTS } from '../../prompt/constants' -import { truncateText } from '../../prompt/truncation' -import { CodyPromptContext, defaultCodyPromptContext, getCommandEventSource } from '../prompts' -import { createDisplayTextWithFileLinks, createDisplayTextWithFileSelection } from '../prompts/display-text' -import { - extractTestType, - getHumanLLMText, - isOnlySelectionRequired, - newInteraction, - newInteractionWithError, -} from '../prompts/utils' -import { VSCodeEditorContext } from '../prompts/vscode-context/VSCodeEditorContext' -import { Interaction } from '../transcript/interaction' - -import { ChatQuestion } from './chat-question' -import { numResults } from './helpers' -import { Recipe, RecipeContext, RecipeID } from './recipe' - -/** - * ====================================================== - * Recipe for running custom prompts from the cody.json files - * Works with VS Code only -====================================================== * - */ -export class CustomPrompt implements Recipe { - public id: RecipeID = 'custom-prompt' - public title = 'Custom Prompt' - - /** - * Retrieves an Interaction object based on the humanChatInput and RecipeContext provided. - * The Interaction object contains messages from both the human and the assistant, as well as context information. - */ - public async getInteraction(commandRunnerID: string, context: RecipeContext): Promise { - const command = context.editor.controllers?.command?.getCommand(commandRunnerID) - if (!command) { - return createInteractionForError('command') - } - const isChatQuestion = command?.slashCommand === '/ask' - - const contextConfig = command?.context || defaultCodyPromptContext - // If selection is required, ensure not to accept visible content as selection - const selection = contextConfig?.selection - ? await context.editor.getActiveTextEditorSmartSelection() - : context.editor.getActiveTextEditorSelectionOrVisibleContent() - - // Get prompt text from the editor command or from the human input - const commandAdditionalInput = command.additionalInput - const promptText = commandAdditionalInput ? `${command.prompt}\n${commandAdditionalInput}` : command.prompt - const commandName = isChatQuestion ? promptText : command.slashCommand || promptText - - // Log all custom commands under 'custom' - const source = getCommandEventSource(command) - - if (!promptText) { - return createInteractionForError('prompt', promptText) - } - - if (contextConfig?.selection && !selection?.selectedText) { - return createInteractionForError('selection', commandName) - } - - const text = getHumanLLMText(promptText, selection?.fileName) - - const commandOutput = command.context?.output - const contextFiles = command.contextFiles - - // Add selection file name as display when available - const displayText = contextFiles?.length - ? createDisplayTextWithFileLinks(contextFiles, promptText) - : contextConfig.currentFile || contextConfig.selection - ? createDisplayTextWithFileSelection( - commandAdditionalInput ? `${commandName} ${commandAdditionalInput}` : commandName, - selection - ) - : `${commandName} ${commandAdditionalInput}`.trim() - - const truncatedText = truncateText(text, MAX_HUMAN_INPUT_TOKENS) - - const editorContext = new VSCodeEditorContext(context.editor, selection) - - // Attach code selection to prompt text if only selection is needed as context - if (selection && isOnlySelectionRequired(contextConfig)) { - const contextMessages = Promise.resolve(editorContext.getCurrentFileContextFromEditorSelection()) - return newInteraction({ text, displayText, contextMessages, source }) - } - - const contextMessages = this.getContextMessages( - editorContext, - promptText, - context.editor, - context.codebaseContext, - contextConfig, - selection, - context.userInputContextFiles, - commandOutput - ) - - return newInteraction({ text: truncatedText, displayText, contextMessages, source }) - } - - private async getContextMessages( - editorContext: VSCodeEditorContext, - promptText: string, - editor: Editor, - codebaseContext: CodebaseContext, - contextConfig: CodyPromptContext, - selection?: ActiveTextEditorSelection | null, - contextFiles?: ContextFile[], - commandOutput?: string | null - ): Promise { - const contextMessages: ContextMessage[] = [] - const workspaceRootUri = editor.getWorkspaceRootUri() - const isUnitTestRequest = extractTestType(promptText) === 'unit' - - if (contextConfig.none) { - return [] - } - - if (contextConfig.codebase) { - const codebaseMessages = await codebaseContext.getContextMessages(promptText, numResults) - contextMessages.push(...codebaseMessages) - } - - if (contextConfig.openTabs) { - const openTabsMessages = await editorContext.getEditorOpenTabsContext() - contextMessages.push(...openTabsMessages) - } - - if (contextConfig.currentDir) { - const currentDirMessages = await editorContext.getCurrentDirContext(isUnitTestRequest) - contextMessages.push(...currentDirMessages) - } - - if (contextConfig.directoryPath) { - const dirMessages = await editorContext.getEditorDirContext( - contextConfig.directoryPath, - selection?.fileName - ) - contextMessages.push(...dirMessages) - } - - if (contextConfig.filePath) { - const fileMessages = await editorContext.getFilePathContext(contextConfig.filePath) - contextMessages.push(...fileMessages) - } - - // Context for unit tests requests - if (isUnitTestRequest && contextMessages.length === 0) { - if (selection?.fileName) { - const importsContext = await editorContext.getUnitTestContextMessages(selection, workspaceRootUri) - contextMessages.push(...importsContext) - } - } - - if (contextConfig.currentFile || contextConfig.selection !== false) { - const currentFileMessages = editorContext.getCurrentFileContextFromEditorSelection() - contextMessages.push(...currentFileMessages) - } - - if (contextConfig.command && commandOutput) { - const outputMessages = editorContext.getTerminalOutputContext(commandOutput) - contextMessages.push(...outputMessages) - } - - if (contextFiles?.length) { - const contextFileMessages = await ChatQuestion.getContextFilesContext(editor, contextFiles) - contextMessages.push(...contextFileMessages) - } - - // Return sliced results - const maxResults = Math.floor((NUM_CODE_RESULTS + NUM_TEXT_RESULTS) / 2) * 2 - return contextMessages.slice(-maxResults * 2) - } -} - -function createInteractionForError(errorType: 'command' | 'prompt' | 'selection', args?: string): Promise { - switch (errorType) { - case 'command': - return newInteractionWithError('Invalid command -- command not found.') - case 'prompt': - return newInteractionWithError('Please enter a valid prompt for the custom command.', args || '') - case 'selection': - return newInteractionWithError( - `__${args}__ requires highlighted code. Please select some code in your editor and try again.`, - args - ) - } -} diff --git a/lib/shared/src/chat/recipes/recipe.ts b/lib/shared/src/chat/recipes/recipe.ts index 96cb5ec2c4f..a4cf9fbfee2 100644 --- a/lib/shared/src/chat/recipes/recipe.ts +++ b/lib/shared/src/chat/recipes/recipe.ts @@ -28,7 +28,6 @@ export type RecipeID = | 'generate-unit-test' | 'git-history' | 'improve-variable-names' - | 'custom-prompt' | 'next-questions' | 'pr-description' | 'release-notes' diff --git a/lib/shared/src/editor/index.ts b/lib/shared/src/editor/index.ts index eea6d36fdd4..1737cdf51b6 100644 --- a/lib/shared/src/editor/index.ts +++ b/lib/shared/src/editor/index.ts @@ -1,8 +1,5 @@ import { URI } from 'vscode-uri' -import { CodyPrompt } from '../chat/prompts' -import { ContextFile } from '../codebase-context/messages' - export interface ActiveTextEditor { content: string filePath: string @@ -82,8 +79,6 @@ export interface VsCodeFixupController { } export interface VsCodeCommandsController { - addCommand(key: string, input?: string, contextFiles?: ContextFile[], addEnhancedContext?: boolean): Promise - getCommand(commandRunnerId: string): CodyPrompt | null menu(type: 'custom' | 'config' | 'default', showDesc?: boolean): Promise } diff --git a/vscode/src/chat/MessageProvider.ts b/vscode/src/chat/MessageProvider.ts index 39582af9b4a..3ea3b1ab48e 100644 --- a/vscode/src/chat/MessageProvider.ts +++ b/vscode/src/chat/MessageProvider.ts @@ -367,12 +367,7 @@ export abstract class MessageProvider extends MessageHandler implements vscode.D // Filter the human input to check for chat commands and retrieve the correct recipe id // e.g. /edit from 'chat-question' should be redirected to use the 'fixup' recipe - const command = await this.chatCommandsFilter( - humanChatInput, - recipeId, - { source, requestID }, - userInputContextFiles - ) + const command = await this.chatCommandsFilter(humanChatInput, recipeId, { source, requestID }) if (!command) { return } @@ -576,14 +571,13 @@ export abstract class MessageProvider extends MessageHandler implements vscode.D break } // Get prompt details from controller by title then execute prompt's command - return this.executeRecipe('custom-prompt', title, 'custom-commands') + return vscode.commands.executeCommand('cody.action.commands.exec', title) } protected async chatCommandsFilter( text: string, recipeId: RecipeID, - eventTrace?: { requestID?: string; source?: ChatEventSource }, - userContextFiles?: ContextFile[] + eventTrace?: { requestID?: string; source?: ChatEventSource } ): Promise<{ text: string; recipeId: RecipeID; source?: ChatEventSource } | void> { const source = eventTrace?.source || undefined text = text.trim() @@ -634,23 +628,7 @@ export abstract class MessageProvider extends MessageHandler implements vscode.D const assistantResponse = 'Command failed. Please open a file and try again.' return this.addCustomInteraction({ assistantResponse, text, source }) } - const commandRunnerID = await this.editor.controllers.command?.addCommand( - text, - eventTrace?.requestID, - userContextFiles - ) - // no op - if (!commandRunnerID) { - return - } - - if (commandRunnerID === 'invalid') { - const assistantResponse = `__${text}__ is not a valid command` - // If no command found, send error message to view - return this.addCustomInteraction({ assistantResponse, text, source }) - } - - return { text: commandRunnerID, recipeId: 'custom-prompt', source } + return vscode.commands.executeCommand('cody.action.commands.exec', text) } } } diff --git a/vscode/src/chat/chat-view/ChatManager.ts b/vscode/src/chat/chat-view/ChatManager.ts index 230bb258d5f..311d29110f4 100644 --- a/vscode/src/chat/chat-view/ChatManager.ts +++ b/vscode/src/chat/chat-view/ChatManager.ts @@ -92,12 +92,6 @@ export class ChatManager implements vscode.Disposable { return } - // If it's a fixup command, run the recipe via sidebar view without creating a new panel - if (command.mode === 'edit' || command.mode === 'insert') { - await this.sidebarViewController.executeRecipe('custom-prompt', command.prompt, source) - return - } - // Else, open a new chanel panel and run the command in the new panel const chatProvider = await this.getChatProvider() await chatProvider.executeCommand(command, source) diff --git a/vscode/src/chat/chat-view/SimpleChatPanelProvider.ts b/vscode/src/chat/chat-view/SimpleChatPanelProvider.ts index da5c3c51872..753f2c76b16 100644 --- a/vscode/src/chat/chat-view/SimpleChatPanelProvider.ts +++ b/vscode/src/chat/chat-view/SimpleChatPanelProvider.ts @@ -581,16 +581,22 @@ export class SimpleChatPanelProvider implements vscode.Disposable { } public async executeCommand(command: CodyPrompt, source: ChatEventSource, requestID = uuid.v4()): Promise { - const promptText = command.prompt + command.additionalInput + const promptText = [command.prompt, command.additionalInput].join(' ') // Check for edit commands if (command.mode !== 'ask') { return executeEdit({ instruction: promptText }, source) } - const text = [command.slashCommand, command.additionalInput].join(' ') + const inputText = [command.slashCommand, command.additionalInput].join(' ') const currentFile = this.editor.getActiveTextEditorSelectionOrVisibleContent() - const displayText = createDisplayTextWithFileSelection(text, currentFile) + if (!currentFile) { + if (command.context?.selection || command.context?.currentFile || command.context?.currentDir) { + this.postError(new Error('Command failed. Please open a file and try again.'), 'transcript') + return + } + } + const displayText = createDisplayTextWithFileSelection(inputText, currentFile) this.chatModel.addHumanMessage({ text: promptText }, displayText) - await this.saveSession(text) + await this.saveSession(inputText) // trigger the context progress indicator this.postViewTranscript({ speaker: 'assistant' }) await this.generateAssistantResponse( @@ -604,7 +610,7 @@ export class SimpleChatPanelProvider implements vscode.Disposable { requestID, chatModel: this.chatModel.modelID, // 🚨 SECURITY: included only for DotCom users. - promptText: authStatus.endpoint && isDotCom(authStatus.endpoint) ? text : undefined, + promptText: authStatus.endpoint && isDotCom(authStatus.endpoint) ? promptText : undefined, contextSummary, } telemetryService.log('CodyVSCodeExtension:recipe:chat-question:executed', properties, { @@ -620,7 +626,7 @@ export class SimpleChatPanelProvider implements vscode.Disposable { if (this.webviewPanel) { this.webviewPanel.title = this.history.getChat(this.authProvider.getAuthStatus(), this.sessionID)?.chatTitle || - getChatPanelTitle(text) + getChatPanelTitle(inputText) } } diff --git a/vscode/src/commands/CommandsController.ts b/vscode/src/commands/CommandsController.ts index 50fc24a86a1..9f75b6f679f 100644 --- a/vscode/src/commands/CommandsController.ts +++ b/vscode/src/commands/CommandsController.ts @@ -51,25 +51,6 @@ export class CommandsController implements VsCodeCommandsController, vscode.Disp this.fileWatcherInit() } - /** - * TODO bee can be removed once we have moved away from recipe - * - * Gets a CodyPrompt object for the given command runner ID. - * @param commandRunnerId - The ID of the command runner to get the prompt for. - * @returns The CodyPrompt object for the command runner, or null if not found. - * - * Looks up the command runner instance in the commandRunners map by the given ID. - * If found, returns the CodyPrompt associated with that runner. Otherwise returns null. - */ - public getCommand(commandRunnerId: string): CodyPrompt | null { - const commandRunner = this.commandRunners.get(commandRunnerId) - if (!commandRunner) { - return null - } - this.commandRunners.delete(commandRunnerId) - return commandRunner?.codyCommand - } - public isCommand(text: string): boolean { const commandSplit = text.split(' ') // The unique key for the command. e.g. /test @@ -93,92 +74,45 @@ export class CommandsController implements VsCodeCommandsController, vscode.Disp if (!command) { return null } - - command.prompt = command.prompt.replace('/ask', '') - command.additionalInput = commandInput - command.requestID = requestID - command.contextFiles = contextFiles - const runnerID = await this.createCodyCommandRunner(command, commandInput) - this.runCommand(runnerID) - return command - } - - private runCommand(commandRunnerId: string): CodyPrompt | null { - const commandRunner = this.commandRunners.get(commandRunnerId) - if (!commandRunner) { - return null - } - this.commandRunners.delete(commandRunnerId) - return commandRunner?.codyCommand - } - - /** - * TODO bee can be removed once we have moved away from recipe - * Adds a new command to the commands map. - * - * Looks up the command prompt using the given key in the default prompts map. - * If found, creates a new Cody command runner instance for that prompt and input. - * Returns the ID of the created runner, or 'invalid' if not found. - */ - public async addCommand(text: string, requestID?: string, contextFiles?: ContextFile[]): Promise { - const commandSplit = text.split(' ') - // The unique key for the command. e.g. /test - const commandKey = commandSplit.shift() || text - // Additional instruction that will be added to end of prompt in the custom-prompt recipe - const commandInput = commandKey === text ? '' : commandSplit.join(' ') - - const command = this.default.get(commandKey) - if (!command) { - return 'invalid' - } - if (command.slashCommand === '/ask') { - command.prompt = text + command.prompt = command.prompt.replace('/ask', '') } - command.additionalInput = commandInput + command.mode = command.prompt.startsWith('/edit') ? 'edit' : command.mode || 'ask' command.requestID = requestID command.contextFiles = contextFiles - return this.createCodyCommandRunner(command, commandInput) + await this.createCodyCommandRunner(command, commandInput) + return command } - /** - * Creates a new Cody command runner instance and returns the ID. - * - * This creates a new CommandRunner instance with the given CodyPrompt, input text, - * and fixup request flag. It adds the runner to the commandRunners map, sets it - * as the current prompt in progress, and logs the command usage. - * - * If the prompt has a shell command in its context, it will execute that command. - * - * Finally, it returns the unique ID for the created CommandRunner instance. - */ - private async createCodyCommandRunner(command: CodyPrompt, input = ''): Promise { + private async createCodyCommandRunner(command: CodyPrompt, input = ''): Promise { const commandKey = command.slashCommand - const defaultEditCommands = new Set(['/edit', '/fix', '/doc']) - const isFixupRequest = defaultEditCommands.has(commandKey) || command.prompt.startsWith('/edit') + const defaultEditCommands = new Set(['/edit', '/doc']) + const isFixupRequest = defaultEditCommands.has(commandKey) || command.mode !== 'ask' logDebug('CommandsController:createCodyCommandRunner:creating', commandKey) // Start the command runner - const codyCommand = new CommandRunner(command, input, isFixupRequest) - this.commandRunners.set(codyCommand.id, codyCommand) + const runner = new CommandRunner(command, input, isFixupRequest) + this.commandRunners.set(runner.id, runner) // Save command to command history this.lastUsedCommands.add(command.slashCommand) // Fixup request will be taken care by the fixup recipe in the CommandRunner - if (isFixupRequest || command.mode !== 'ask') { - return '' + if (isFixupRequest) { + return undefined } // Run shell command if any const shellCommand = command.context?.command if (shellCommand) { - await codyCommand.runShell(this.tools.exeCommand(shellCommand)) + await runner.runShell(this.tools.exeCommand(shellCommand)) } - return codyCommand.id + this.commandRunners.delete(runner.id) + + return runner } /** diff --git a/vscode/src/extension.web.ts b/vscode/src/extension.web.ts index 351b88a8dcf..e9057ef0ad3 100644 --- a/vscode/src/extension.web.ts +++ b/vscode/src/extension.web.ts @@ -2,7 +2,6 @@ import * as vscode from 'vscode' import { ChatQuestion } from '@sourcegraph/cody-shared/src/chat/recipes/chat-question' import { ContextSearch } from '@sourcegraph/cody-shared/src/chat/recipes/context-search' -import { CustomPrompt } from '@sourcegraph/cody-shared/src/chat/recipes/custom-prompt' import { ExplainCodeDetailed } from '@sourcegraph/cody-shared/src/chat/recipes/explain-code-detailed' import { ExplainCodeHighLevel } from '@sourcegraph/cody-shared/src/chat/recipes/explain-code-high-level' import { FindCodeSmells } from '@sourcegraph/cody-shared/src/chat/recipes/find-code-smells' @@ -32,7 +31,6 @@ export const VSCODE_WEB_RECIPES: Recipe[] = [ new GenerateDocstring(), new GenerateTest(), new ImproveVariableNames(), - new CustomPrompt(), new NextQuestions(), new TranslateToLanguage(), new ContextSearch(), diff --git a/vscode/src/main.ts b/vscode/src/main.ts index 9c20f57cb61..3c27d9a7774 100644 --- a/vscode/src/main.ts +++ b/vscode/src/main.ts @@ -280,11 +280,20 @@ const register = async ( // Sync initial auth status await chatManager.syncAuthStatus(authProvider.getAuthStatus()) + // Execute Cody Commands and Cody Custom Commands const executeCommand = async (commandKey: string, source: ChatEventSource = 'editor'): Promise => { const command = await commandsController?.findCommand(commandKey) - if (command) { - await chatManager.executeCommand(command, source) + if (!command) { + return + } + // If it's not a ask command, it's a fixup command. If it's a fixup request, we can exit early + // This is because findCommand will start the CommandRunner, + // which would send all fixup requests to the FixupController + if (command.mode !== 'ask') { + return } + + return chatManager.executeCommand(command, source) } const executeFixup = async (