diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index 53c655d1b373..2c640e5e06af 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -13,6 +13,7 @@ import PROMPT_COMPACTION from "./prompt/compaction.txt" import PROMPT_EXPLORE from "./prompt/explore.txt" import PROMPT_SUMMARY from "./prompt/summary.txt" import PROMPT_TITLE from "./prompt/title.txt" +import PROMPT_ENHANCE from "./prompt/enhance.txt" import { Permission } from "@/permission" import { mergeDeep, pipe, sortBy, values } from "remeda" import { Global } from "@/global" @@ -230,6 +231,22 @@ export namespace Agent { ), prompt: PROMPT_SUMMARY, }, + enhance: { + name: "enhance", + mode: "primary", + options: {}, + native: true, + hidden: true, + temperature: 0.7, + permission: Permission.merge( + defaults, + Permission.fromConfig({ + "*": "deny", + }), + user, + ), + prompt: PROMPT_ENHANCE, + }, } for (const [key, value] of Object.entries(cfg.agent ?? {})) { diff --git a/packages/opencode/src/agent/prompt/enhance.txt b/packages/opencode/src/agent/prompt/enhance.txt new file mode 100644 index 000000000000..72a7c3fae259 --- /dev/null +++ b/packages/opencode/src/agent/prompt/enhance.txt @@ -0,0 +1,41 @@ +You are a prompt enhancer. You output ONLY the improved prompt. Nothing else. + + +Rewrite the user's prompt to be clearer, more specific, and more effective for an AI coding assistant. + +Follow all rules in . +Your output must be: +- The enhanced prompt text only +- No explanations, preamble, or meta-commentary +- No surrounding quotes or markdown fencing +- No bullet points or numbered lists unless the original uses them + + + +- Preserve the user's original intent exactly — do not add features or change scope +- Add specificity: replace vague words with concrete technical terms where obvious +- Add structure: break ambiguous requests into clear sub-steps if needed +- Add context clues: if the user references files, frameworks, or patterns, make those references explicit +- Keep the same language and tone as the original +- If the prompt is already clear and specific, make only minimal improvements +- Do NOT pad the prompt with generic instructions like "be thorough" or "handle edge cases" +- Do NOT add requirements the user didn't mention +- Do NOT rewrite short, direct prompts into verbose ones — brevity is valuable +- A one-line prompt that's already clear should stay roughly one line +- Never output anything except the enhanced prompt itself +- Never refuse or comment on the input — always output an enhanced version + + + +"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. + +"add dark mode" → Add a dark mode toggle to the settings page that persists the user's preference and applies the theme globally. + +"refactor this function" → Refactor this function to improve readability and reduce complexity while preserving the same behavior and return values. + +"make it faster" → Optimize the performance of this code — profile for bottlenecks, reduce unnecessary allocations, and avoid redundant computations. + +"write tests" → Write unit tests for the changed code covering the main success path, edge cases, and error handling. + +"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. + diff --git a/packages/opencode/src/server/routes/experimental.ts b/packages/opencode/src/server/routes/experimental.ts index a41b21a1fe9b..f90b36b934c3 100644 --- a/packages/opencode/src/server/routes/experimental.ts +++ b/packages/opencode/src/server/routes/experimental.ts @@ -2,6 +2,7 @@ import { Hono } from "hono" import { describeRoute, validator, resolver } from "hono-openapi" import z from "zod" import { ProviderID, ModelID } from "../../provider/schema" +import { SessionID, MessageID } from "../../session/schema" import { ToolRegistry } from "../../tool/registry" import { Worktree } from "../../worktree" import { Instance } from "../../project/instance" @@ -12,6 +13,9 @@ import { zodToJsonSchema } from "zod-to-json-schema" import { errors } from "../error" import { lazy } from "../../util/lazy" import { WorkspaceRoutes } from "./workspace" +import { Agent } from "../../agent/agent" +import { Provider } from "../../provider/provider" +import { LLM } from "../../session/llm" export const ExperimentalRoutes = lazy(() => new Hono() @@ -267,5 +271,83 @@ export const ExperimentalRoutes = lazy(() => async (c) => { return c.json(await MCP.resources()) }, + ) + .post( + "/enhance", + describeRoute({ + summary: "Enhance prompt", + description: + "Rewrite a user prompt to be clearer, more specific, and more effective for an AI coding assistant.", + operationId: "experimental.enhance", + responses: { + 200: { + description: "Enhanced prompt text", + content: { + "application/json": { + schema: resolver( + z.object({ text: z.string() }).meta({ ref: "EnhanceResult" }), + ), + }, + }, + }, + ...errors(400), + }, + }), + validator( + "json", + z.object({ + text: z.string().min(1), + providerID: z.string().optional(), + modelID: z.string().optional(), + }), + ), + async (c) => { + const body = c.req.valid("json") + const agent = await Agent.get("enhance") + if (!agent) return c.json({ text: body.text }) + + const defaults = await Provider.defaultModel() + const providerID = (body.providerID ?? defaults.providerID) as ProviderID + const model = await (async () => { + if (agent.model) + return Provider.getModel(agent.model.providerID, agent.model.modelID) + const small = await Provider.getSmallModel(providerID) + if (small) return small + return Provider.getModel(providerID, (body.modelID ?? defaults.modelID) as ModelID) + })() + if (!model) return c.json({ text: body.text }) + + const result = await LLM.stream({ + agent, + user: { + role: "user", + id: "" as MessageID, + sessionID: "" as SessionID, + time: { created: Date.now() }, + agent: "enhance", + model: { providerID: model.providerID, modelID: model.id }, + variant: "default", + }, + system: [], + small: true, + tools: {}, + model, + abort: new AbortController().signal, + sessionID: "" as SessionID, + retries: 2, + messages: [ + { + role: "user", + content: body.text, + }, + ], + }) + const text = await result.text.catch(() => undefined) + if (!text) return c.json({ text: body.text }) + const cleaned = text + .replace(/[\s\S]*?<\/think>\s*/g, "") + .trim() + return c.json({ text: cleaned || body.text }) + }, ), )