opencode-prompt-expand 0.0.0-stage → 0.1.0
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/CHANGELOG.md +12 -0
- package/LICENSE +21 -0
- package/README.md +338 -2
- package/README.zh-CN.md +177 -0
- package/assets/bmc-qr.png +0 -0
- package/assets/featured.html +260 -0
- package/assets/featured.png +0 -0
- package/dist/config.js +76 -0
- package/dist/gui-experimental.cjs +61 -0
- package/dist/index.js +170 -0
- package/dist/model.js +104 -0
- package/dist/optimizer.js +86 -0
- package/dist/project-context.js +121 -0
- package/dist/prompts.js +109 -0
- package/dist/rpc.js +47 -0
- package/dist/types.js +1 -0
- package/package.json +64 -4
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=1080, initial-scale=1" />
|
|
6
|
+
<title>opencode-prompt-expand featured image</title>
|
|
7
|
+
<style>
|
|
8
|
+
* { box-sizing: border-box; }
|
|
9
|
+
html, body { margin: 0; width: 1080px; height: 1080px; overflow: hidden; }
|
|
10
|
+
body {
|
|
11
|
+
font-family: "Segoe UI", "Inter", Arial, sans-serif;
|
|
12
|
+
background: #080d19;
|
|
13
|
+
color: #f5f7ff;
|
|
14
|
+
-webkit-font-smoothing: antialiased;
|
|
15
|
+
}
|
|
16
|
+
.canvas {
|
|
17
|
+
position: relative;
|
|
18
|
+
width: 1080px;
|
|
19
|
+
height: 1080px;
|
|
20
|
+
padding: 64px 72px 54px;
|
|
21
|
+
overflow: hidden;
|
|
22
|
+
background:
|
|
23
|
+
radial-gradient(circle at 78% 18%, rgba(70, 130, 255, 0.20), transparent 34%),
|
|
24
|
+
radial-gradient(circle at 18% 78%, rgba(40, 220, 190, 0.13), transparent 32%),
|
|
25
|
+
linear-gradient(145deg, #0a1020 0%, #0b1224 52%, #070b14 100%);
|
|
26
|
+
}
|
|
27
|
+
.canvas::before {
|
|
28
|
+
content: "";
|
|
29
|
+
position: absolute;
|
|
30
|
+
inset: 0;
|
|
31
|
+
background-image:
|
|
32
|
+
linear-gradient(rgba(140, 170, 230, 0.045) 1px, transparent 1px),
|
|
33
|
+
linear-gradient(90deg, rgba(140, 170, 230, 0.045) 1px, transparent 1px);
|
|
34
|
+
background-size: 54px 54px;
|
|
35
|
+
mask-image: linear-gradient(to bottom, rgba(0,0,0,0.7), transparent 78%);
|
|
36
|
+
}
|
|
37
|
+
.glow {
|
|
38
|
+
position: absolute;
|
|
39
|
+
border-radius: 999px;
|
|
40
|
+
filter: blur(80px);
|
|
41
|
+
opacity: 0.5;
|
|
42
|
+
pointer-events: none;
|
|
43
|
+
}
|
|
44
|
+
.g1 { width: 420px; height: 420px; right: -120px; top: 40px; background: #245bff; }
|
|
45
|
+
.g2 { width: 360px; height: 360px; left: -140px; bottom: 20px; background: #16c7a7; opacity: 0.3; }
|
|
46
|
+
.top {
|
|
47
|
+
position: relative;
|
|
48
|
+
display: flex;
|
|
49
|
+
align-items: center;
|
|
50
|
+
justify-content: space-between;
|
|
51
|
+
z-index: 2;
|
|
52
|
+
}
|
|
53
|
+
.badge {
|
|
54
|
+
display: inline-flex;
|
|
55
|
+
align-items: center;
|
|
56
|
+
gap: 12px;
|
|
57
|
+
padding: 12px 18px;
|
|
58
|
+
border: 1px solid rgba(120, 160, 230, 0.28);
|
|
59
|
+
background: rgba(16, 28, 52, 0.72);
|
|
60
|
+
border-radius: 999px;
|
|
61
|
+
font-size: 17px;
|
|
62
|
+
letter-spacing: 2.4px;
|
|
63
|
+
font-weight: 650;
|
|
64
|
+
color: #b9c9e9;
|
|
65
|
+
}
|
|
66
|
+
.dot {
|
|
67
|
+
width: 10px;
|
|
68
|
+
height: 10px;
|
|
69
|
+
border-radius: 50%;
|
|
70
|
+
background: #3ee0be;
|
|
71
|
+
box-shadow: 0 0 18px rgba(62, 224, 190, 0.9);
|
|
72
|
+
}
|
|
73
|
+
.version {
|
|
74
|
+
font-family: Consolas, "Cascadia Code", monospace;
|
|
75
|
+
font-size: 18px;
|
|
76
|
+
color: #7e91b8;
|
|
77
|
+
}
|
|
78
|
+
h1 {
|
|
79
|
+
position: relative;
|
|
80
|
+
z-index: 2;
|
|
81
|
+
margin: 54px 0 0;
|
|
82
|
+
font-size: 86px;
|
|
83
|
+
line-height: 0.98;
|
|
84
|
+
letter-spacing: -3.8px;
|
|
85
|
+
font-weight: 760;
|
|
86
|
+
}
|
|
87
|
+
h1 span {
|
|
88
|
+
color: #67e5ca;
|
|
89
|
+
text-shadow: 0 0 46px rgba(53, 220, 185, 0.24);
|
|
90
|
+
}
|
|
91
|
+
.tagline {
|
|
92
|
+
position: relative;
|
|
93
|
+
z-index: 2;
|
|
94
|
+
margin: 24px 0 0;
|
|
95
|
+
max-width: 820px;
|
|
96
|
+
font-size: 29px;
|
|
97
|
+
line-height: 1.35;
|
|
98
|
+
color: #a9b9d8;
|
|
99
|
+
letter-spacing: -0.3px;
|
|
100
|
+
}
|
|
101
|
+
.flow {
|
|
102
|
+
position: relative;
|
|
103
|
+
z-index: 2;
|
|
104
|
+
display: grid;
|
|
105
|
+
grid-template-columns: 1fr 104px 1.18fr;
|
|
106
|
+
gap: 12px;
|
|
107
|
+
align-items: center;
|
|
108
|
+
margin-top: 48px;
|
|
109
|
+
}
|
|
110
|
+
.card {
|
|
111
|
+
min-height: 300px;
|
|
112
|
+
border-radius: 26px;
|
|
113
|
+
padding: 26px 26px 22px;
|
|
114
|
+
border: 1px solid rgba(130, 165, 230, 0.22);
|
|
115
|
+
background: linear-gradient(180deg, rgba(24, 38, 68, 0.86), rgba(12, 20, 38, 0.88));
|
|
116
|
+
box-shadow: 0 22px 60px rgba(0, 0, 0, 0.32), inset 0 1px 0 rgba(255,255,255,0.05);
|
|
117
|
+
}
|
|
118
|
+
.card.result {
|
|
119
|
+
border-color: rgba(75, 225, 193, 0.35);
|
|
120
|
+
background: linear-gradient(180deg, rgba(18, 53, 62, 0.62), rgba(10, 26, 35, 0.9));
|
|
121
|
+
}
|
|
122
|
+
.label {
|
|
123
|
+
font-family: Consolas, "Cascadia Code", monospace;
|
|
124
|
+
font-size: 14px;
|
|
125
|
+
letter-spacing: 2.6px;
|
|
126
|
+
color: #8094bb;
|
|
127
|
+
margin-bottom: 20px;
|
|
128
|
+
}
|
|
129
|
+
.result .label { color: #64d9c0; }
|
|
130
|
+
.quote {
|
|
131
|
+
font-size: 31px;
|
|
132
|
+
line-height: 1.32;
|
|
133
|
+
color: #e7edff;
|
|
134
|
+
letter-spacing: -0.5px;
|
|
135
|
+
margin: 8px 0 18px;
|
|
136
|
+
}
|
|
137
|
+
.muted {
|
|
138
|
+
font-size: 17px;
|
|
139
|
+
color: #7386aa;
|
|
140
|
+
line-height: 1.4;
|
|
141
|
+
}
|
|
142
|
+
.rline {
|
|
143
|
+
display: flex;
|
|
144
|
+
align-items: center;
|
|
145
|
+
gap: 12px;
|
|
146
|
+
margin: 13px 0;
|
|
147
|
+
}
|
|
148
|
+
.check {
|
|
149
|
+
width: 24px;
|
|
150
|
+
height: 24px;
|
|
151
|
+
border-radius: 8px;
|
|
152
|
+
display: grid;
|
|
153
|
+
place-items: center;
|
|
154
|
+
background: rgba(58, 221, 187, 0.14);
|
|
155
|
+
border: 1px solid rgba(58, 221, 187, 0.35);
|
|
156
|
+
color: #64e5cb;
|
|
157
|
+
font-size: 15px;
|
|
158
|
+
font-weight: 800;
|
|
159
|
+
flex: none;
|
|
160
|
+
}
|
|
161
|
+
.bar {
|
|
162
|
+
height: 10px;
|
|
163
|
+
border-radius: 99px;
|
|
164
|
+
background: linear-gradient(90deg, rgba(109, 145, 210, 0.75), rgba(109, 145, 210, 0.28));
|
|
165
|
+
}
|
|
166
|
+
.w90 { width: 90%; }
|
|
167
|
+
.w74 { width: 74%; }
|
|
168
|
+
.w82 { width: 82%; }
|
|
169
|
+
.w60 { width: 60%; }
|
|
170
|
+
.w68 { width: 68%; }
|
|
171
|
+
.arrow {
|
|
172
|
+
display: flex;
|
|
173
|
+
flex-direction: column;
|
|
174
|
+
align-items: center;
|
|
175
|
+
gap: 10px;
|
|
176
|
+
}
|
|
177
|
+
.bolt {
|
|
178
|
+
width: 68px;
|
|
179
|
+
height: 68px;
|
|
180
|
+
border-radius: 22px;
|
|
181
|
+
display: grid;
|
|
182
|
+
place-items: center;
|
|
183
|
+
font-size: 36px;
|
|
184
|
+
background: linear-gradient(145deg, #47e3c5, #347dff);
|
|
185
|
+
color: #07111f;
|
|
186
|
+
box-shadow: 0 0 45px rgba(60, 220, 195, 0.28);
|
|
187
|
+
}
|
|
188
|
+
.arrow-line {
|
|
189
|
+
width: 2px;
|
|
190
|
+
height: 58px;
|
|
191
|
+
background: linear-gradient(to bottom, rgba(80, 225, 200, 0.7), rgba(80, 225, 200, 0));
|
|
192
|
+
}
|
|
193
|
+
.footer {
|
|
194
|
+
position: absolute;
|
|
195
|
+
z-index: 2;
|
|
196
|
+
left: 72px;
|
|
197
|
+
right: 72px;
|
|
198
|
+
bottom: 50px;
|
|
199
|
+
display: flex;
|
|
200
|
+
gap: 14px;
|
|
201
|
+
align-items: center;
|
|
202
|
+
font-family: Consolas, "Cascadia Code", monospace;
|
|
203
|
+
font-size: 16px;
|
|
204
|
+
color: #6d82a8;
|
|
205
|
+
letter-spacing: 0.4px;
|
|
206
|
+
}
|
|
207
|
+
.footer span {
|
|
208
|
+
padding: 7px 12px;
|
|
209
|
+
border-radius: 8px;
|
|
210
|
+
background: rgba(20, 34, 60, 0.62);
|
|
211
|
+
border: 1px solid rgba(120, 155, 220, 0.14);
|
|
212
|
+
}
|
|
213
|
+
.footer .accent { color: #67e5ca; }
|
|
214
|
+
</style>
|
|
215
|
+
</head>
|
|
216
|
+
<body>
|
|
217
|
+
<div class="canvas">
|
|
218
|
+
<div class="glow g1"></div>
|
|
219
|
+
<div class="glow g2"></div>
|
|
220
|
+
|
|
221
|
+
<div class="top">
|
|
222
|
+
<div class="badge"><span class="dot"></span> OPENCODE DESKTOP PLUGIN</div>
|
|
223
|
+
<div class="version">v0.1.0</div>
|
|
224
|
+
</div>
|
|
225
|
+
|
|
226
|
+
<h1>opencode-<br /><span>prompt-expand</span></h1>
|
|
227
|
+
<p class="tagline">Turn rough drafts into structured prompts — in place, in your composer.</p>
|
|
228
|
+
|
|
229
|
+
<div class="flow">
|
|
230
|
+
<div class="card draft">
|
|
231
|
+
<div class="label">DRAFT</div>
|
|
232
|
+
<div class="quote">“修一下登录的 bug”</div>
|
|
233
|
+
<div class="bar w82" style="margin-bottom:12px"></div>
|
|
234
|
+
<div class="bar w60" style="margin-bottom:22px"></div>
|
|
235
|
+
<div class="muted">vague · missing scope<br />no acceptance criteria</div>
|
|
236
|
+
</div>
|
|
237
|
+
|
|
238
|
+
<div class="arrow">
|
|
239
|
+
<div class="bolt">⚡</div>
|
|
240
|
+
<div class="arrow-line"></div>
|
|
241
|
+
</div>
|
|
242
|
+
|
|
243
|
+
<div class="card result">
|
|
244
|
+
<div class="label">EXPANDED</div>
|
|
245
|
+
<div class="rline"><span class="check">✓</span><div class="bar w90"></div></div>
|
|
246
|
+
<div class="rline"><span class="check">✓</span><div class="bar w74"></div></div>
|
|
247
|
+
<div class="rline"><span class="check">✓</span><div class="bar w82"></div></div>
|
|
248
|
+
<div class="rline"><span class="check">✓</span><div class="bar w68"></div></div>
|
|
249
|
+
<div class="muted" style="margin-top:18px">goal · scope · constraints<br />output · acceptance</div>
|
|
250
|
+
</div>
|
|
251
|
+
</div>
|
|
252
|
+
|
|
253
|
+
<div class="footer">
|
|
254
|
+
<span>MIT licensed</span>
|
|
255
|
+
<span>BYOK</span>
|
|
256
|
+
<span class="accent">never auto-sends</span>
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
</body>
|
|
260
|
+
</html>
|
|
Binary file
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { DEFAULT_PROMPTS } from "./prompts.js";
|
|
2
|
+
export const DEFAULT_SKIP_PATTERNS = [
|
|
3
|
+
/^\s*(git|npm|pnpm|yarn|bun|docker|kubectl|ls|cd|cat|curl)\b/i,
|
|
4
|
+
/^\s*[\/!]/,
|
|
5
|
+
/^(继续|好的|收到|谢谢|可以|ok|okay|yes|no|go|continue|thanks?)[。.!!]?$/i,
|
|
6
|
+
/^\s*[\d\W]{1,3}\s*$/,
|
|
7
|
+
];
|
|
8
|
+
export const DEFAULT_CONFIG = {
|
|
9
|
+
model: "auto",
|
|
10
|
+
mode: "concise",
|
|
11
|
+
maxRounds: 5,
|
|
12
|
+
timeoutMs: 30000,
|
|
13
|
+
maxGrowth: 5,
|
|
14
|
+
minChars: 10,
|
|
15
|
+
maxPromptChars: 6000,
|
|
16
|
+
skipPatterns: DEFAULT_SKIP_PATTERNS,
|
|
17
|
+
skipOnMentions: true,
|
|
18
|
+
projectContext: true,
|
|
19
|
+
projectContextBudget: 2048,
|
|
20
|
+
draftFallback: true,
|
|
21
|
+
prompts: DEFAULT_PROMPTS,
|
|
22
|
+
};
|
|
23
|
+
function numberOption(value, fallback, min) {
|
|
24
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
25
|
+
return fallback;
|
|
26
|
+
return Math.max(min, Math.floor(value));
|
|
27
|
+
}
|
|
28
|
+
function booleanOption(value, fallback) {
|
|
29
|
+
return typeof value === "boolean" ? value : fallback;
|
|
30
|
+
}
|
|
31
|
+
function compilePatterns(value) {
|
|
32
|
+
if (!Array.isArray(value))
|
|
33
|
+
return DEFAULT_SKIP_PATTERNS;
|
|
34
|
+
const patterns = [];
|
|
35
|
+
for (const entry of value) {
|
|
36
|
+
if (typeof entry !== "string")
|
|
37
|
+
continue;
|
|
38
|
+
try {
|
|
39
|
+
patterns.push(new RegExp(entry, "i"));
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// Invalid user pattern: ignore it rather than breaking plugin setup.
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return patterns;
|
|
46
|
+
}
|
|
47
|
+
function mergePrompts(value) {
|
|
48
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
49
|
+
return DEFAULT_PROMPTS;
|
|
50
|
+
const merged = { ...DEFAULT_PROMPTS };
|
|
51
|
+
for (const [key, prompt] of Object.entries(value)) {
|
|
52
|
+
if (typeof prompt === "string" && prompt.trim())
|
|
53
|
+
merged[key] = prompt;
|
|
54
|
+
}
|
|
55
|
+
return merged;
|
|
56
|
+
}
|
|
57
|
+
export function resolveConfig(options = {}, overrides = {}) {
|
|
58
|
+
const mode = overrides.mode ?? options.mode;
|
|
59
|
+
const model = overrides.model ?? options.model;
|
|
60
|
+
const projectContext = overrides.projectContext ?? options.projectContext;
|
|
61
|
+
return {
|
|
62
|
+
model: typeof model === "string" && model.trim() ? model.trim() : DEFAULT_CONFIG.model,
|
|
63
|
+
mode: mode === "full" ? "full" : "concise",
|
|
64
|
+
maxRounds: numberOption(options.maxRounds, DEFAULT_CONFIG.maxRounds, 1),
|
|
65
|
+
timeoutMs: numberOption(options.timeoutMs, DEFAULT_CONFIG.timeoutMs, 500),
|
|
66
|
+
maxGrowth: numberOption(options.maxGrowth, DEFAULT_CONFIG.maxGrowth, 1),
|
|
67
|
+
minChars: numberOption(options.minChars, DEFAULT_CONFIG.minChars, 1),
|
|
68
|
+
maxPromptChars: numberOption(options.maxPromptChars, DEFAULT_CONFIG.maxPromptChars, 1),
|
|
69
|
+
skipPatterns: compilePatterns(options.skipPatterns),
|
|
70
|
+
skipOnMentions: booleanOption(options.skipOnMentions, DEFAULT_CONFIG.skipOnMentions),
|
|
71
|
+
projectContext: booleanOption(projectContext, DEFAULT_CONFIG.projectContext),
|
|
72
|
+
projectContextBudget: numberOption(options.projectContextBudget, DEFAULT_CONFIG.projectContextBudget, 0),
|
|
73
|
+
draftFallback: booleanOption(options.draftFallback, DEFAULT_CONFIG.draftFallback),
|
|
74
|
+
prompts: mergePrompts(options.prompts),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const i=require("solid-js/web"),m=require("@opencode/plugin/gui"),W=require("solid-js"),K=require("solid-js/store"),J=require("@opencode/plugin/rpc"),b=require("effect");function Y(e,t){e.add(m.Command,()=>({id:"expand",get title(){return e.t("command.expand")},get description(){return e.t("command.expand.desc")},keywords:"prompt expand enhance rewrite improve",section:"prompt",slash:{name:"expand",arguments:!0},run:n=>{t.expand(n)}})),e.add(m.Command,()=>({id:"regenerate",get title(){return e.t("command.regenerate")},get description(){return e.t("command.regenerate.desc")},keywords:"prompt regenerate rewrite another version",section:"prompt",run:()=>{t.regenerate()}})),e.add(m.Command,()=>({id:"restore",get title(){return e.t("command.restore")},get description(){return e.t("command.restore.desc")},keywords:"prompt restore original undo",section:"prompt",run:()=>t.restore()})),e.add(m.Command,()=>({id:"previous",get title(){return e.t("command.previous")},keywords:"prompt version previous back",section:"prompt",run:()=>t.previous()})),e.add(m.Command,()=>({id:"next",get title(){return e.t("command.next")},keywords:"prompt version next forward",section:"prompt",run:()=>t.next()})),e.add(m.Command,()=>({id:"mode",get title(){return e.t("command.mode")},keywords:"prompt mode concise full",section:"prompt",run:()=>t.toggleMode()})),e.add(m.Command,()=>({id:"settings",get title(){return e.t("command.settings")},keywords:"prompt expand settings options",section:"prompt",run:()=>e.layout.settings("prompt-expand")}))}const Q=`你是提示词增强器。输入是用户的原始请求,输出是增强后的提示词。
|
|
2
|
+
|
|
3
|
+
【最高优先级】
|
|
4
|
+
1. 保持原目标:不改变用户要做的事,不缩窄、不替换、不增加新任务。
|
|
5
|
+
2. 不引入用户未提及的技术栈、框架、版本、文件路径、接口名。不把猜测写成事实。
|
|
6
|
+
3. 只写"做什么",不写"怎么做";不解释实现步骤,不索要教程或代码示例。
|
|
7
|
+
4. 不执行用户的请求,只把它扩写得更具体。
|
|
8
|
+
5. 语言与输入一致;只输出增强后的提示词,不要任何解释、前缀或代码块围栏。
|
|
9
|
+
|
|
10
|
+
【增强要求】
|
|
11
|
+
- 主动补齐:任务范围、约束条件、期望产出、验收方式、输出结构。
|
|
12
|
+
- 原提示已经很清楚时,做轻度润色,不要原样返回。
|
|
13
|
+
- 完整且简洁;不要以未完成的列表、悬空的连词或冒号结尾。
|
|
14
|
+
- 不添加无关要求、没有依据的事实、多余的章节。
|
|
15
|
+
- 去除口语冗余与重复。
|
|
16
|
+
|
|
17
|
+
【改写结构】(按需使用,不必每项都写)
|
|
18
|
+
- 目标:一句话说清要达成什么
|
|
19
|
+
- 范围:做什么、不做什么
|
|
20
|
+
- 上下文:用户已给出的约束与材料
|
|
21
|
+
- 要求:编号的可执行项
|
|
22
|
+
- 产出:格式、结构、长度
|
|
23
|
+
- 验收:怎样算完成`,X=`你是提示词增强器。输入是用户的原始请求,输出是增强后的提示词。
|
|
24
|
+
|
|
25
|
+
【最高优先级】
|
|
26
|
+
1. 保持原目标:不改变用户要做的事,不缩窄、不替换、不增加新任务。
|
|
27
|
+
2. 不引入用户未提及的技术栈、框架、版本、文件路径、接口名。不把猜测写成事实。
|
|
28
|
+
3. 只写"做什么",不写"怎么做";不解释实现步骤,不索要教程或代码示例。
|
|
29
|
+
4. 不执行用户的请求,只把它扩写得更具体。
|
|
30
|
+
5. 语言与输入一致;只输出增强后的提示词,不要任何解释、前缀或代码块围栏。
|
|
31
|
+
|
|
32
|
+
【增强要求】
|
|
33
|
+
- 主动补齐:任务范围、约束条件、期望产出、验收方式、输出结构。
|
|
34
|
+
- 原提示已经很清楚时,做轻度润色,不要原样返回。
|
|
35
|
+
- 完整且简洁;不要以未完成的列表、悬空的连词或冒号结尾。
|
|
36
|
+
- 不添加无关要求、没有依据的事实、多余的章节。
|
|
37
|
+
- 去除口语冗余与重复。
|
|
38
|
+
|
|
39
|
+
【面向 GPT 的结构偏好】
|
|
40
|
+
- 用编号列表写清可执行要求,步骤顺序明确。
|
|
41
|
+
- 每条验收标准写成可判定的条件,避免"更好""高端"这类主观词。
|
|
42
|
+
- 产出格式写清楚(文件、表格、清单或代码块),需要时给出字段名。`,Z=`你是提示词增强器。输入是用户的原始请求,输出是增强后的提示词。
|
|
43
|
+
|
|
44
|
+
【最高优先级】
|
|
45
|
+
1. 保持原目标:不改变用户要做的事,不缩窄、不替换、不增加新任务。
|
|
46
|
+
2. 不引入用户未提及的技术栈、框架、版本、文件路径、接口名。不把猜测写成事实。
|
|
47
|
+
3. 只写"做什么",不写"怎么做";不解释实现步骤,不索要教程或代码示例。
|
|
48
|
+
4. 不执行用户的请求,只把它扩写得更具体。
|
|
49
|
+
5. 语言与输入一致;只输出增强后的提示词,不要任何解释、前缀或代码块围栏。
|
|
50
|
+
|
|
51
|
+
【增强要求】
|
|
52
|
+
- 主动补齐:任务范围、约束条件、期望产出、验收方式、输出结构。
|
|
53
|
+
- 原提示已经很清楚时,做轻度润色,不要原样返回。
|
|
54
|
+
- 完整且简洁;不要以未完成的列表、悬空的连词或冒号结尾。
|
|
55
|
+
- 不添加无关要求、没有依据的事实、多余的章节。
|
|
56
|
+
- 去除口语冗余与重复。
|
|
57
|
+
|
|
58
|
+
【面向 Claude 的结构偏好】
|
|
59
|
+
- 优先写清目标、边界和约束,用连贯段落表达,不必强行机械编号。
|
|
60
|
+
- 明确说明哪些内容不在范围内,减少越界改动。
|
|
61
|
+
- 验收方式写成"完成时应满足什么",而不是步骤清单。`,V={default:Q,"*gpt*":X,"*claude*":Z},ee=[/^\s*(git|npm|pnpm|yarn|bun|docker|kubectl|ls|cd|cat|curl)\b/i,/^\s*[\/!]/,/^(继续|好的|收到|谢谢|可以|ok|okay|yes|no|go|continue|thanks?)[。.!!]?$/i,/^\s*[\d\W]{1,3}\s*$/],x={model:"auto",maxRounds:5,timeoutMs:3e4,maxGrowth:5,minChars:10,maxPromptChars:6e3,skipOnMentions:!0,projectContext:!0,projectContextBudget:2048,draftFallback:!0};function M(e,t,n){return typeof e!="number"||!Number.isFinite(e)?t:Math.max(n,Math.floor(e))}function H(e,t){return typeof e=="boolean"?e:t}function te(e){if(!Array.isArray(e))return ee;const t=[];for(const n of e)if(typeof n=="string")try{t.push(new RegExp(n,"i"))}catch{}return t}function oe(e){if(!e||typeof e!="object"||Array.isArray(e))return V;const t={...V};for(const[n,s]of Object.entries(e))typeof s=="string"&&s.trim()&&(t[n]=s);return t}function ne(e={},t={}){const n=t.mode??e.mode,s=t.model??e.model,d=t.projectContext??e.projectContext;return{model:typeof s=="string"&&s.trim()?s.trim():x.model,mode:n==="full"?"full":"concise",maxRounds:M(e.maxRounds,x.maxRounds,1),timeoutMs:M(e.timeoutMs,x.timeoutMs,500),maxGrowth:M(e.maxGrowth,x.maxGrowth,1),minChars:M(e.minChars,x.minChars,1),maxPromptChars:M(e.maxPromptChars,x.maxPromptChars,1),skipPatterns:te(e.skipPatterns),skipOnMentions:H(e.skipOnMentions,x.skipOnMentions),projectContext:H(d,x.projectContext),projectContextBudget:M(e.projectContextBudget,x.projectContextBudget,0),draftFallback:H(e.draftFallback,x.draftFallback),prompts:oe(e.prompts)}}const re=[/目标/,/范围/,/要求/,/验收/],se=/^(继续|好的|收到|谢谢|可以|ok|okay|yes|no|go|continue|thanks?)[。.!!]?$/i,ie=/(^|\s)@[\w./\\-]{2,}/;function ae(e){return ie.test(e)}function de(e){return re.filter(t=>t.test(e)).length}function ce(e){return de(e)>=3}function ue(e,t){const n=e.trim(),s=ce(n);return n?/^\s*[\/!]/.test(n)?{skip:!0,reason:"command",notify:!1,structured:s}:se.test(n)?{skip:!0,reason:"confirmation",notify:!1,structured:s}:I(n)<t.minChars?{skip:!0,reason:"too_short",notify:!0,structured:s}:I(n)>t.maxPromptChars?{skip:!0,reason:"too_long",notify:!1,structured:s}:t.skipPatterns.some(d=>d.test(n))?{skip:!0,reason:"pattern",notify:!1,structured:s}:t.skipOnMentions&&ae(n)?{skip:!0,reason:"mention",notify:!0,structured:s}:{skip:!1,notify:!1,structured:s}:{skip:!0,reason:"empty",notify:!1,structured:s}}function I(e){return Array.from(e).length}const le={type:"object",properties:{providerID:{type:"string"},modelID:{type:"string"},variant:{type:"string"}},required:["providerID","modelID"],additionalProperties:!1},pe=J.Rpc.define({id:"prompt-expand",events:{},methods:{optimize:{input:{type:"object",properties:{text:{type:"string"},mode:{enum:["concise","full"]},round:{type:"integer",minimum:1},previous:{type:"string"},sessionID:{type:"string"},directory:{type:"string"},composerModel:le,modelPreference:{type:"string"},projectContext:{type:"boolean"}},required:["text","mode","round"],additionalProperties:!1},output:{type:"object",properties:{ok:{type:"boolean"},text:{type:"string"},model:{type:"string"},code:{type:"string"},message:{type:"string"}},required:["ok"],additionalProperties:!1}}}}),S="__prompt_expand_draft__";function me(e){return e instanceof HTMLTextAreaElement?!e.disabled&&!e.readOnly:e instanceof HTMLInputElement?(e.type==="text"||e.type==="")&&!e.disabled&&!e.readOnly:e.isContentEditable}function fe(e){const t=e.getBoundingClientRect();if(t.width<=0||t.height<=0)return!1;const n=window.getComputedStyle(e);return n.visibility!=="hidden"&&n.display!=="none"}function F(){return typeof document>"u"?null:Array.from(document.querySelectorAll('textarea, input, [contenteditable="true"]')).filter(t=>me(t)&&fe(t)).map(t=>({element:t,rect:t.getBoundingClientRect()})).filter(({rect:t})=>t.bottom>window.innerHeight*.35).sort((t,n)=>n.rect.bottom-t.rect.bottom)[0]?.element??null}function ge(e){return e?e instanceof HTMLTextAreaElement||e instanceof HTMLInputElement?e.value:e.innerText??e.textContent??"":""}function he(e,t){if(!e||!e.isConnected)throw new Error("No draft composer found");if(e.focus(),e instanceof HTMLTextAreaElement||e instanceof HTMLInputElement){const d=e instanceof HTMLTextAreaElement?HTMLTextAreaElement.prototype:HTMLInputElement.prototype,u=Object.getOwnPropertyDescriptor(d,"value")?.set;u?u.call(e,t):e.value=t,e.dispatchEvent(new InputEvent("input",{bubbles:!0,inputType:"insertText",data:t})),e.setSelectionRange(t.length,t.length);return}const n=window.getSelection();if(n){const d=document.createRange();d.selectNodeContents(e),n.removeAllRanges(),n.addRange(d)}if(document.execCommand("insertText",!1,t)||(e.textContent=t,e.dispatchEvent(new InputEvent("beforeinput",{bubbles:!0,inputType:"insertText",data:t})),e.dispatchEvent(new InputEvent("input",{bubbles:!0,inputType:"insertText",data:t}))),!e.isConnected)throw new Error("Draft composer disappeared during write");e.focus()}function ye(){return{composer:{text:()=>ge(F()),setText:t=>he(F(),t),focus:()=>F()?.focus(),model:()=>{}},available:()=>F()!==null}}function ve(e){if(e.ok)return"";const t=e.code?.trim(),n=e.message?.trim(),s=[t?`[${t}]`:"",n].filter(Boolean).join(" ");return s.length>300?`${s.slice(0,297)}...`:s}class xe{runs=new Map;maxVersions;constructor(t=5){this.maxVersions=Math.max(1,t)}get(t){return this.runs.get(t)}start(t,n){const s={original:n,versions:[],index:-1};return this.runs.set(t,s),s}sync(t,n){const s=this.runs.get(t);return!s||n!==s.versions[s.index]?this.start(t,n):s}push(t,n,s){const d=this.runs.get(t)??this.start(t,n);return d.original=d.versions.length===0?n:d.original,d.versions.length>=this.maxVersions&&(d.versions.shift(),d.index>=0&&(d.index-=1)),d.versions.push(s),d.index=d.versions.length-1,d}setIndex(t,n){const s=this.runs.get(t);if(!(!s||n<0||n>=s.versions.length))return s.index=n,s}clear(t){this.runs.delete(t)}clearAll(){this.runs.clear()}}function be(e,t){const n=new xe(5),[s,d]=K.createStore({});function u(){const o=e.stores.prefs.value,a=o.model?.trim();return ne(e.options,{mode:o.mode,model:a&&a!=="auto"?a:void 0,projectContext:o.projectContext})}function p(o,a,r){e.toast.show({title:e.t(o),variant:"default"})}function l(o,a,r){const c=a?e.t(a):"",g=r?.trim().slice(0,300),h=[c,g].filter(Boolean).join(" ");e.toast.show({title:e.t(o),...h?{description:h}:{},variant:"error"})}function C(){try{const o=e.sessions.current();return o?.directory?o.directory:(e.servers.get("sidecar")??e.servers.list().map(r=>e.servers.get(r)).find(r=>r!==void 0))?.data.location.default().directory}catch{return}}function w(o){const a=e.sessions.current();if(a){if(o&&o!==a.id)return;const c=e.screen.current();if(!c){l("toast.noComposer","toast.noComposerDesc");return}return{key:a.id,composer:c.composer,directory:C()}}if(o&&o!==S)return;if(e.options.draftFallback===!1){l("toast.noComposer","toast.noComposerDesc");return}const r=e.router.path();if(r.startsWith("/settings")||r.startsWith("/plugins")){l("toast.noComposer","toast.noComposerDesc");return}if(!t.available()){l("toast.noComposer","toast.noComposerDesc");return}return{key:S,composer:t.composer,directory:C()}}function P(o,a){if(a){if(o==="too_short"){p("toast.tooShort");return}o==="mention"&&p("toast.mention")}}function E(o,a,r){const c=w(o);if(!(!c||c.key!==o)){if(c.composer.text()!==a){p("toast.edited");return}try{c.composer.setText(r),c.composer.focus(),n.clear(o),e.toast.show({title:e.t("toast.restored"),variant:"success"})}catch{l("toast.failed","toast.failedDesc")}}}async function v(o,a){const r=w(a);if(!r||s[r.key])return;const c=u(),g=typeof o=="string"&&o.trim().length>0,h=g?o.trim():r.composer.text();if(!h.trim())return;const _=ue(h,c);if(_.skip){P(_.reason,_.notify);return}const y=g?n.start(r.key,h):n.sync(r.key,r.composer.text());let D=1,A;if(!g&&y.versions.length>0&&r.composer.text()===y.versions[y.index]&&(D=y.versions.length+1,A=y.versions[y.index]),D>c.maxRounds){p("toast.limit");return}d(r.key,!0);try{const f=await e.client.rpc(pe).optimize({text:h,mode:c.mode,round:D,...A?{previous:A}:{},...r.key!==S?{sessionID:r.key}:{},...r.directory?{directory:r.directory}:{},...r.composer.model()?{composerModel:r.composer.model()}:{},modelPreference:c.model,projectContext:c.projectContext});if(!f.ok){const q=ve(f);console.error("[prompt-expand] optimize failed:",q),f.code==="no_model"?l("toast.noModel","toast.noModelDesc",q):l("toast.failed","toast.failedDesc",q);return}r.composer.setText(f.text),r.composer.focus(),n.push(r.key,y.original,f.text);const T=I(y.original),B=I(f.text);e.toast.show({title:e.t("toast.expanded"),description:`${T} → ${B} ${e.t("toast.chars")}`,variant:"success",duration:1e4,actions:[{label:e.t("action.undo"),run:()=>E(r.key,f.text,y.original)},{label:e.t("action.regenerate"),run:()=>{v(void 0,r.key)}}]})}catch(f){const T=f instanceof Error?f.message:String(f);console.error("[prompt-expand] expand failed:",T),l("toast.failed","toast.failedDesc",T)}finally{d(r.key,!1)}}function O(o){return v(void 0,o)}function j(o){const a=w(o);if(!a)return;const r=n.get(a.key);if(!r||r.versions.length===0){p("toast.noVersions");return}if(a.composer.text()!==r.versions[r.index]){p("toast.edited");return}try{a.composer.setText(r.original),a.composer.focus(),n.clear(a.key),e.toast.show({title:e.t("toast.restored"),variant:"success"})}catch{l("toast.failed","toast.failedDesc")}}function k(o,a){const r=w(a);if(!r)return;const c=n.sync(r.key,r.composer.text());if(c.versions.length===0){p("toast.noVersions");return}const g=c.index+o;if(g<0||g>=c.versions.length){p("toast.noVersions");return}const h=c.versions[g];try{r.composer.setText(h),r.composer.focus(),n.setIndex(r.key,g)}catch{l("toast.failed","toast.failedDesc")}}function $(){const o=e.options.mode==="full"?"full":"concise",r=(e.stores.prefs.value.mode??o)==="concise"?"full":"concise";e.stores.prefs.update(c=>{c.mode=r}),p(r==="concise"?"toast.modeConcise":"toast.modeFull")}function R(o){return s[o]===!0}function L(o){return R(o)?{label:e.t("pill.expanding"),tone:"success"}:{label:e.t("pill.expand"),run:()=>{v(void 0,o)}}}return{expand:v,regenerate:O,restore:j,previous:o=>k(-1,o),next:o=>k(1,o),toggleMode:$,busy:R,pill:L}}const Ce=b.Schema.Struct({mode:b.Schema.optional(b.Schema.Union([b.Schema.Literal("concise"),b.Schema.Literal("full")])),model:b.Schema.optional(b.Schema.String),projectContext:b.Schema.optional(b.Schema.Boolean)}),ke=m.Plugin.define({i18n:{en:{"command.expand":"Expand prompt","command.expand.desc":"Turn the current draft into a structured prompt.","command.regenerate":"Regenerate prompt","command.regenerate.desc":"Generate the next expanded version of the current draft.","command.restore":"Restore original prompt","command.restore.desc":"Restore the draft from before the first expansion.","command.previous":"Previous expanded version","command.next":"Next expanded version","command.mode":"Toggle expansion mode","command.settings":"Prompt Expand settings","pill.expand":"⚡ Expand Prompt","pill.expanding":"Expanding…","toast.expanded":"Expanded","toast.chars":"chars","toast.failed":"Expand failed","toast.failedDesc":"Original text was not changed.","toast.tooShort":"Too short to expand","toast.mention":"Contains @ references, skipped","toast.noComposer":"No composer found","toast.noComposerDesc":"Put the cursor in the composer and try again.","toast.noModel":"No connected model","toast.noModelDesc":"Connect a provider or choose a model first.","toast.limit":"5-version limit reached","toast.edited":"Text was edited, not restored","toast.noVersions":"No expanded versions yet","toast.restored":"Original restored","toast.modeConcise":"Mode: concise","toast.modeFull":"Mode: full","action.undo":"Undo","action.regenerate":"Regenerate","settings.title":"Prompt Expand","settings.description":"Configure how drafts are expanded.","settings.mode":"Mode","settings.mode.concise":"Concise (~800 chars)","settings.mode.full":"Full expansion","settings.model":"Model","settings.model.help":"auto, composer, or provider/model; empty uses opencode.jsonc","settings.projectContext":"Project context","settings.projectContext.help":"Read package.json, rule files, and git status.","settings.note":"No default keyboard shortcut. Bind commands in Settings > Shortcuts."},zh:{"command.expand":"扩展提示词","command.expand.desc":"把当前草稿扩展成结构化提示词。","command.regenerate":"重新生成提示词","command.regenerate.desc":"为当前草稿生成下一版扩展结果。","command.restore":"恢复原始提示词","command.restore.desc":"恢复到第一次扩展前的草稿。","command.previous":"上一版扩展结果","command.next":"下一版扩展结果","command.mode":"切换扩展模式","command.settings":"提示词扩展设置","pill.expand":"⚡ Expand Prompt","pill.expanding":"正在扩展…","toast.expanded":"已扩展","toast.chars":"字符","toast.failed":"扩展失败","toast.failedDesc":"原文未改动。","toast.tooShort":"太短,无需扩展","toast.mention":"含 @ 引用,已跳过","toast.noComposer":"未找到输入框","toast.noComposerDesc":"请把光标放进 composer 后重试。","toast.noModel":"没有可用模型","toast.noModelDesc":"请先连接 provider 或选择一个模型。","toast.limit":"已达 5 版上限","toast.edited":"内容已修改,未恢复","toast.noVersions":"还没有扩展版本","toast.restored":"已恢复原文","toast.modeConcise":"模式:简洁","toast.modeFull":"模式:充分展开","action.undo":"撤销","action.regenerate":"重生成","settings.title":"提示词扩展","settings.description":"配置草稿扩展方式。","settings.mode":"模式","settings.mode.concise":"简洁(约 800 字符)","settings.mode.full":"充分展开","settings.model":"模型","settings.model.help":"auto、composer 或 provider/model;留空使用 opencode.jsonc","settings.projectContext":"项目感知","settings.projectContext.help":"读取 package.json、规则文件和 git 状态。","settings.note":"默认没有快捷键,可在 Settings > Shortcuts 中绑定。"}},stores:{prefs:m.Store.global(Ce,{})}});function Se(e,t){return{id:"expand",show:n=>t.pill(n.session.id)}}var we=i.template('<div style="padding:8px 0"><h2 style=font-size:16px;margin-bottom:8px></h2><p></p><div><span></span><select><option value=concise></option><option value=full></option></select></div><div><span></span><input><span></span></div><div><label style=display:flex;align-items:center;gap:8px><input type=checkbox></label><span></span></div><p>');const z={display:"flex","flex-direction":"column",gap:"6px","margin-bottom":"20px","max-width":"560px"},U={"font-size":"13px","font-weight":"600"},N={"font-size":"12px",opacity:"0.65"},G={"font-size":"13px",padding:"6px 8px","border-radius":"6px",border:"1px solid rgba(127, 127, 127, 0.35)",background:"transparent",color:"inherit"};function Te(){const e=m.usePlugin(),t=e.stores.prefs;return(()=>{var n=we(),s=n.firstChild,d=s.nextSibling,u=d.nextSibling,p=u.firstChild,l=p.nextSibling,C=l.firstChild,w=C.nextSibling,P=u.nextSibling,E=P.firstChild,v=E.nextSibling,O=v.nextSibling,j=P.nextSibling,k=j.firstChild,$=k.firstChild,R=k.nextSibling,L=j.nextSibling;return i.insert(s,()=>e.t("settings.title")),i.insert(d,()=>e.t("settings.description")),i.insert(p,()=>e.t("settings.mode")),l.addEventListener("change",o=>{const a=o.currentTarget.value==="full"?"full":"concise";t.update(r=>{r.mode=a})}),i.insert(C,()=>e.t("settings.mode.concise")),i.insert(w,()=>e.t("settings.mode.full")),i.insert(E,()=>e.t("settings.model")),v.$$input=o=>{const a=o.currentTarget.value;t.update(r=>{r.model=a})},i.setAttribute(v,"spellcheck",!1),i.insert(O,()=>e.t("settings.model.help")),$.addEventListener("change",o=>{const a=o.currentTarget.checked;t.update(r=>{r.projectContext=a})}),i.insert(k,()=>e.t("settings.projectContext"),null),i.insert(R,()=>e.t("settings.projectContext.help")),i.insert(L,()=>e.t("settings.note")),i.effect(o=>{var a=N,r=z,c=U,g=G,h=z,_=U,y=G,D=N,A=z,f={...U},T=N,B=N;return o.e=i.style(d,a,o.e),o.t=i.style(u,r,o.t),o.a=i.style(p,c,o.a),o.o=i.style(l,g,o.o),o.i=i.style(P,h,o.i),o.n=i.style(E,_,o.n),o.s=i.style(v,y,o.s),o.h=i.style(O,D,o.h),o.r=i.style(j,A,o.r),o.d=i.style(k,f,o.d),o.l=i.style(R,T,o.l),o.u=i.style(L,B,o.u),o},{e:void 0,t:void 0,a:void 0,o:void 0,i:void 0,n:void 0,s:void 0,h:void 0,r:void 0,d:void 0,l:void 0,u:void 0}),i.effect(()=>l.value=t.value.mode??"concise"),i.effect(()=>v.value=t.value.model??""),i.effect(()=>$.checked=t.value.projectContext??!0),n})()}function Me(e){return{id:"prompt-expand",get title(){return e.t("settings.title")},get entries(){return[{id:"prompt-expand",title:e.t("settings.title"),description:e.t("settings.description")}]},render:()=>i.createComponent(Te,{})}}i.delegateEvents(["input"]);var Pe=i.template('<div style="display:flex;justify-content:center;padding:6px 0"><button type=button style="font-size:13px;padding:5px 12px;border-radius:999px;border:1px solid rgba(127, 127, 127, 0.35);background:transparent;color:inherit">');const Ee=e=>{const t=ye(),n=be(e,t);Y(e,n),e.add(m.ComposerPill,Se(e,n)),e.add(m.SettingsPage,Me(e)),e.add(m.Slot,{at:"window.bottom",render:()=>i.createComponent(W.Show,{get when(){return i.memo(()=>!e.screen.current()&&!e.router.path().startsWith("/settings"))()&&!e.router.path().startsWith("/plugins")},get children(){var s=Pe(),d=s.firstChild;return d.$$click=()=>{n.expand()},i.insert(d,(()=>{var u=i.memo(()=>!!n.busy(S));return()=>u()?e.t("pill.expanding"):e.t("pill.expand")})()),i.effect(u=>{var p=n.busy(S),l=n.busy(S)?"default":"pointer",C=n.busy(S)?.65:1;return p!==u.e&&(d.disabled=u.e=p),l!==u.t&&i.setStyleProperty(d,"cursor",u.t=l),C!==u.a&&i.setStyleProperty(d,"opacity",u.a=C),u},{e:void 0,t:void 0,a:void 0}),s}})})},je=m.Plugin.define({...ke,setup:Ee});i.delegateEvents(["click"]);exports.default=je;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { Plugin } from "@opencode/plugin";
|
|
2
|
+
import { resolveConfig } from "./config.js";
|
|
3
|
+
import { buildPrompt } from "./prompts.js";
|
|
4
|
+
import { cleanOutput, shouldSkip, validateOutput } from "./optimizer.js";
|
|
5
|
+
import { pickModel } from "./model.js";
|
|
6
|
+
import { readProjectContext, resolveProjectDirectory } from "./project-context.js";
|
|
7
|
+
import { PromptExpand } from "./rpc.js";
|
|
8
|
+
class TimeoutError extends Error {
|
|
9
|
+
constructor() {
|
|
10
|
+
super("timeout");
|
|
11
|
+
this.name = "TimeoutError";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function withTimeout(promise, timeoutMs) {
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
const timer = setTimeout(() => reject(new TimeoutError()), timeoutMs);
|
|
17
|
+
promise.then((value) => {
|
|
18
|
+
clearTimeout(timer);
|
|
19
|
+
resolve(value);
|
|
20
|
+
}, (error) => {
|
|
21
|
+
clearTimeout(timer);
|
|
22
|
+
reject(error);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
async function readVcs(ctx, directory) {
|
|
27
|
+
try {
|
|
28
|
+
const input = { location: { directory } };
|
|
29
|
+
const info = await ctx.vcs.get(input);
|
|
30
|
+
const status = await ctx.vcs.status(input);
|
|
31
|
+
return {
|
|
32
|
+
branch: info?.data?.branch?.current,
|
|
33
|
+
files: Array.isArray(status?.data) ? status.data.map((file) => file.file) : [],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function modelRefFromSession(session) {
|
|
41
|
+
const model = session?.model;
|
|
42
|
+
if (!model?.providerID || !model?.id)
|
|
43
|
+
return undefined;
|
|
44
|
+
return {
|
|
45
|
+
providerID: model.providerID,
|
|
46
|
+
id: model.id,
|
|
47
|
+
...(model.variant ? { variant: model.variant } : {}),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export default Plugin.define({
|
|
51
|
+
id: "prompt-expand",
|
|
52
|
+
async setup(ctx) {
|
|
53
|
+
const registration = await ctx.rpc.register(PromptExpand, {
|
|
54
|
+
optimize: async (raw, rpcContext) => {
|
|
55
|
+
const input = raw;
|
|
56
|
+
const config = resolveConfig(ctx.options, {
|
|
57
|
+
mode: input.mode,
|
|
58
|
+
model: input.modelPreference,
|
|
59
|
+
projectContext: input.projectContext,
|
|
60
|
+
});
|
|
61
|
+
const decision = shouldSkip(input.text, config);
|
|
62
|
+
if (decision.skip) {
|
|
63
|
+
return {
|
|
64
|
+
ok: false,
|
|
65
|
+
code: decision.reason ?? "skipped",
|
|
66
|
+
message: decision.reason ?? "skipped",
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
let session;
|
|
70
|
+
try {
|
|
71
|
+
if (input.sessionID)
|
|
72
|
+
session = await ctx.session.get({ sessionID: input.sessionID });
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
session = undefined;
|
|
76
|
+
}
|
|
77
|
+
let models = [];
|
|
78
|
+
let defaultModel = null;
|
|
79
|
+
try {
|
|
80
|
+
const listed = (await ctx.model.list());
|
|
81
|
+
models = Array.isArray(listed) ? listed : Array.isArray(listed?.data) ? listed.data : [];
|
|
82
|
+
const selected = (await ctx.model.default());
|
|
83
|
+
defaultModel =
|
|
84
|
+
selected && "data" in selected
|
|
85
|
+
? (selected.data ?? null)
|
|
86
|
+
: selected;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
models = [];
|
|
90
|
+
defaultModel = null;
|
|
91
|
+
}
|
|
92
|
+
const sessionModel = modelRefFromSession(session);
|
|
93
|
+
const picked = pickModel({
|
|
94
|
+
preference: config.model,
|
|
95
|
+
composerModel: input.composerModel,
|
|
96
|
+
sessionModel,
|
|
97
|
+
models,
|
|
98
|
+
defaultModel,
|
|
99
|
+
});
|
|
100
|
+
if (!picked) {
|
|
101
|
+
return {
|
|
102
|
+
ok: false,
|
|
103
|
+
code: "no_model",
|
|
104
|
+
message: "No connected chat model is available.",
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
let projectContext = "";
|
|
108
|
+
if (config.projectContext) {
|
|
109
|
+
const directory = resolveProjectDirectory({
|
|
110
|
+
sessionDirectory: session?.location?.directory,
|
|
111
|
+
requestDirectory: input.directory,
|
|
112
|
+
pluginDirectory: ctx.location?.directory,
|
|
113
|
+
});
|
|
114
|
+
if (directory) {
|
|
115
|
+
const vcs = await readVcs(ctx, directory);
|
|
116
|
+
try {
|
|
117
|
+
projectContext = await readProjectContext(directory, config.projectContextBudget, vcs);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
projectContext = "";
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const targetModelID = input.composerModel?.modelID ?? sessionModel?.id;
|
|
125
|
+
const prompt = buildPrompt({
|
|
126
|
+
text: input.text,
|
|
127
|
+
mode: config.mode,
|
|
128
|
+
structured: decision.structured,
|
|
129
|
+
previous: input.previous,
|
|
130
|
+
round: input.round,
|
|
131
|
+
projectContext,
|
|
132
|
+
targetModelID,
|
|
133
|
+
prompts: config.prompts,
|
|
134
|
+
});
|
|
135
|
+
try {
|
|
136
|
+
const generated = await withTimeout(ctx.experimental.generate.text({
|
|
137
|
+
model: picked.model,
|
|
138
|
+
prompt,
|
|
139
|
+
}), config.timeoutMs);
|
|
140
|
+
const cleaned = cleanOutput(generated.text);
|
|
141
|
+
const validation = validateOutput(cleaned, input.text, config);
|
|
142
|
+
if (!validation.ok) {
|
|
143
|
+
return {
|
|
144
|
+
ok: false,
|
|
145
|
+
code: `invalid_${validation.reason}`,
|
|
146
|
+
message: "The generated prompt did not pass validation.",
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
ok: true,
|
|
151
|
+
text: cleaned,
|
|
152
|
+
model: `${picked.model.providerID}/${picked.model.id}`,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
if (rpcContext?.signal?.aborted) {
|
|
157
|
+
return { ok: false, code: "aborted", message: "Request aborted." };
|
|
158
|
+
}
|
|
159
|
+
if (error instanceof TimeoutError) {
|
|
160
|
+
return { ok: false, code: "timeout", message: "Expansion timed out." };
|
|
161
|
+
}
|
|
162
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
163
|
+
console.error("[prompt-expand] generate failed:", message);
|
|
164
|
+
return { ok: false, code: "failed", message: `Expansion failed: ${message}` };
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
return () => registration.dispose();
|
|
169
|
+
},
|
|
170
|
+
});
|