Skip to content

Commit ea70958

Browse files
anandgupta42claude
andcommitted
fix: address code review findings for prompt enhancement
- Add 15s timeout via `AbortController` to prevent indefinite hangs - Extract `ENHANCE_ID` constant and document synthetic `as any` casts - Fix `clean()` regex to match full-string code fences only (avoids stripping inner code blocks) - Export `stripThinkTags()` as separate utility for testability - Move auto-enhance before extmark expansion (prevents sending expanded paste content to the small model) - Add toast feedback and error logging for auto-enhance path - Update `store.prompt.input` after enhancement so history is accurate - Add outer try/catch with logging to `enhancePrompt()` - Expand tests from 9 to 30: `stripThinkTags()`, `clean()` edge cases, combined pipeline tests Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 1ddc4f5 commit ea70958

3 files changed

Lines changed: 201 additions & 78 deletions

File tree

packages/opencode/src/altimate/enhance-prompt.ts

Lines changed: 78 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ import { MessageV2 } from "@/session/message-v2"
88

99
const log = Log.create({ service: "enhance-prompt" })
1010

11+
const ENHANCE_TIMEOUT_MS = 15_000
12+
// Synthetic ID for enhancement requests — not a real session/message
13+
const ENHANCE_ID = "enhance-prompt" as any
14+
1115
// Research-backed enhancement prompt based on:
1216
// - AutoPrompter (arxiv 2504.20196): 5 missing info categories that cause 27% lower edit correctness
1317
// - Meta-prompting best practices: clear role, structural scaffolding, few-shot examples
@@ -49,9 +53,14 @@ Enhanced: "Analyze why the query is slow. Run EXPLAIN/query profile to identify
4953
User: "migrate this from snowflake to bigquery"
5054
Enhanced: "Migrate the SQL from Snowflake dialect to BigQuery dialect. Convert Snowflake-specific functions (e.g. DATEADD, IFF, QUALIFY) to BigQuery equivalents. Preserve the query logic and verify the translated query is syntactically valid."`
5155

56+
export function stripThinkTags(text: string) {
57+
return text.replace(/<think>[\s\S]*?<\/think>\s*/g, "")
58+
}
59+
5260
export function clean(text: string) {
5361
return text
54-
.replace(/^```\w*\n?|```$/g, "")
62+
.trim()
63+
.replace(/^```\w*\n([\s\S]*?)\n```$/, "$1")
5564
.trim()
5665
.replace(/^(['"])([\s\S]*)\1$/, "$2")
5766
.trim()
@@ -67,67 +76,73 @@ export async function isAutoEnhanceEnabled(): Promise<boolean> {
6776
}
6877

6978
export async function enhancePrompt(text: string): Promise<string> {
70-
if (!text.trim()) return text
71-
72-
log.info("enhancing", { length: text.length })
73-
74-
const defaultModel = await Provider.defaultModel()
75-
const model =
76-
(await Provider.getSmallModel(defaultModel.providerID)) ??
77-
(await Provider.getModel(defaultModel.providerID, defaultModel.modelID))
78-
79-
const agent: Agent.Info = {
80-
name: "enhance-prompt",
81-
mode: "primary",
82-
hidden: true,
83-
options: {},
84-
permission: [],
85-
prompt: ENHANCE_SYSTEM_PROMPT,
86-
temperature: 0.7,
87-
}
88-
89-
const user: MessageV2.User = {
90-
id: "enhance-prompt" as any,
91-
sessionID: "enhance-prompt" as any,
92-
role: "user",
93-
time: { created: Date.now() },
94-
agent: "enhance-prompt",
95-
model: {
96-
providerID: model.providerID,
97-
modelID: model.id,
98-
},
99-
}
100-
101-
const stream = await LLM.stream({
102-
agent,
103-
user,
104-
system: [],
105-
small: true,
106-
tools: {},
107-
model,
108-
abort: new AbortController().signal,
109-
sessionID: "enhance-prompt" as any,
110-
retries: 2,
111-
messages: [
112-
{
113-
role: "user",
114-
content: text,
79+
const trimmed = text.trim()
80+
if (!trimmed) return text
81+
82+
log.info("enhancing", { length: trimmed.length })
83+
84+
const controller = new AbortController()
85+
const timeout = setTimeout(() => controller.abort(), ENHANCE_TIMEOUT_MS)
86+
87+
try {
88+
const defaultModel = await Provider.defaultModel()
89+
const model =
90+
(await Provider.getSmallModel(defaultModel.providerID)) ??
91+
(await Provider.getModel(defaultModel.providerID, defaultModel.modelID))
92+
93+
const agent: Agent.Info = {
94+
name: "enhance-prompt",
95+
mode: "primary",
96+
hidden: true,
97+
options: {},
98+
permission: [],
99+
prompt: ENHANCE_SYSTEM_PROMPT,
100+
temperature: 0.7,
101+
}
102+
103+
const user: MessageV2.User = {
104+
id: ENHANCE_ID,
105+
sessionID: ENHANCE_ID,
106+
role: "user",
107+
time: { created: Date.now() },
108+
agent: "enhance-prompt",
109+
model: {
110+
providerID: model.providerID,
111+
modelID: model.id,
115112
},
116-
],
117-
})
118-
119-
const result = await stream.text.catch((err) => {
120-
log.error("failed to enhance prompt", { error: err })
121-
return undefined
122-
})
123-
124-
if (!result) return text
125-
126-
const cleaned = clean(
127-
result
128-
.replace(/<think>[\s\S]*?<\/think>\s*/g, "")
129-
.trim(),
130-
)
131-
132-
return cleaned || text
113+
}
114+
115+
const stream = await LLM.stream({
116+
agent,
117+
user,
118+
system: [],
119+
small: true,
120+
tools: {},
121+
model,
122+
abort: controller.signal,
123+
sessionID: ENHANCE_ID,
124+
retries: 2,
125+
messages: [
126+
{
127+
role: "user",
128+
content: trimmed,
129+
},
130+
],
131+
})
132+
133+
const result = await stream.text.catch((err) => {
134+
log.error("failed to enhance prompt", { error: err })
135+
return undefined
136+
})
137+
138+
if (!result) return text
139+
140+
const cleaned = clean(stripThinkTags(result).trim())
141+
return cleaned || text
142+
} catch (err) {
143+
log.error("enhance prompt failed", { error: err })
144+
return text
145+
} finally {
146+
clearTimeout(timeout)
147+
}
133148
}

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

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,26 @@ export function Prompt(props: PromptProps) {
611611
const messageID = MessageID.ascending()
612612
let inputText = store.prompt.input
613613

614+
// altimate_change start - auto-enhance prompt before expanding paste text
615+
// Only enhance the raw user text, not shell commands or slash commands
616+
if (store.mode === "normal" && !inputText.startsWith("/")) {
617+
try {
618+
const autoEnhance = await isAutoEnhanceEnabled()
619+
if (autoEnhance) {
620+
toast.show({ message: "Enhancing prompt...", variant: "info", duration: 2000 })
621+
const enhanced = await enhancePrompt(inputText)
622+
if (enhanced !== inputText) {
623+
inputText = enhanced
624+
setStore("prompt", "input", enhanced)
625+
}
626+
}
627+
} catch (err) {
628+
// Enhancement failure should never block prompt submission
629+
console.error("auto-enhance failed, using original prompt", err)
630+
}
631+
}
632+
// altimate_change end
633+
614634
// Expand pasted text inline before submitting
615635
const allExtmarks = input.extmarks.getAllForTypeId(promptPartTypeId)
616636
const sortedExtmarks = allExtmarks.sort((a: { start: number }, b: { start: number }) => b.start - a.start)
@@ -630,20 +650,6 @@ export function Prompt(props: PromptProps) {
630650
// Filter out text parts (pasted content) since they're now expanded inline
631651
const nonTextParts = store.prompt.parts.filter((part) => part.type !== "text")
632652

633-
// altimate_change start - auto-enhance prompt before sending (if enabled)
634-
// Only enhance normal prompts, not shell commands or slash commands
635-
if (store.mode === "normal" && !inputText.startsWith("/")) {
636-
try {
637-
const autoEnhance = await isAutoEnhanceEnabled()
638-
if (autoEnhance) {
639-
inputText = await enhancePrompt(inputText)
640-
}
641-
} catch {
642-
// Enhancement failure should never block prompt submission
643-
}
644-
}
645-
// altimate_change end
646-
647653
// Capture mode before it gets reset
648654
const currentMode = store.mode
649655
const variant = local.model.variant.current()

packages/opencode/test/altimate/enhance-prompt.test.ts

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, expect, test } from "bun:test"
2-
import { clean } from "../../src/altimate/enhance-prompt"
2+
import { clean, stripThinkTags } from "../../src/altimate/enhance-prompt"
33

44
describe("enhance-prompt clean()", () => {
55
test("strips markdown code fences", () => {
@@ -38,4 +38,106 @@ describe("enhance-prompt clean()", () => {
3838
const input = "```\nFirst do X.\nThen do Y.\n```"
3939
expect(clean(input)).toBe("First do X.\nThen do Y.")
4040
})
41+
42+
test("handles code fences with trailing whitespace", () => {
43+
expect(clean(" ```\nenhanced prompt\n``` ")).toBe("enhanced prompt")
44+
})
45+
46+
test("preserves inner code blocks", () => {
47+
const input = "Run this:\n```sql\nSELECT 1\n```\nThen verify."
48+
expect(clean(input)).toBe("Run this:\n```sql\nSELECT 1\n```\nThen verify.")
49+
})
50+
51+
test("handles whitespace-only string", () => {
52+
expect(clean(" ")).toBe("")
53+
})
54+
55+
test("handles code fence with no newline before content", () => {
56+
expect(clean("```enhanced prompt```")).toBe("```enhanced prompt```")
57+
})
58+
59+
test("handles single backtick quotes (not code fences)", () => {
60+
expect(clean("`enhanced prompt`")).toBe("`enhanced prompt`")
61+
})
62+
63+
test("strips quotes from multiline content", () => {
64+
expect(clean('"First line.\nSecond line."')).toBe("First line.\nSecond line.")
65+
})
66+
67+
test("does not strip mismatched quotes", () => {
68+
expect(clean("'enhanced prompt\"")).toBe("'enhanced prompt\"")
69+
})
70+
71+
test("handles nested quotes inside code fences", () => {
72+
// After fence stripping, quote stripping also triggers on surrounding quotes
73+
expect(clean('```\n\'inner quoted\'\n```')).toBe("inner quoted")
74+
})
75+
})
76+
77+
describe("enhance-prompt stripThinkTags()", () => {
78+
test("removes single think block", () => {
79+
expect(stripThinkTags("<think>reasoning here</think>actual prompt")).toBe("actual prompt")
80+
})
81+
82+
test("removes think block with trailing whitespace", () => {
83+
expect(stripThinkTags("<think>reasoning</think>\n\nactual prompt")).toBe("actual prompt")
84+
})
85+
86+
test("removes multiple think blocks", () => {
87+
const input = "<think>first</think>part one <think>second</think>part two"
88+
expect(stripThinkTags(input)).toBe("part one part two")
89+
})
90+
91+
test("handles multiline think content", () => {
92+
const input = "<think>\nStep 1: analyze\nStep 2: rewrite\n</think>\nEnhanced prompt here"
93+
expect(stripThinkTags(input)).toBe("Enhanced prompt here")
94+
})
95+
96+
test("returns text unchanged when no think tags", () => {
97+
expect(stripThinkTags("fix the auth bug")).toBe("fix the auth bug")
98+
})
99+
100+
test("handles empty string", () => {
101+
expect(stripThinkTags("")).toBe("")
102+
})
103+
104+
test("handles think tags with no content after", () => {
105+
expect(stripThinkTags("<think>reasoning only</think>")).toBe("")
106+
})
107+
108+
test("handles nested angle brackets inside think tags", () => {
109+
expect(stripThinkTags("<think>check if x < 5 and y > 3</think>result")).toBe("result")
110+
})
111+
})
112+
113+
describe("enhance-prompt combined pipeline", () => {
114+
test("strips think tags then code fences then quotes", () => {
115+
const input = '<think>reasoning</think>```\n"enhanced prompt"\n```'
116+
const result = clean(stripThinkTags(input).trim())
117+
expect(result).toBe("enhanced prompt")
118+
})
119+
120+
test("strips think tags and preserves plain text", () => {
121+
const input = "<think>let me think about this</think>Fix the failing dbt test by checking the schema."
122+
const result = clean(stripThinkTags(input).trim())
123+
expect(result).toBe("Fix the failing dbt test by checking the schema.")
124+
})
125+
126+
test("handles think tags with code-fenced response", () => {
127+
const input = "<think>The user wants to fix a test</think>\n```text\nInvestigate the failing test.\n```"
128+
const result = clean(stripThinkTags(input).trim())
129+
expect(result).toBe("Investigate the failing test.")
130+
})
131+
132+
test("handles clean output that is empty after stripping", () => {
133+
const input = '<think>everything is reasoning</think>```\n\n```'
134+
const result = clean(stripThinkTags(input).trim())
135+
expect(result).toBe("")
136+
})
137+
138+
test("preserves content when no wrapping detected", () => {
139+
const input = "Add a created_at timestamp column to the users dbt model."
140+
const result = clean(stripThinkTags(input).trim())
141+
expect(result).toBe("Add a created_at timestamp column to the users dbt model.")
142+
})
41143
})

0 commit comments

Comments
 (0)