feat: add /enhance command to improve user prompts

This adds the /enhance command which uses an LLM to refine user prompts
based on conversation history.

Closes #25133
This commit is contained in:
Akhilesh Kumar
2026-04-10 17:47:27 +00:00
parent 447a854ad9
commit 04f05459f8
8 changed files with 298 additions and 3 deletions
+8 -3
View File
@@ -1,11 +1,16 @@
{
"experimental": {
"plan": true,
"extensionReloading": true,
"modelSteering": true,
"memoryManager": true
},
"general": {
"devtools": true
"devtools": true,
"plan": {
"enabled": true
}
},
"agents": {
"overrides": {}
}
}
}
@@ -31,6 +31,7 @@ import { corgiCommand } from '../ui/commands/corgiCommand.js';
import { docsCommand } from '../ui/commands/docsCommand.js';
import { directoryCommand } from '../ui/commands/directoryCommand.js';
import { editorCommand } from '../ui/commands/editorCommand.js';
import { enhanceCommand } from '../ui/commands/enhanceCommand.js';
import { extensionsCommand } from '../ui/commands/extensionsCommand.js';
import { footerCommand } from '../ui/commands/footerCommand.js';
import { helpCommand } from '../ui/commands/helpCommand.js';
@@ -133,6 +134,7 @@ export class BuiltinCommandLoader implements ICommandLoader {
docsCommand,
directoryCommand,
editorCommand,
enhanceCommand,
...(this.config?.getExtensionsEnabled() === false
? [
{
@@ -58,6 +58,7 @@ export const createMockCommandContext = (
pendingItem: null,
setPendingItem: vi.fn(),
loadHistory: vi.fn(),
setInput: vi.fn(),
toggleCorgiMode: vi.fn(),
toggleShortcutsHelp: vi.fn(),
toggleVimEnabled: vi.fn(),
@@ -0,0 +1,175 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect, beforeEach, vi, type Mock } from 'vitest';
import { enhanceCommand } from './enhanceCommand.js';
import { type CommandContext } from './types.js';
import { createMockCommandContext } from '../../test-utils/mockCommandContext.js';
import { MessageType } from '../types.js';
import { LlmRole } from '@google/gemini-cli-core';
describe('enhanceCommand', () => {
let mockContext: CommandContext;
let mockGenerateContent: Mock;
beforeEach(() => {
mockGenerateContent = vi.fn();
mockContext = createMockCommandContext({
services: {
agentContext: {
promptId: 'test-prompt-id',
geminiClient: {
getHistory: vi.fn().mockReturnValue([
{ role: 'user', parts: [{ text: 'previous user msg' }] },
{ role: 'model', parts: [{ text: 'previous model msg' }] },
]),
},
config: {
getModel: vi.fn().mockReturnValue('test-model'),
getContentGenerator: vi.fn().mockReturnValue({
generateContent: mockGenerateContent,
}),
getGemini31LaunchedSync: vi.fn().mockReturnValue(true),
getHasAccessToPreviewModel: vi.fn().mockReturnValue(true),
},
},
},
ui: {
addItem: vi.fn(),
setDebugMessage: vi.fn(),
},
} as unknown as CommandContext);
});
it('should have the correct name and description', () => {
expect(enhanceCommand.name).toBe('enhance');
expect(enhanceCommand.description).toBe(
'Enhance a prompt with additional context and rephrasing',
);
});
it('should show error if no prompt is provided', async () => {
if (!enhanceCommand.action) throw new Error('Action must be defined');
await enhanceCommand.action(mockContext, '');
expect(mockContext.ui.addItem).toHaveBeenCalledWith(
expect.objectContaining({
type: MessageType.ERROR,
text: expect.stringContaining('Please provide a prompt'),
}),
);
});
it('should call generateContent with correct parameters and show enhanced prompt', async () => {
if (!enhanceCommand.action) throw new Error('Action must be defined');
mockGenerateContent.mockResolvedValue({
candidates: [
{
content: {
parts: [{ text: 'Enhanced: do something' }],
},
},
],
});
await enhanceCommand.action(mockContext, 'do something');
expect(mockGenerateContent).toHaveBeenCalledWith(
expect.objectContaining({
model: 'test-model',
contents: [
{ role: 'user', parts: [{ text: 'previous user msg' }] },
{ role: 'model', parts: [{ text: 'previous model msg' }] },
{ role: 'user', parts: [{ text: 'do something' }] },
],
config: {
systemInstruction: {
role: 'system',
parts: [
{
text: expect.stringContaining(
"Generate an enhanced version of the user's prompt",
),
},
],
},
},
}),
'test-prompt-id',
LlmRole.UTILITY_TOOL,
);
expect(mockContext.ui.addItem).toHaveBeenCalledWith(
expect.objectContaining({
type: MessageType.INFO,
text: expect.stringContaining(
'Enhanced prompt:\n\nEnhanced: do something',
),
}),
);
expect(mockContext.ui.setInput).toHaveBeenCalledWith(
'Enhanced: do something',
);
});
it('should clean the response from markdown and quotes', async () => {
if (!enhanceCommand.action) throw new Error('Action must be defined');
mockGenerateContent.mockResolvedValue({
candidates: [
{
content: {
parts: [{ text: '```markdown\n"Clean me"\n```' }],
},
},
],
});
await enhanceCommand.action(mockContext, 'dirty prompt');
expect(mockContext.ui.addItem).toHaveBeenCalledWith(
expect.objectContaining({
type: MessageType.INFO,
text: expect.stringContaining('Enhanced prompt:\n\nClean me'),
}),
);
expect(mockContext.ui.setInput).toHaveBeenCalledWith('Clean me');
});
it('should handle API errors gracefully', async () => {
if (!enhanceCommand.action) throw new Error('Action must be defined');
mockGenerateContent.mockRejectedValue(new Error('API Error'));
await enhanceCommand.action(mockContext, 'test prompt');
expect(mockContext.ui.addItem).toHaveBeenCalledWith(
expect.objectContaining({
type: MessageType.ERROR,
text: expect.stringContaining('Failed to enhance prompt: API Error'),
}),
);
});
it('should handle empty response from model', async () => {
if (!enhanceCommand.action) throw new Error('Action must be defined');
mockGenerateContent.mockResolvedValue({
candidates: [],
});
await enhanceCommand.action(mockContext, 'test prompt');
expect(mockContext.ui.addItem).toHaveBeenCalledWith(
expect.objectContaining({
type: MessageType.ERROR,
text: expect.stringContaining('Empty response from model'),
}),
);
});
});
@@ -0,0 +1,104 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { CommandKind, type SlashCommand } from './types.js';
import { MessageType } from '../types.js';
import { LlmRole, resolveModel } from '@google/gemini-cli-core';
import type { Content } from '@google/genai';
const INSTRUCTION =
"Generate an enhanced version of the user's prompt, using the preceding conversation as context. Reply with ONLY the enhanced prompt - no conversation, explanations, lead-in, bullet points, placeholders, or surrounding quotes.";
function clean(text: string) {
const stripped = text.replace(/^```\w*\n?|```$/g, '').trim();
return stripped.replace(/^(['"])([\s\S]*)\1$/, '$2').trim();
}
export const enhanceCommand: SlashCommand = {
name: 'enhance',
description: 'Enhance a prompt with additional context and rephrasing',
kind: CommandKind.BUILT_IN,
autoExecute: true,
action: async (context, args) => {
const draft = args.trim();
if (!draft) {
context.ui.addItem({
type: MessageType.ERROR,
text: 'Please provide a prompt to enhance. Usage: /enhance <prompt>',
});
return;
}
const agentContext = context.services.agentContext;
if (!agentContext) {
context.ui.addItem({
type: MessageType.ERROR,
text: 'Agent context not available.',
});
return;
}
const config = agentContext.config;
const contentGenerator = config.getContentGenerator();
const promptId = agentContext.promptId;
const model = resolveModel(
config.getModel(),
config.getGemini31LaunchedSync?.() ?? false,
false,
config.getHasAccessToPreviewModel?.() ?? true,
config,
);
context.ui.setDebugMessage('Enhancing prompt...');
try {
const history = agentContext.geminiClient?.getHistory() ?? [];
const contents: Content[] = [
...history,
{ role: 'user', parts: [{ text: draft }] },
];
const response = await contentGenerator.generateContent(
{
model,
contents,
config: {
systemInstruction: {
role: 'system',
parts: [{ text: INSTRUCTION }],
},
},
},
promptId,
LlmRole.UTILITY_TOOL,
);
const enhancedText = response.candidates?.[0]?.content?.parts?.[0]?.text;
if (enhancedText) {
const cleanedText = clean(enhancedText);
context.ui.addItem({
type: MessageType.INFO,
text: `Enhanced prompt:\n\n${cleanedText}`,
});
context.ui.setInput(cleanedText);
} else {
context.ui.addItem({
type: MessageType.ERROR,
text: 'Failed to enhance prompt: Empty response from model.',
});
}
} catch (error) {
context.ui.addItem({
type: MessageType.ERROR,
text: `Failed to enhance prompt: ${error instanceof Error ? error.message : String(error)}`,
});
} finally {
context.ui.setDebugMessage('');
}
},
};
+6
View File
@@ -70,6 +70,12 @@ export interface CommandContext {
* @param postLoadInput Optional text to set in the input buffer after loading history.
*/
loadHistory: (history: HistoryItem[], postLoadInput?: string) => void;
/**
* Sets the text in the input buffer.
*
* @param text The text to set.
*/
setInput: (text: string) => void;
/** Toggles a special display mode. */
toggleCorgiMode: () => void;
toggleDebugProfiler: () => void;
@@ -228,6 +228,7 @@ export const useSlashCommandProcessor = (
actions.setText(postLoadInput);
}
},
setInput: (text) => actions.setText(text),
setDebugMessage: actions.setDebugMessage,
pendingItem,
setPendingItem,
@@ -29,6 +29,7 @@ export function createNonInteractiveUI(): CommandContext['ui'] {
clear: () => {},
setDebugMessage: (_message) => {},
loadHistory: (_newHistory) => {},
setInput: (_text) => {},
pendingItem: null,
setPendingItem: (_item) => {},
toggleCorgiMode: () => {},