@mtayfur/opencode-prompt-enhancer 0.0.9 → 0.0.11
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 +155 -34
package/package.json
CHANGED
|
@@ -14,6 +14,17 @@ 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 PROGRESS_TOAST_DURATION_MS = 2_000
|
|
20
|
+
const RESULT_TOAST_DURATION_MS = 2_000
|
|
21
|
+
const ENHANCEMENT_CANCELED_MESSAGE = "Prompt enhancement canceled."
|
|
22
|
+
const ENHANCEMENT_ANIMATION_FRAMES = [
|
|
23
|
+
"Enhancing prompt",
|
|
24
|
+
"Enhancing prompt.",
|
|
25
|
+
"Enhancing prompt..",
|
|
26
|
+
"Enhancing prompt...",
|
|
27
|
+
] as const
|
|
17
28
|
const DIALOG_TITLE = "Enhance Prompt"
|
|
18
29
|
const TOAST_TITLE = "Prompt enhancer"
|
|
19
30
|
|
|
@@ -23,8 +34,13 @@ type ModelRef = {
|
|
|
23
34
|
}
|
|
24
35
|
|
|
25
36
|
type Api = Parameters<TuiPlugin>[0]
|
|
37
|
+
type ActiveEnhancement = {
|
|
38
|
+
controller: AbortController
|
|
39
|
+
}
|
|
40
|
+
|
|
26
41
|
type PluginState = {
|
|
27
42
|
enhancing: boolean
|
|
43
|
+
activeEnhancement?: ActiveEnhancement
|
|
28
44
|
promptRef?: TuiPromptRef
|
|
29
45
|
promptTarget?: PromptTarget
|
|
30
46
|
lastOriginal?: TuiPromptInfo
|
|
@@ -87,6 +103,39 @@ function nextPromptInfo(prompt: TuiPromptInfo, input: string): TuiPromptInfo {
|
|
|
87
103
|
}
|
|
88
104
|
}
|
|
89
105
|
|
|
106
|
+
function errorFromReason(reason: unknown, fallbackMessage: string): Error {
|
|
107
|
+
return reason instanceof Error ? reason : new Error(fallbackMessage)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function withRequestTimeout<T>(
|
|
111
|
+
signal: AbortSignal,
|
|
112
|
+
timeoutMs: number,
|
|
113
|
+
run: (signal: AbortSignal) => Promise<T>,
|
|
114
|
+
): Promise<T> {
|
|
115
|
+
const controller = new AbortController()
|
|
116
|
+
const timeoutMessage = `Prompt enhancement timed out after ${Math.floor(timeoutMs / 1000)} seconds.`
|
|
117
|
+
const onAbort = () => controller.abort(signal.reason)
|
|
118
|
+
const timeout = setTimeout(() => controller.abort(new Error(timeoutMessage)), timeoutMs)
|
|
119
|
+
|
|
120
|
+
if (signal.aborted) {
|
|
121
|
+
controller.abort(signal.reason)
|
|
122
|
+
} else {
|
|
123
|
+
signal.addEventListener("abort", onAbort, { once: true })
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
return await run(controller.signal)
|
|
128
|
+
} catch (error) {
|
|
129
|
+
if (controller.signal.aborted && !signal.aborted) {
|
|
130
|
+
throw errorFromReason(controller.signal.reason, timeoutMessage)
|
|
131
|
+
}
|
|
132
|
+
throw error
|
|
133
|
+
} finally {
|
|
134
|
+
clearTimeout(timeout)
|
|
135
|
+
signal.removeEventListener("abort", onAbort)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
90
139
|
function samePromptTarget(left: PromptTarget | undefined, right: PromptTarget | undefined): boolean {
|
|
91
140
|
if (!left || !right || left.name !== right.name) return false
|
|
92
141
|
if (left.name === "home" && right.name === "home") {
|
|
@@ -138,6 +187,7 @@ async function clearPrompt(api: Api, state: PluginState, handle: PromptHandle, s
|
|
|
138
187
|
const promptRef = handle.ref
|
|
139
188
|
if (promptRef) {
|
|
140
189
|
promptRef.set(nextPromptInfo(template ?? promptRef.current, ""))
|
|
190
|
+
promptRef.blur()
|
|
141
191
|
return true
|
|
142
192
|
}
|
|
143
193
|
|
|
@@ -186,7 +236,43 @@ async function restorePrompt(
|
|
|
186
236
|
return true
|
|
187
237
|
}
|
|
188
238
|
|
|
189
|
-
|
|
239
|
+
const requestOptions = { signal, throwOnError: true } as const
|
|
240
|
+
await api.client.tui.clearPrompt({ directory: handle.directory }, requestOptions)
|
|
241
|
+
if (prompt.input) {
|
|
242
|
+
await api.client.tui.appendPrompt({ directory: handle.directory, text: prompt.input }, requestOptions)
|
|
243
|
+
}
|
|
244
|
+
return true
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function startEnhancementAnimation(
|
|
248
|
+
api: Api,
|
|
249
|
+
state: PluginState,
|
|
250
|
+
handle: PromptHandle,
|
|
251
|
+
template?: TuiPromptInfo,
|
|
252
|
+
): () => void {
|
|
253
|
+
const promptRef = handle.ref
|
|
254
|
+
if (!promptRef) return () => {}
|
|
255
|
+
|
|
256
|
+
let frame = 0
|
|
257
|
+
let stopped = false
|
|
258
|
+
|
|
259
|
+
const render = () => {
|
|
260
|
+
if (stopped) return
|
|
261
|
+
if (!isPromptHandleActive(api, state, handle)) {
|
|
262
|
+
stopped = true
|
|
263
|
+
return
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
promptRef.set(nextPromptInfo(template ?? promptRef.current, ENHANCEMENT_ANIMATION_FRAMES[frame]))
|
|
267
|
+
frame = (frame + 1) % ENHANCEMENT_ANIMATION_FRAMES.length
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
render()
|
|
271
|
+
const interval = setInterval(render, ENHANCEMENT_ANIMATION_INTERVAL_MS)
|
|
272
|
+
return () => {
|
|
273
|
+
stopped = true
|
|
274
|
+
clearInterval(interval)
|
|
275
|
+
}
|
|
190
276
|
}
|
|
191
277
|
|
|
192
278
|
function gatherContext(api: Api): string {
|
|
@@ -259,20 +345,24 @@ async function enhanceWithModel(
|
|
|
259
345
|
if (!tempSessionID) throw new Error("Failed to start prompt enhancer.")
|
|
260
346
|
|
|
261
347
|
try {
|
|
262
|
-
const response = await
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
348
|
+
const response = await withRequestTimeout(
|
|
349
|
+
signal,
|
|
350
|
+
ENHANCEMENT_TIMEOUT_MS,
|
|
351
|
+
(requestSignal) => api.client.session.prompt(
|
|
352
|
+
{
|
|
353
|
+
sessionID: tempSessionID,
|
|
354
|
+
directory,
|
|
355
|
+
model,
|
|
356
|
+
system: ENHANCER_SYSTEM_PROMPT,
|
|
357
|
+
parts: [
|
|
358
|
+
{
|
|
359
|
+
type: "text",
|
|
360
|
+
text: userMessage,
|
|
361
|
+
},
|
|
362
|
+
],
|
|
363
|
+
},
|
|
364
|
+
{ signal: requestSignal, throwOnError: true },
|
|
365
|
+
),
|
|
276
366
|
)
|
|
277
367
|
|
|
278
368
|
const parts = response.data?.parts
|
|
@@ -282,11 +372,9 @@ async function enhanceWithModel(
|
|
|
282
372
|
if (!enhanced) throw new Error("Enhancer returned no text.")
|
|
283
373
|
return enhanced
|
|
284
374
|
} finally {
|
|
285
|
-
|
|
286
|
-
await api.client.session.delete({ sessionID: tempSessionID, directory })
|
|
287
|
-
} catch {
|
|
375
|
+
void api.client.session.delete({ sessionID: tempSessionID, directory }).catch(() => {
|
|
288
376
|
// Best-effort cleanup for the temporary enhancement session.
|
|
289
|
-
}
|
|
377
|
+
})
|
|
290
378
|
}
|
|
291
379
|
}
|
|
292
380
|
|
|
@@ -347,10 +435,20 @@ function openEnhanceDialog(
|
|
|
347
435
|
variant: "info",
|
|
348
436
|
title: TOAST_TITLE,
|
|
349
437
|
message: "Enhancing prompt...",
|
|
350
|
-
duration:
|
|
438
|
+
duration: PROGRESS_TOAST_DURATION_MS,
|
|
351
439
|
})
|
|
352
440
|
|
|
441
|
+
const enhancementController = new AbortController()
|
|
442
|
+
const onLifecycleAbort = () => enhancementController.abort(signal.reason)
|
|
443
|
+
state.activeEnhancement = { controller: enhancementController }
|
|
444
|
+
if (signal.aborted) {
|
|
445
|
+
enhancementController.abort(signal.reason)
|
|
446
|
+
} else {
|
|
447
|
+
signal.addEventListener("abort", onLifecycleAbort, { once: true })
|
|
448
|
+
}
|
|
449
|
+
|
|
353
450
|
void (async () => {
|
|
451
|
+
let stopAnimation = () => {}
|
|
354
452
|
try {
|
|
355
453
|
const cleared = await clearPrompt(api, state, handle, signal, originalPrompt)
|
|
356
454
|
if (!cleared) {
|
|
@@ -362,8 +460,15 @@ function openEnhanceDialog(
|
|
|
362
460
|
return
|
|
363
461
|
}
|
|
364
462
|
|
|
365
|
-
|
|
463
|
+
stopAnimation = startEnhancementAnimation(api, state, handle, originalPrompt)
|
|
464
|
+
|
|
465
|
+
const enhanced = await enhanceWithModel(api, options, input, enhancementController.signal)
|
|
366
466
|
if (signal.aborted) return
|
|
467
|
+
if (enhancementController.signal.aborted) {
|
|
468
|
+
throw errorFromReason(enhancementController.signal.reason, ENHANCEMENT_CANCELED_MESSAGE)
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
stopAnimation()
|
|
367
472
|
|
|
368
473
|
const wrote = await writePrompt(api, state, handle, enhanced, signal, originalPrompt)
|
|
369
474
|
if (!wrote) {
|
|
@@ -374,6 +479,9 @@ function openEnhanceDialog(
|
|
|
374
479
|
})
|
|
375
480
|
return
|
|
376
481
|
}
|
|
482
|
+
if (enhancementController.signal.aborted) {
|
|
483
|
+
throw errorFromReason(enhancementController.signal.reason, ENHANCEMENT_CANCELED_MESSAGE)
|
|
484
|
+
}
|
|
377
485
|
|
|
378
486
|
state.lastOriginal = originalPrompt
|
|
379
487
|
state.lastEnhancedInput = enhanced
|
|
@@ -382,11 +490,13 @@ function openEnhanceDialog(
|
|
|
382
490
|
variant: "success",
|
|
383
491
|
title: "Prompt enhanced",
|
|
384
492
|
message: "Enhanced prompt added to input.",
|
|
385
|
-
duration:
|
|
493
|
+
duration: RESULT_TOAST_DURATION_MS,
|
|
386
494
|
})
|
|
387
495
|
} catch (error) {
|
|
388
496
|
if (signal.aborted) return
|
|
389
497
|
|
|
498
|
+
stopAnimation()
|
|
499
|
+
|
|
390
500
|
let restored = true
|
|
391
501
|
if (originalPrompt) {
|
|
392
502
|
try {
|
|
@@ -403,12 +513,24 @@ function openEnhanceDialog(
|
|
|
403
513
|
}
|
|
404
514
|
}
|
|
405
515
|
|
|
406
|
-
const
|
|
516
|
+
const canceled = enhancementController.signal.aborted && !signal.aborted
|
|
517
|
+
const baseMessage = canceled
|
|
518
|
+
? ENHANCEMENT_CANCELED_MESSAGE
|
|
519
|
+
: error instanceof Error
|
|
520
|
+
? error.message
|
|
521
|
+
: "Prompt enhancement failed."
|
|
407
522
|
const message = restored
|
|
408
|
-
?
|
|
523
|
+
? canceled
|
|
524
|
+
? "Enhancement canceled. Original prompt restored."
|
|
525
|
+
: baseMessage
|
|
409
526
|
: `${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 })
|
|
527
|
+
api.ui.toast({ variant: canceled && restored ? "info" : "error", title: TOAST_TITLE, message })
|
|
411
528
|
} finally {
|
|
529
|
+
stopAnimation()
|
|
530
|
+
signal.removeEventListener("abort", onLifecycleAbort)
|
|
531
|
+
if (state.activeEnhancement?.controller === enhancementController) {
|
|
532
|
+
state.activeEnhancement = undefined
|
|
533
|
+
}
|
|
412
534
|
state.enhancing = false
|
|
413
535
|
}
|
|
414
536
|
})()
|
|
@@ -422,6 +544,11 @@ function revertEnhancement(
|
|
|
422
544
|
state: PluginState,
|
|
423
545
|
signal: AbortSignal,
|
|
424
546
|
): void {
|
|
547
|
+
if (state.enhancing && state.activeEnhancement) {
|
|
548
|
+
state.activeEnhancement.controller.abort(new Error(ENHANCEMENT_CANCELED_MESSAGE))
|
|
549
|
+
return
|
|
550
|
+
}
|
|
551
|
+
|
|
425
552
|
if (!state.lastOriginal) {
|
|
426
553
|
api.ui.toast({ variant: "warning", title: TOAST_TITLE, message: "No enhancement to revert." })
|
|
427
554
|
return
|
|
@@ -473,7 +600,7 @@ function revertEnhancement(
|
|
|
473
600
|
variant: "success",
|
|
474
601
|
title: TOAST_TITLE,
|
|
475
602
|
message: "Reverted to original prompt.",
|
|
476
|
-
duration:
|
|
603
|
+
duration: RESULT_TOAST_DURATION_MS,
|
|
477
604
|
})
|
|
478
605
|
} catch (error) {
|
|
479
606
|
if (signal.aborted) return
|
|
@@ -522,10 +649,6 @@ const tui: TuiPlugin = async (api, options) => {
|
|
|
522
649
|
category: "Prompt",
|
|
523
650
|
keybind: "ctrl+e",
|
|
524
651
|
suggested: true,
|
|
525
|
-
slash: {
|
|
526
|
-
name: "enhance",
|
|
527
|
-
aliases: ["enhance-prompt"],
|
|
528
|
-
},
|
|
529
652
|
onSelect: () => {
|
|
530
653
|
openEnhanceDialog(api, options, state, api.lifecycle.signal)
|
|
531
654
|
},
|
|
@@ -536,10 +659,6 @@ const tui: TuiPlugin = async (api, options) => {
|
|
|
536
659
|
description: "Revert last prompt enhancement (Ctrl+Shift+E)",
|
|
537
660
|
category: "Prompt",
|
|
538
661
|
keybind: "ctrl+shift+e",
|
|
539
|
-
slash: {
|
|
540
|
-
name: "revert-enhance",
|
|
541
|
-
aliases: ["revert-enhancement"],
|
|
542
|
-
},
|
|
543
662
|
onSelect: () => {
|
|
544
663
|
revertEnhancement(api, state, api.lifecycle.signal)
|
|
545
664
|
},
|
|
@@ -549,6 +668,8 @@ const tui: TuiPlugin = async (api, options) => {
|
|
|
549
668
|
api.lifecycle.onDispose(() => {
|
|
550
669
|
unregister()
|
|
551
670
|
api.ui.dialog.clear()
|
|
671
|
+
state.activeEnhancement?.controller.abort(api.lifecycle.signal.reason)
|
|
672
|
+
state.activeEnhancement = undefined
|
|
552
673
|
state.enhancing = false
|
|
553
674
|
state.promptRef = undefined
|
|
554
675
|
state.promptTarget = undefined
|