@mtayfur/opencode-prompt-enhancer 0.0.10 → 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/plugins/prompt-enhancer.tsx +210 -29
package/package.json
CHANGED
|
@@ -14,6 +14,16 @@ import { ENHANCER_SYSTEM_PROMPT } from "./enhancer-system-prompt"
|
|
|
14
14
|
const MAX_RECENT_MESSAGES = 3
|
|
15
15
|
const MAX_CHANGED_FILES = 25
|
|
16
16
|
const MAX_PROMPT_PREVIEW_LENGTH = 250
|
|
17
|
+
const ENHANCEMENT_TIMEOUT_MS = 60_000
|
|
18
|
+
const ENHANCEMENT_ANIMATION_INTERVAL_MS = 250
|
|
19
|
+
const TOAST_DURATION_MS = 3_000
|
|
20
|
+
const ENHANCEMENT_CANCELED_MESSAGE = "Prompt enhancement canceled."
|
|
21
|
+
const ENHANCEMENT_ANIMATION_FRAMES = [
|
|
22
|
+
"Enhancing prompt",
|
|
23
|
+
"Enhancing prompt.",
|
|
24
|
+
"Enhancing prompt..",
|
|
25
|
+
"Enhancing prompt...",
|
|
26
|
+
] as const
|
|
17
27
|
const DIALOG_TITLE = "Enhance Prompt"
|
|
18
28
|
const TOAST_TITLE = "Prompt enhancer"
|
|
19
29
|
|
|
@@ -23,8 +33,18 @@ type ModelRef = {
|
|
|
23
33
|
}
|
|
24
34
|
|
|
25
35
|
type Api = Parameters<TuiPlugin>[0]
|
|
36
|
+
type ActiveEnhancement = {
|
|
37
|
+
controller: AbortController
|
|
38
|
+
stopAnimation?: () => void
|
|
39
|
+
handle: PromptHandle
|
|
40
|
+
originalPrompt?: TuiPromptInfo
|
|
41
|
+
input: string
|
|
42
|
+
canceled?: boolean
|
|
43
|
+
}
|
|
44
|
+
|
|
26
45
|
type PluginState = {
|
|
27
46
|
enhancing: boolean
|
|
47
|
+
activeEnhancement?: ActiveEnhancement
|
|
28
48
|
promptRef?: TuiPromptRef
|
|
29
49
|
promptTarget?: PromptTarget
|
|
30
50
|
lastOriginal?: TuiPromptInfo
|
|
@@ -87,6 +107,39 @@ function nextPromptInfo(prompt: TuiPromptInfo, input: string): TuiPromptInfo {
|
|
|
87
107
|
}
|
|
88
108
|
}
|
|
89
109
|
|
|
110
|
+
function errorFromReason(reason: unknown, fallbackMessage: string): Error {
|
|
111
|
+
return reason instanceof Error ? reason : new Error(fallbackMessage)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function withRequestTimeout<T>(
|
|
115
|
+
signal: AbortSignal,
|
|
116
|
+
timeoutMs: number,
|
|
117
|
+
run: (signal: AbortSignal) => Promise<T>,
|
|
118
|
+
): Promise<T> {
|
|
119
|
+
const controller = new AbortController()
|
|
120
|
+
const timeoutMessage = `Prompt enhancement timed out after ${Math.floor(timeoutMs / 1000)} seconds.`
|
|
121
|
+
const onAbort = () => controller.abort(signal.reason)
|
|
122
|
+
const timeout = setTimeout(() => controller.abort(new Error(timeoutMessage)), timeoutMs)
|
|
123
|
+
|
|
124
|
+
if (signal.aborted) {
|
|
125
|
+
controller.abort(signal.reason)
|
|
126
|
+
} else {
|
|
127
|
+
signal.addEventListener("abort", onAbort, { once: true })
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
return await run(controller.signal)
|
|
132
|
+
} catch (error) {
|
|
133
|
+
if (controller.signal.aborted && !signal.aborted) {
|
|
134
|
+
throw errorFromReason(controller.signal.reason, timeoutMessage)
|
|
135
|
+
}
|
|
136
|
+
throw error
|
|
137
|
+
} finally {
|
|
138
|
+
clearTimeout(timeout)
|
|
139
|
+
signal.removeEventListener("abort", onAbort)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
90
143
|
function samePromptTarget(left: PromptTarget | undefined, right: PromptTarget | undefined): boolean {
|
|
91
144
|
if (!left || !right || left.name !== right.name) return false
|
|
92
145
|
if (left.name === "home" && right.name === "home") {
|
|
@@ -138,6 +191,7 @@ async function clearPrompt(api: Api, state: PluginState, handle: PromptHandle, s
|
|
|
138
191
|
const promptRef = handle.ref
|
|
139
192
|
if (promptRef) {
|
|
140
193
|
promptRef.set(nextPromptInfo(template ?? promptRef.current, ""))
|
|
194
|
+
promptRef.blur()
|
|
141
195
|
return true
|
|
142
196
|
}
|
|
143
197
|
|
|
@@ -186,7 +240,62 @@ async function restorePrompt(
|
|
|
186
240
|
return true
|
|
187
241
|
}
|
|
188
242
|
|
|
189
|
-
|
|
243
|
+
const requestOptions = { signal, throwOnError: true } as const
|
|
244
|
+
await api.client.tui.clearPrompt({ directory: handle.directory }, requestOptions)
|
|
245
|
+
if (prompt.input) {
|
|
246
|
+
await api.client.tui.appendPrompt({ directory: handle.directory, text: prompt.input }, requestOptions)
|
|
247
|
+
}
|
|
248
|
+
return true
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async function cancelActiveEnhancement(
|
|
252
|
+
api: Api,
|
|
253
|
+
state: PluginState,
|
|
254
|
+
signal: AbortSignal,
|
|
255
|
+
): Promise<boolean> {
|
|
256
|
+
const active = state.activeEnhancement
|
|
257
|
+
if (!state.enhancing || !active) return false
|
|
258
|
+
|
|
259
|
+
active.canceled = true
|
|
260
|
+
active.stopAnimation?.()
|
|
261
|
+
active.controller.abort(new Error(ENHANCEMENT_CANCELED_MESSAGE))
|
|
262
|
+
|
|
263
|
+
if (active.originalPrompt) {
|
|
264
|
+
return restorePrompt(api, state, active.handle, active.originalPrompt, signal)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return writePrompt(api, state, active.handle, active.input, signal)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function startEnhancementAnimation(
|
|
271
|
+
api: Api,
|
|
272
|
+
state: PluginState,
|
|
273
|
+
handle: PromptHandle,
|
|
274
|
+
template?: TuiPromptInfo,
|
|
275
|
+
): () => void {
|
|
276
|
+
const promptRef = handle.ref
|
|
277
|
+
if (!promptRef) return () => {}
|
|
278
|
+
|
|
279
|
+
let frame = 0
|
|
280
|
+
let stopped = false
|
|
281
|
+
|
|
282
|
+
const render = () => {
|
|
283
|
+
if (stopped) return
|
|
284
|
+
if (!isPromptHandleActive(api, state, handle)) {
|
|
285
|
+
stopped = true
|
|
286
|
+
return
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
promptRef.set(nextPromptInfo(template ?? promptRef.current, ENHANCEMENT_ANIMATION_FRAMES[frame]))
|
|
290
|
+
frame = (frame + 1) % ENHANCEMENT_ANIMATION_FRAMES.length
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
render()
|
|
294
|
+
const interval = setInterval(render, ENHANCEMENT_ANIMATION_INTERVAL_MS)
|
|
295
|
+
return () => {
|
|
296
|
+
stopped = true
|
|
297
|
+
clearInterval(interval)
|
|
298
|
+
}
|
|
190
299
|
}
|
|
191
300
|
|
|
192
301
|
function gatherContext(api: Api): string {
|
|
@@ -225,8 +334,6 @@ function gatherContext(api: Api): string {
|
|
|
225
334
|
const files = diff.slice(0, MAX_CHANGED_FILES).map((f) => ` @${f.file}`)
|
|
226
335
|
sections.push(`Files changed in session:\n${files.join("\n")}`)
|
|
227
336
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
337
|
}
|
|
231
338
|
|
|
232
339
|
return sections.join("\n\n")
|
|
@@ -259,20 +366,24 @@ async function enhanceWithModel(
|
|
|
259
366
|
if (!tempSessionID) throw new Error("Failed to start prompt enhancer.")
|
|
260
367
|
|
|
261
368
|
try {
|
|
262
|
-
const response = await
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
369
|
+
const response = await withRequestTimeout(
|
|
370
|
+
signal,
|
|
371
|
+
ENHANCEMENT_TIMEOUT_MS,
|
|
372
|
+
(requestSignal) => api.client.session.prompt(
|
|
373
|
+
{
|
|
374
|
+
sessionID: tempSessionID,
|
|
375
|
+
directory,
|
|
376
|
+
model,
|
|
377
|
+
system: ENHANCER_SYSTEM_PROMPT,
|
|
378
|
+
parts: [
|
|
379
|
+
{
|
|
380
|
+
type: "text",
|
|
381
|
+
text: userMessage,
|
|
382
|
+
},
|
|
383
|
+
],
|
|
384
|
+
},
|
|
385
|
+
{ signal: requestSignal, throwOnError: true },
|
|
386
|
+
),
|
|
276
387
|
)
|
|
277
388
|
|
|
278
389
|
const parts = response.data?.parts
|
|
@@ -282,11 +393,9 @@ async function enhanceWithModel(
|
|
|
282
393
|
if (!enhanced) throw new Error("Enhancer returned no text.")
|
|
283
394
|
return enhanced
|
|
284
395
|
} finally {
|
|
285
|
-
|
|
286
|
-
await api.client.session.delete({ sessionID: tempSessionID, directory })
|
|
287
|
-
} catch {
|
|
396
|
+
void api.client.session.delete({ sessionID: tempSessionID, directory }).catch(() => {
|
|
288
397
|
// Best-effort cleanup for the temporary enhancement session.
|
|
289
|
-
}
|
|
398
|
+
})
|
|
290
399
|
}
|
|
291
400
|
}
|
|
292
401
|
|
|
@@ -347,10 +456,25 @@ function openEnhanceDialog(
|
|
|
347
456
|
variant: "info",
|
|
348
457
|
title: TOAST_TITLE,
|
|
349
458
|
message: "Enhancing prompt...",
|
|
350
|
-
duration:
|
|
459
|
+
duration: TOAST_DURATION_MS,
|
|
351
460
|
})
|
|
352
461
|
|
|
462
|
+
const enhancementController = new AbortController()
|
|
463
|
+
const onLifecycleAbort = () => enhancementController.abort(signal.reason)
|
|
464
|
+
state.activeEnhancement = {
|
|
465
|
+
controller: enhancementController,
|
|
466
|
+
handle,
|
|
467
|
+
originalPrompt,
|
|
468
|
+
input,
|
|
469
|
+
}
|
|
470
|
+
if (signal.aborted) {
|
|
471
|
+
enhancementController.abort(signal.reason)
|
|
472
|
+
} else {
|
|
473
|
+
signal.addEventListener("abort", onLifecycleAbort, { once: true })
|
|
474
|
+
}
|
|
475
|
+
|
|
353
476
|
void (async () => {
|
|
477
|
+
let stopAnimation = () => {}
|
|
354
478
|
try {
|
|
355
479
|
const cleared = await clearPrompt(api, state, handle, signal, originalPrompt)
|
|
356
480
|
if (!cleared) {
|
|
@@ -362,8 +486,18 @@ function openEnhanceDialog(
|
|
|
362
486
|
return
|
|
363
487
|
}
|
|
364
488
|
|
|
365
|
-
|
|
489
|
+
stopAnimation = startEnhancementAnimation(api, state, handle, originalPrompt)
|
|
490
|
+
if (state.activeEnhancement) {
|
|
491
|
+
state.activeEnhancement.stopAnimation = stopAnimation
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const enhanced = await enhanceWithModel(api, options, input, enhancementController.signal)
|
|
366
495
|
if (signal.aborted) return
|
|
496
|
+
if (enhancementController.signal.aborted) {
|
|
497
|
+
throw errorFromReason(enhancementController.signal.reason, ENHANCEMENT_CANCELED_MESSAGE)
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
stopAnimation()
|
|
367
501
|
|
|
368
502
|
const wrote = await writePrompt(api, state, handle, enhanced, signal, originalPrompt)
|
|
369
503
|
if (!wrote) {
|
|
@@ -374,6 +508,9 @@ function openEnhanceDialog(
|
|
|
374
508
|
})
|
|
375
509
|
return
|
|
376
510
|
}
|
|
511
|
+
if (enhancementController.signal.aborted) {
|
|
512
|
+
throw errorFromReason(enhancementController.signal.reason, ENHANCEMENT_CANCELED_MESSAGE)
|
|
513
|
+
}
|
|
377
514
|
|
|
378
515
|
state.lastOriginal = originalPrompt
|
|
379
516
|
state.lastEnhancedInput = enhanced
|
|
@@ -382,11 +519,19 @@ function openEnhanceDialog(
|
|
|
382
519
|
variant: "success",
|
|
383
520
|
title: "Prompt enhanced",
|
|
384
521
|
message: "Enhanced prompt added to input.",
|
|
385
|
-
duration:
|
|
522
|
+
duration: TOAST_DURATION_MS,
|
|
386
523
|
})
|
|
387
524
|
} catch (error) {
|
|
388
525
|
if (signal.aborted) return
|
|
389
526
|
|
|
527
|
+
stopAnimation()
|
|
528
|
+
|
|
529
|
+
const canceled = enhancementController.signal.aborted && !signal.aborted
|
|
530
|
+
if (canceled && state.activeEnhancement?.canceled) {
|
|
531
|
+
// cancelActiveEnhancement already stopped the animation and restored the prompt.
|
|
532
|
+
return
|
|
533
|
+
}
|
|
534
|
+
|
|
390
535
|
let restored = true
|
|
391
536
|
if (originalPrompt) {
|
|
392
537
|
try {
|
|
@@ -402,13 +547,23 @@ function openEnhanceDialog(
|
|
|
402
547
|
restored = false
|
|
403
548
|
}
|
|
404
549
|
}
|
|
405
|
-
|
|
406
|
-
|
|
550
|
+
const baseMessage = canceled
|
|
551
|
+
? ENHANCEMENT_CANCELED_MESSAGE
|
|
552
|
+
: error instanceof Error
|
|
553
|
+
? error.message
|
|
554
|
+
: "Prompt enhancement failed."
|
|
407
555
|
const message = restored
|
|
408
|
-
?
|
|
556
|
+
? canceled
|
|
557
|
+
? "Enhancement canceled. Original prompt restored."
|
|
558
|
+
: baseMessage
|
|
409
559
|
: `${baseMessage} Original prompt could not be restored because the prompt changed. Please re-enter your prompt manually.`
|
|
410
|
-
api.ui.toast({ variant: "error", title: TOAST_TITLE, message })
|
|
560
|
+
api.ui.toast({ variant: canceled && restored ? "info" : "error", title: TOAST_TITLE, message })
|
|
411
561
|
} finally {
|
|
562
|
+
stopAnimation()
|
|
563
|
+
signal.removeEventListener("abort", onLifecycleAbort)
|
|
564
|
+
if (state.activeEnhancement?.controller === enhancementController) {
|
|
565
|
+
state.activeEnhancement = undefined
|
|
566
|
+
}
|
|
412
567
|
state.enhancing = false
|
|
413
568
|
}
|
|
414
569
|
})()
|
|
@@ -422,6 +577,30 @@ function revertEnhancement(
|
|
|
422
577
|
state: PluginState,
|
|
423
578
|
signal: AbortSignal,
|
|
424
579
|
): void {
|
|
580
|
+
if (state.enhancing && state.activeEnhancement) {
|
|
581
|
+
void (async () => {
|
|
582
|
+
try {
|
|
583
|
+
const restored = await cancelActiveEnhancement(api, state, signal)
|
|
584
|
+
if (!restored) {
|
|
585
|
+
api.ui.toast({ variant: "warning", title: TOAST_TITLE, message: "Prompt changed while canceling." })
|
|
586
|
+
return
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
api.ui.toast({
|
|
590
|
+
variant: "info",
|
|
591
|
+
title: TOAST_TITLE,
|
|
592
|
+
message: "Enhancement canceled. Original prompt restored.",
|
|
593
|
+
duration: TOAST_DURATION_MS,
|
|
594
|
+
})
|
|
595
|
+
} catch (error) {
|
|
596
|
+
if (signal.aborted) return
|
|
597
|
+
const message = error instanceof Error ? error.message : "Cancel failed."
|
|
598
|
+
api.ui.toast({ variant: "error", title: TOAST_TITLE, message })
|
|
599
|
+
}
|
|
600
|
+
})()
|
|
601
|
+
return
|
|
602
|
+
}
|
|
603
|
+
|
|
425
604
|
if (!state.lastOriginal) {
|
|
426
605
|
api.ui.toast({ variant: "warning", title: TOAST_TITLE, message: "No enhancement to revert." })
|
|
427
606
|
return
|
|
@@ -473,7 +652,7 @@ function revertEnhancement(
|
|
|
473
652
|
variant: "success",
|
|
474
653
|
title: TOAST_TITLE,
|
|
475
654
|
message: "Reverted to original prompt.",
|
|
476
|
-
duration:
|
|
655
|
+
duration: TOAST_DURATION_MS,
|
|
477
656
|
})
|
|
478
657
|
} catch (error) {
|
|
479
658
|
if (signal.aborted) return
|
|
@@ -541,6 +720,8 @@ const tui: TuiPlugin = async (api, options) => {
|
|
|
541
720
|
api.lifecycle.onDispose(() => {
|
|
542
721
|
unregister()
|
|
543
722
|
api.ui.dialog.clear()
|
|
723
|
+
state.activeEnhancement?.controller.abort(api.lifecycle.signal.reason)
|
|
724
|
+
state.activeEnhancement = undefined
|
|
544
725
|
state.enhancing = false
|
|
545
726
|
state.promptRef = undefined
|
|
546
727
|
state.promptTarget = undefined
|