Skip to content

Commit 71f5519

Browse files
committed
feat: prompt enhancement button (Bolt-style)
1 parent 6c14ea1 commit 71f5519

3 files changed

Lines changed: 140 additions & 0 deletions

File tree

packages/opencode/src/agent/agent.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import PROMPT_COMPACTION from "./prompt/compaction.txt"
1313
import PROMPT_EXPLORE from "./prompt/explore.txt"
1414
import PROMPT_SUMMARY from "./prompt/summary.txt"
1515
import PROMPT_TITLE from "./prompt/title.txt"
16+
import PROMPT_ENHANCE from "./prompt/enhance.txt"
1617
import { Permission } from "@/permission"
1718
import { mergeDeep, pipe, sortBy, values } from "remeda"
1819
import { Global } from "@/global"
@@ -230,6 +231,22 @@ export namespace Agent {
230231
),
231232
prompt: PROMPT_SUMMARY,
232233
},
234+
enhance: {
235+
name: "enhance",
236+
mode: "primary",
237+
options: {},
238+
native: true,
239+
hidden: true,
240+
temperature: 0.7,
241+
permission: Permission.merge(
242+
defaults,
243+
Permission.fromConfig({
244+
"*": "deny",
245+
}),
246+
user,
247+
),
248+
prompt: PROMPT_ENHANCE,
249+
},
233250
}
234251

235252
for (const [key, value] of Object.entries(cfg.agent ?? {})) {
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
You are a prompt enhancer. You output ONLY the improved prompt. Nothing else.
2+
3+
<task>
4+
Rewrite the user's prompt to be clearer, more specific, and more effective for an AI coding assistant.
5+
6+
Follow all rules in <rules>.
7+
Your output must be:
8+
- The enhanced prompt text only
9+
- No explanations, preamble, or meta-commentary
10+
- No surrounding quotes or markdown fencing
11+
- No bullet points or numbered lists unless the original uses them
12+
</task>
13+
14+
<rules>
15+
- Preserve the user's original intent exactly — do not add features or change scope
16+
- Add specificity: replace vague words with concrete technical terms where obvious
17+
- Add structure: break ambiguous requests into clear sub-steps if needed
18+
- Add context clues: if the user references files, frameworks, or patterns, make those references explicit
19+
- Keep the same language and tone as the original
20+
- If the prompt is already clear and specific, make only minimal improvements
21+
- Do NOT pad the prompt with generic instructions like "be thorough" or "handle edge cases"
22+
- Do NOT add requirements the user didn't mention
23+
- Do NOT rewrite short, direct prompts into verbose ones — brevity is valuable
24+
- A one-line prompt that's already clear should stay roughly one line
25+
- Never output anything except the enhanced prompt itself
26+
- Never refuse or comment on the input — always output an enhanced version
27+
</rules>
28+
29+
<examples>
30+
"fix the bug" → Fix the bug in the current file — identify the root cause, apply the minimal correction, and verify the fix doesn't break existing behavior.
31+
32+
"add dark mode" → Add a dark mode toggle to the settings page that persists the user's preference and applies the theme globally.
33+
34+
"refactor this function" → Refactor this function to improve readability and reduce complexity while preserving the same behavior and return values.
35+
36+
"make it faster" → Optimize the performance of this code — profile for bottlenecks, reduce unnecessary allocations, and avoid redundant computations.
37+
38+
"write tests" → Write unit tests for the changed code covering the main success path, edge cases, and error handling.
39+
40+
"why is this broken" → Investigate why this code is failing — trace the execution path, identify where the actual behavior diverges from expected, and explain the root cause.
41+
</examples>

packages/opencode/src/server/routes/experimental.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Hono } from "hono"
22
import { describeRoute, validator, resolver } from "hono-openapi"
33
import z from "zod"
44
import { ProviderID, ModelID } from "../../provider/schema"
5+
import { SessionID, MessageID } from "../../session/schema"
56
import { ToolRegistry } from "../../tool/registry"
67
import { Worktree } from "../../worktree"
78
import { Instance } from "../../project/instance"
@@ -12,6 +13,9 @@ import { zodToJsonSchema } from "zod-to-json-schema"
1213
import { errors } from "../error"
1314
import { lazy } from "../../util/lazy"
1415
import { WorkspaceRoutes } from "./workspace"
16+
import { Agent } from "../../agent/agent"
17+
import { Provider } from "../../provider/provider"
18+
import { LLM } from "../../session/llm"
1519

1620
export const ExperimentalRoutes = lazy(() =>
1721
new Hono()
@@ -267,5 +271,83 @@ export const ExperimentalRoutes = lazy(() =>
267271
async (c) => {
268272
return c.json(await MCP.resources())
269273
},
274+
)
275+
.post(
276+
"/enhance",
277+
describeRoute({
278+
summary: "Enhance prompt",
279+
description:
280+
"Rewrite a user prompt to be clearer, more specific, and more effective for an AI coding assistant.",
281+
operationId: "experimental.enhance",
282+
responses: {
283+
200: {
284+
description: "Enhanced prompt text",
285+
content: {
286+
"application/json": {
287+
schema: resolver(
288+
z.object({ text: z.string() }).meta({ ref: "EnhanceResult" }),
289+
),
290+
},
291+
},
292+
},
293+
...errors(400),
294+
},
295+
}),
296+
validator(
297+
"json",
298+
z.object({
299+
text: z.string().min(1),
300+
providerID: z.string().optional(),
301+
modelID: z.string().optional(),
302+
}),
303+
),
304+
async (c) => {
305+
const body = c.req.valid("json")
306+
const agent = await Agent.get("enhance")
307+
if (!agent) return c.json({ text: body.text })
308+
309+
const defaults = await Provider.defaultModel()
310+
const providerID = (body.providerID ?? defaults.providerID) as ProviderID
311+
const model = await (async () => {
312+
if (agent.model)
313+
return Provider.getModel(agent.model.providerID, agent.model.modelID)
314+
const small = await Provider.getSmallModel(providerID)
315+
if (small) return small
316+
return Provider.getModel(providerID, (body.modelID ?? defaults.modelID) as ModelID)
317+
})()
318+
if (!model) return c.json({ text: body.text })
319+
320+
const result = await LLM.stream({
321+
agent,
322+
user: {
323+
role: "user",
324+
id: "" as MessageID,
325+
sessionID: "" as SessionID,
326+
time: { created: Date.now() },
327+
agent: "enhance",
328+
model: { providerID: model.providerID, modelID: model.id },
329+
variant: "default",
330+
},
331+
system: [],
332+
small: true,
333+
tools: {},
334+
model,
335+
abort: new AbortController().signal,
336+
sessionID: "" as SessionID,
337+
retries: 2,
338+
messages: [
339+
{
340+
role: "user",
341+
content: body.text,
342+
},
343+
],
344+
})
345+
const text = await result.text.catch(() => undefined)
346+
if (!text) return c.json({ text: body.text })
347+
const cleaned = text
348+
.replace(/<think>[\s\S]*?<\/think>\s*/g, "")
349+
.trim()
350+
return c.json({ text: cleaned || body.text })
351+
},
270352
),
271353
)

0 commit comments

Comments
 (0)