Skip to content

Commit 7aafb9c

Browse files
anandgupta42claude
andcommitted
feat: add prompt enhancement feature
Add AI-powered prompt enhancement that rewrites rough user prompts into clearer, more specific versions before sending to the main model. - Add `enhancePrompt()` utility using a small/cheap model to polish prompts - Register `prompt.enhance` TUI command with `<leader>i` keybind - Show "enhance" hint in the bottom bar alongside agents/commands - Add `prompt_enhance` keybind to config schema - Add unit tests for the `clean()` text sanitization function Inspired by KiloCode's prompt enhancement feature. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent f87a3cd commit 7aafb9c

File tree

4 files changed

+191
-0
lines changed

4 files changed

+191
-0
lines changed
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// altimate_change - new file
2+
import { Provider } from "@/provider/provider"
3+
import { LLM } from "@/session/llm"
4+
import { Agent } from "@/agent/agent"
5+
import { Log } from "@/util/log"
6+
import { MessageV2 } from "@/session/message-v2"
7+
8+
const log = Log.create({ service: "enhance-prompt" })
9+
10+
const ENHANCE_SYSTEM_PROMPT = `You are a prompt enhancement specialist for a data engineering coding agent.
11+
12+
Your job is to take a user's rough prompt and rewrite it into a clearer, more specific version that will produce better results from the coding agent.
13+
14+
Rules:
15+
- Reply with ONLY the enhanced prompt text — no conversation, explanations, lead-in, bullet points, placeholders, or surrounding quotes
16+
- Preserve the user's intent exactly — do not add requirements they didn't ask for
17+
- Make implicit requirements explicit (e.g. if they say "fix the bug", specify what kind of verification to do)
18+
- Add structure when the prompt is vague (e.g. "look at X first, then modify Y")
19+
- Keep the enhanced prompt concise — longer is not better
20+
- If the original prompt is already clear and specific, return it unchanged
21+
- Do not wrap your response in markdown code fences or quotes`
22+
23+
export function clean(text: string) {
24+
return text
25+
.replace(/^```\w*\n?|```$/g, "")
26+
.trim()
27+
.replace(/^(['"])([\s\S]*)\1$/, "$2")
28+
.trim()
29+
}
30+
31+
export async function enhancePrompt(text: string): Promise<string> {
32+
if (!text.trim()) return text
33+
34+
log.info("enhancing", { length: text.length })
35+
36+
const defaultModel = await Provider.defaultModel()
37+
const model =
38+
(await Provider.getSmallModel(defaultModel.providerID)) ??
39+
(await Provider.getModel(defaultModel.providerID, defaultModel.modelID))
40+
41+
const agent: Agent.Info = {
42+
name: "enhance-prompt",
43+
mode: "primary",
44+
hidden: true,
45+
options: {},
46+
permission: [],
47+
prompt: ENHANCE_SYSTEM_PROMPT,
48+
temperature: 0.7,
49+
}
50+
51+
const user: MessageV2.User = {
52+
id: "enhance-prompt" as any,
53+
sessionID: "enhance-prompt" as any,
54+
role: "user",
55+
time: { created: Date.now() },
56+
agent: "enhance-prompt",
57+
model: {
58+
providerID: model.providerID,
59+
modelID: model.id,
60+
},
61+
}
62+
63+
const stream = await LLM.stream({
64+
agent,
65+
user,
66+
system: [],
67+
small: true,
68+
tools: {},
69+
model,
70+
abort: new AbortController().signal,
71+
sessionID: "enhance-prompt" as any,
72+
retries: 2,
73+
messages: [
74+
{
75+
role: "user",
76+
content: text,
77+
},
78+
],
79+
})
80+
81+
const result = await stream.text.catch((err) => {
82+
log.error("failed to enhance prompt", { error: err })
83+
return undefined
84+
})
85+
86+
if (!result) return text
87+
88+
const cleaned = clean(
89+
result
90+
.replace(/<think>[\s\S]*?<\/think>\s*/g, "")
91+
.trim(),
92+
)
93+
94+
return cleaned || text
95+
}

packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ import { useToast } from "../../ui/toast"
3434
import { useKV } from "../../context/kv"
3535
import { useTextareaKeybindings } from "../textarea-keybindings"
3636
import { DialogSkill } from "../dialog-skill"
37+
// altimate_change start - import prompt enhancement
38+
import { enhancePrompt } from "@/altimate/enhance-prompt"
39+
// altimate_change end
3740

3841
export type PromptProps = {
3942
sessionID?: string
@@ -194,6 +197,50 @@ export function Prompt(props: PromptProps) {
194197
dialog.clear()
195198
},
196199
},
200+
// altimate_change start - add prompt enhance command
201+
{
202+
title: "Enhance prompt",
203+
value: "prompt.enhance",
204+
keybind: "prompt_enhance",
205+
category: "Prompt",
206+
enabled: !!store.prompt.input,
207+
onSelect: async (dialog) => {
208+
if (!store.prompt.input.trim()) return
209+
dialog.clear()
210+
const original = store.prompt.input
211+
toast.show({
212+
message: "Enhancing prompt...",
213+
variant: "info",
214+
duration: 2000,
215+
})
216+
try {
217+
const enhanced = await enhancePrompt(original)
218+
if (enhanced !== original) {
219+
input.setText(enhanced)
220+
setStore("prompt", "input", enhanced)
221+
input.gotoBufferEnd()
222+
toast.show({
223+
message: "Prompt enhanced",
224+
variant: "success",
225+
duration: 2000,
226+
})
227+
} else {
228+
toast.show({
229+
message: "Prompt already looks good",
230+
variant: "info",
231+
duration: 2000,
232+
})
233+
}
234+
} catch {
235+
toast.show({
236+
message: "Failed to enhance prompt",
237+
variant: "error",
238+
duration: 3000,
239+
})
240+
}
241+
},
242+
},
243+
// altimate_change end
197244
{
198245
title: "Paste",
199246
value: "prompt.paste",
@@ -1155,6 +1202,11 @@ export function Prompt(props: PromptProps) {
11551202
<text fg={theme.text}>
11561203
{keybind.print("command_list")} <span style={{ fg: theme.textMuted }}>commands</span>
11571204
</text>
1205+
{/* altimate_change start - show enhance hint */}
1206+
<text fg={theme.text}>
1207+
{keybind.print("prompt_enhance")} <span style={{ fg: theme.textMuted }}>enhance</span>
1208+
</text>
1209+
{/* altimate_change end */}
11581210
</Match>
11591211
<Match when={store.mode === "shell"}>
11601212
<text fg={theme.text}>

packages/opencode/src/config/config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -866,6 +866,9 @@ export namespace Config {
866866
agent_cycle: z.string().optional().default("tab").describe("Next agent"),
867867
agent_cycle_reverse: z.string().optional().default("shift+tab").describe("Previous agent"),
868868
variant_cycle: z.string().optional().default("ctrl+t").describe("Cycle model variants"),
869+
// altimate_change start - add prompt enhance keybind
870+
prompt_enhance: z.string().optional().default("<leader>i").describe("Enhance prompt with AI before sending"),
871+
// altimate_change end
869872
input_clear: z.string().optional().default("ctrl+c").describe("Clear input field"),
870873
input_paste: z.string().optional().default("ctrl+v").describe("Paste from clipboard"),
871874
input_submit: z.string().optional().default("return").describe("Submit input"),
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { describe, expect, test } from "bun:test"
2+
import { clean } from "../../src/altimate/enhance-prompt"
3+
4+
describe("enhance-prompt clean()", () => {
5+
test("strips markdown code fences", () => {
6+
expect(clean("```\nfixed prompt\n```")).toBe("fixed prompt")
7+
})
8+
9+
test("strips code fences with language tag", () => {
10+
expect(clean("```text\nenhanced prompt\n```")).toBe("enhanced prompt")
11+
})
12+
13+
test("strips surrounding single quotes", () => {
14+
expect(clean("'enhanced prompt'")).toBe("enhanced prompt")
15+
})
16+
17+
test("strips surrounding double quotes", () => {
18+
expect(clean('"enhanced prompt"')).toBe("enhanced prompt")
19+
})
20+
21+
test("trims whitespace", () => {
22+
expect(clean(" enhanced prompt ")).toBe("enhanced prompt")
23+
})
24+
25+
test("handles combined wrapping", () => {
26+
expect(clean('```\n"enhanced prompt"\n```')).toBe("enhanced prompt")
27+
})
28+
29+
test("returns plain text unchanged", () => {
30+
expect(clean("fix the auth bug")).toBe("fix the auth bug")
31+
})
32+
33+
test("handles empty string", () => {
34+
expect(clean("")).toBe("")
35+
})
36+
37+
test("handles multiline content", () => {
38+
const input = "```\nFirst do X.\nThen do Y.\n```"
39+
expect(clean(input)).toBe("First do X.\nThen do Y.")
40+
})
41+
})

0 commit comments

Comments
 (0)