@jianyiagi/dsh-prompt-enhancer 0.0.0-stage → 1.0.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/LICENSE +21 -0
- package/README.md +110 -2
- package/client.js +141 -0
- package/cordis.patch.yml +8 -0
- package/enhance-core.js +102 -0
- package/icon.svg +4 -0
- package/index.js +81 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +46 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 jianyiagi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,111 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @jianyiagi/dsh-prompt-enhancer
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> DSH(DeepSeek Harness)Web 端插件:**输入框一键优化提示词**。
|
|
4
|
+
> 有内容时,模型选择器左侧出现一个闪光按钮;点一下,模型把提示词改写得更清晰、更可执行,结果直接替换输入框草稿。
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 功能
|
|
9
|
+
|
|
10
|
+
- **一键增强**:优化表达 + 结构化(自然段落优先),消除歧义与口语化,明确目标 / 对象 / 约束 / 期望输出
|
|
11
|
+
- **忠于原文**:保留原意与语言,不编造原文没有的信息;代码、命令、路径、变量名、字段名**原样保留**
|
|
12
|
+
- **无冗余**:不写「未指定 / 未提供」占位,各部分不重复,长度不明显超过原文
|
|
13
|
+
- **直接可用**:结果写回输入框,不满意可直接编辑器撤销
|
|
14
|
+
- **双语**:界面文案随 DSH 语言(中文 / English)
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 安装
|
|
19
|
+
|
|
20
|
+
### 桌面端(Electron App)
|
|
21
|
+
|
|
22
|
+
**推荐**:左侧栏「**插件**」页 → 安装 bundle → 填入包名:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
@jianyiagi/dsh-prompt-enhancer
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
装完**完全退出 App 再重新启动**(不是刷新页面)。
|
|
29
|
+
|
|
30
|
+
### CLI / Web 端
|
|
31
|
+
|
|
32
|
+
```powershell
|
|
33
|
+
dsh plugin --profile <你的profile> add @jianyiagi/dsh-prompt-enhancer
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
若 `dsh` 不在 PATH,用 DSH 安装目录下的 bin:
|
|
37
|
+
|
|
38
|
+
```powershell
|
|
39
|
+
node "<dsh-app>\lib\bin.js" plugin --profile=<profile> add @jianyiagi/dsh-prompt-enhancer
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
> `<dsh-app>` = DSH 安装目录下的 `resources\dsh\app`。
|
|
43
|
+
> 若装完没生效,说明包已安装但 **bundle 未被选中**:在「插件」页勾选它,或把包名加入 profile 的 `dsh.profile.bundles` 后重启。
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## 使用
|
|
48
|
+
|
|
49
|
+
| 入口 | 行为 |
|
|
50
|
+
|---|---|
|
|
51
|
+
| 输入框工具行的闪光按钮(模型选择器左侧) | 草稿非空时出现 → 单击 → 转圈 → 草稿被增强结果替换 |
|
|
52
|
+
| 斜杠命令 `/prompt-enhance <文本>` | 结果以命令结果行展示,**不写回草稿**(附带能力) |
|
|
53
|
+
|
|
54
|
+
增强失败时按钮左侧显示一行错误(4 秒后自动消失)。
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 配置:固定增强所用模型(推荐)
|
|
59
|
+
|
|
60
|
+
默认**跟随会话当前模型**。若想更快,可在 profile 补丁里固定一个轻量模型:
|
|
61
|
+
|
|
62
|
+
```yaml
|
|
63
|
+
- insert:
|
|
64
|
+
- id: prompt-enhancer
|
|
65
|
+
name: '@jianyiagi/dsh-prompt-enhancer'
|
|
66
|
+
config:
|
|
67
|
+
provider: <provider-id>
|
|
68
|
+
model: <model-id>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
两个字段必须同时提供才生效;删掉 `config` 即恢复跟随会话模型。
|
|
72
|
+
|
|
73
|
+
> 为什么建议固定:增强是**生成密集型**调用。实测同一段提示词,大模型需 25~48 秒,轻量模型仅 4~6 秒(约 6~12 倍)。输出很短,瓶颈完全在模型思考。
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## 已知限制(不是 bug)
|
|
78
|
+
|
|
79
|
+
1. **每次增强会在对话流留一行** `prompt-enhance · <结果预览>`。原因:插件唯一的客户端→宿主通道是命令注册表,注册表强制记录 `command/run`/`command/done`,而 Chat 层的命令行展示是宿主内建行为、未开插件级隐藏接口。该行不影响模型历史、不计 token。
|
|
80
|
+
2. **引用芯片会退化为纯文本**:草稿里的 `@文件` 引用在整体替换后变成普通文本。
|
|
81
|
+
3. **直接替换、无预览**:不满意请用编辑器撤销。
|
|
82
|
+
4. **仅文本增强**:不改动附件;单次输入上限 24 KB,超时 60 秒。
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## 卸载
|
|
87
|
+
|
|
88
|
+
桌面端:「插件」页卸载。CLI:
|
|
89
|
+
|
|
90
|
+
```powershell
|
|
91
|
+
dsh plugin --profile <profile> remove @jianyiagi/dsh-prompt-enhancer
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## 它怎么工作
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
输入框草稿 → 按钮(会话作用域槽位 conversation.input.right)
|
|
100
|
+
→ ctx.remote.commands.execute("/prompt-enhance <草稿>")
|
|
101
|
+
→ 宿主命令用 ctx.llm.stream 调模型(凭据走你的账户登录态,插件不接触 API Key)
|
|
102
|
+
→ 结果经命令结果回传 → inputActions.setDraft(结果)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
- 不读取、不存储、不上传任何 API Key
|
|
106
|
+
- 不发送除提示词本身以外的任何内容给模型
|
|
107
|
+
- 开源免费(MIT)
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
**版本**:1.0.0 | **维护者**:@jianyiagi
|
package/client.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-prompt-enhancer — browser half.
|
|
3
|
+
*
|
|
4
|
+
* Injects a one-click enhancement button into the composer tool row
|
|
5
|
+
* (`conversation.input.right`, immediately left of the model selector).
|
|
6
|
+
* The button renders only while the draft is non-empty; a click runs the
|
|
7
|
+
* host `/prompt-enhance` command and replaces the whole draft with the
|
|
8
|
+
* returned text through the standard `inputActions.setDraft` face.
|
|
9
|
+
*/
|
|
10
|
+
window.__ModuleLoader__.load({
|
|
11
|
+
id: '@local/dsh-prompt-enhancer',
|
|
12
|
+
factory(require) {
|
|
13
|
+
const React = require('react');
|
|
14
|
+
const h = React.createElement;
|
|
15
|
+
const NS = 'prompt-enhancer';
|
|
16
|
+
|
|
17
|
+
const CSS = [
|
|
18
|
+
'.pe-error{font-size:12px;line-height:20px;white-space:nowrap;',
|
|
19
|
+
'color:var(--dsw-alias-state-error-primary);margin-right:6px;',
|
|
20
|
+
'max-width:240px;overflow:hidden;text-overflow:ellipsis}',
|
|
21
|
+
'.pe-button{box-sizing:border-box;display:inline-flex;flex:none;',
|
|
22
|
+
'align-items:center;justify-content:center;width:28px;height:28px;',
|
|
23
|
+
'padding:0;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;',
|
|
24
|
+
'background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer}',
|
|
25
|
+
'.pe-button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);',
|
|
26
|
+
'color:var(--dsw-alias-label-primary)}',
|
|
27
|
+
'.pe-button:disabled{cursor:default;opacity:.7}',
|
|
28
|
+
'.pe-button:focus-visible{outline:var(--dsw-focus-ring-width,2px) solid',
|
|
29
|
+
'var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:-1px}',
|
|
30
|
+
'.pe-spinner{box-sizing:border-box;width:12px;height:12px;border-radius:50%;',
|
|
31
|
+
'border:2px solid currentColor;border-top-color:transparent;',
|
|
32
|
+
'animation:pe-spin .6s linear infinite}',
|
|
33
|
+
'@keyframes pe-spin{to{transform:rotate(360deg)}}',
|
|
34
|
+
'@media (prefers-reduced-motion:reduce){.pe-spinner{animation:none}}',
|
|
35
|
+
].join('');
|
|
36
|
+
|
|
37
|
+
const ICON = h('svg', {
|
|
38
|
+
viewBox: '0 0 16 16', width: 14, height: 14, 'aria-hidden': true,
|
|
39
|
+
style: { display: 'block' },
|
|
40
|
+
}, h('path', {
|
|
41
|
+
fill: 'currentColor',
|
|
42
|
+
d: 'M8 1.5l1.6 4.4L14 7.5l-4.4 1.6L8 13.5 6.4 9.1 2 7.5l4.4-1.6z',
|
|
43
|
+
}), h('path', {
|
|
44
|
+
fill: 'currentColor', opacity: 0.7,
|
|
45
|
+
d: 'M12.8 10.2l.6 1.6 1.6.6-1.6.6-.6 1.6-.6-1.6-1.6-.6 1.6-.6z',
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
const SPINNER = h('span', { className: 'pe-spinner', 'aria-hidden': true });
|
|
49
|
+
|
|
50
|
+
const ZH = {
|
|
51
|
+
'button.title': '增强提示词',
|
|
52
|
+
'error.unknown': '命令未匹配',
|
|
53
|
+
'error.empty': '模型未返回增强结果',
|
|
54
|
+
'error.failed': '增强失败',
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const EN = {
|
|
58
|
+
'button.title': 'Enhance prompt',
|
|
59
|
+
'error.unknown': 'Command not matched',
|
|
60
|
+
'error.empty': 'Model returned nothing',
|
|
61
|
+
'error.failed': 'Enhancement failed',
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
function EnhanceButton({ useInput, inputActions, enhance, t }) {
|
|
65
|
+
const draft = useInput((state) => state.draft) ?? '';
|
|
66
|
+
const [busy, setBusy] = React.useState(false);
|
|
67
|
+
const [error, setError] = React.useState('');
|
|
68
|
+
const errorTimer = React.useRef(0);
|
|
69
|
+
React.useEffect(() => () => window.clearTimeout(errorTimer.current), []);
|
|
70
|
+
if (draft.trim() === '') return null;
|
|
71
|
+
const flashError = (message) => {
|
|
72
|
+
setError(message);
|
|
73
|
+
window.clearTimeout(errorTimer.current);
|
|
74
|
+
errorTimer.current = window.setTimeout(() => setError(''), 4000);
|
|
75
|
+
};
|
|
76
|
+
const onClick = async () => {
|
|
77
|
+
if (busy) return;
|
|
78
|
+
setBusy(true);
|
|
79
|
+
setError('');
|
|
80
|
+
try {
|
|
81
|
+
const result = await enhance(draft);
|
|
82
|
+
if (!result.ok) {
|
|
83
|
+
flashError(result.error?.message ?? t('error.failed'));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const value = result.value;
|
|
87
|
+
if (value === undefined) {
|
|
88
|
+
flashError(t('error.unknown'));
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (value.result.kind === 'error') {
|
|
92
|
+
flashError(value.result.text);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const text = (value.result.text ?? '').trim();
|
|
96
|
+
if (text === '') {
|
|
97
|
+
flashError(t('error.empty'));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
inputActions.setDraft(text);
|
|
101
|
+
} catch (thrown) {
|
|
102
|
+
flashError(thrown?.message ?? String(thrown));
|
|
103
|
+
} finally {
|
|
104
|
+
setBusy(false);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
return h(React.Fragment, null,
|
|
108
|
+
h('style', { key: 'pe-style' }, CSS),
|
|
109
|
+
error === '' ? null : h('span', {
|
|
110
|
+
key: 'pe-error', className: 'pe-error', role: 'status',
|
|
111
|
+
}, error),
|
|
112
|
+
h('button', {
|
|
113
|
+
key: 'pe-button',
|
|
114
|
+
type: 'button',
|
|
115
|
+
className: 'pe-button',
|
|
116
|
+
title: t('button.title'),
|
|
117
|
+
'aria-label': t('button.title'),
|
|
118
|
+
disabled: busy,
|
|
119
|
+
onMouseDown: (event) => event.preventDefault(),
|
|
120
|
+
onClick,
|
|
121
|
+
}, busy ? SPINNER : ICON),
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
inject: ['slots', 'locale', 'remote', 'remote.commands'],
|
|
127
|
+
apply(ctx) {
|
|
128
|
+
ctx.effect(() => ctx.locale.register(NS, { zh: ZH, en: EN }), 'prompt-enhancer: locale');
|
|
129
|
+
ctx.slots.inject('conversation.input.right', () => ctx.slots.register({
|
|
130
|
+
name: 'conversation.input.right',
|
|
131
|
+
id: 'prompt-enhancer-button',
|
|
132
|
+
order: 10,
|
|
133
|
+
locale: NS,
|
|
134
|
+
inject: (sessionId) => ({
|
|
135
|
+
enhance: (draft) => ctx.remote.commands.execute(sessionId, `/prompt-enhance ${draft}`, []),
|
|
136
|
+
}),
|
|
137
|
+
}, EnhanceButton));
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
},
|
|
141
|
+
});
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# @jianyiagi/dsh-prompt-enhancer profile patch: inserts the plugin row.
|
|
2
|
+
# The host half (index.js) registers the /prompt-enhance command that runs the
|
|
3
|
+
# model-backed enhancement; the browser half (client.js) renders the one-click
|
|
4
|
+
# button in the composer tool row, immediately left of the model selector.
|
|
5
|
+
|
|
6
|
+
- insert:
|
|
7
|
+
- id: prompt-enhancer
|
|
8
|
+
name: '@jianyiagi/dsh-prompt-enhancer'
|
package/enhance-core.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-prompt-enhancer — pure enhancement core.
|
|
3
|
+
*
|
|
4
|
+
* No host imports: everything here is unit-testable in plain Node. The host
|
|
5
|
+
* half (index.js) wires these helpers to the command handler and the LLM
|
|
6
|
+
* stream.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** Built-in enhancement instruction (not configurable). */
|
|
10
|
+
export const SYSTEM_PROMPT = [
|
|
11
|
+
'你是提示词工程专家。请对用户给出的提示词做优化与结构化改写,让它更清晰、更完整、更容易被执行。',
|
|
12
|
+
'',
|
|
13
|
+
'改写原则:',
|
|
14
|
+
'1. 优化表达:消除歧义和口语化说法,明确任务目标、对象、约束与期望输出。',
|
|
15
|
+
'2. 忠于原文:保留原有意图、语言和信息量,不添加原文没有的事实(中文进中文出,英文进英文出)。',
|
|
16
|
+
'3. 保持人类可读:一律用自然流畅的段落或短句表达,禁止使用小标题分节;读起来要像人写的需求说明。',
|
|
17
|
+
'4. 简洁优先:改写结果不要明显长于原文;各部分之间不要重复表述,能合并的合并。',
|
|
18
|
+
'5. 不写占位:原文没有的信息直接省略,禁止输出「未指定」「未提供」「未明确」之类的占位文字。',
|
|
19
|
+
'6. 原样保留:涉及代码、命令、路径、变量名、字段名等内容时原样保留,不要改写或翻译。',
|
|
20
|
+
'',
|
|
21
|
+
'输出要求:只输出改写后的提示词本体,不要解释、前言、后缀,不要使用 Markdown 代码围栏或其他包装。',
|
|
22
|
+
].join('\n');
|
|
23
|
+
|
|
24
|
+
/** Largest draft the command accepts (bytes). */
|
|
25
|
+
export const MAX_INPUT_BYTES = 24576;
|
|
26
|
+
/** Largest enhancement result the command accepts (tokens). */
|
|
27
|
+
export const MAX_OUTPUT_TOKENS = 4096;
|
|
28
|
+
/** Auxiliary-call deadline (ms). */
|
|
29
|
+
export const TIMEOUT_MS = 60000;
|
|
30
|
+
/** Message-source kind recorded for enhancement requests. */
|
|
31
|
+
export const ENHANCE_SOURCE_KIND = 'prompt-enhancer';
|
|
32
|
+
|
|
33
|
+
/** Strip the leading separator whitespace `parseCommand` keeps in rawInput. */
|
|
34
|
+
export function trimDraft(rawInput) {
|
|
35
|
+
return rawInput.replace(/^[\t\n\r ]+/, '');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Strip one whole-output Markdown code fence, if the entire output is fenced. */
|
|
39
|
+
export function stripFence(text) {
|
|
40
|
+
const trimmed = text.trim();
|
|
41
|
+
const match = /^```[^\n]*\n([\s\S]*?)\n?```$/.exec(trimmed);
|
|
42
|
+
return match === null ? trimmed : match[1].trim();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Normalize raw model output into replacement draft text; `''` when unusable. */
|
|
46
|
+
export function normalizeEnhancedText(raw) {
|
|
47
|
+
return stripFence(raw).trim();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Map a terminal finish reason to an Error, or `undefined` for a clean stop. */
|
|
51
|
+
export function finishError(finish) {
|
|
52
|
+
switch (finish.kind) {
|
|
53
|
+
case 'stop': return undefined;
|
|
54
|
+
case 'error':
|
|
55
|
+
case 'aborted': {
|
|
56
|
+
const error = new Error(finish.failure?.message ?? finish.kind);
|
|
57
|
+
return error;
|
|
58
|
+
}
|
|
59
|
+
case 'max-tokens': return new Error('增强结果超出长度上限');
|
|
60
|
+
case 'tool-calls': return new Error('增强模型意外请求了工具');
|
|
61
|
+
default: return new Error(`不支持的结束原因 "${String(finish.kind)}"`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Read an optional pinned model route from the plugin row's `config`.
|
|
67
|
+
*
|
|
68
|
+
* The enhancement call is generation-bound: a large session model can take
|
|
69
|
+
* tens of seconds, so a deployment may pin a fast model for this auxiliary
|
|
70
|
+
* call regardless of the session's current model. Both fields must be
|
|
71
|
+
* non-empty to take effect; otherwise the session route is used.
|
|
72
|
+
* @returns `{provider, model}` for a complete pin, or `undefined`.
|
|
73
|
+
*/
|
|
74
|
+
export function resolvePinnedRoute(config) {
|
|
75
|
+
const provider = config?.provider;
|
|
76
|
+
const model = config?.model;
|
|
77
|
+
if (typeof provider !== 'string' || provider.trim() === '') return undefined;
|
|
78
|
+
if (typeof model !== 'string' || model.trim() === '') return undefined;
|
|
79
|
+
return { provider: provider.trim(), model: model.trim() };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Resolve the model route for the enhancement call, mirroring the Session
|
|
84
|
+
* Controller's `selectionFor(agent).current` chain.
|
|
85
|
+
* @returns `{provider, model}`, or `undefined` when nothing resolves.
|
|
86
|
+
*/
|
|
87
|
+
export function resolveRoute(ctx, agent) {
|
|
88
|
+
const state = ctx.sessionProjections?.stateOf?.(agent.session, 'modelSelection');
|
|
89
|
+
const pending = state?.pending ?? undefined;
|
|
90
|
+
if (pending?.provider !== undefined && pending?.model !== undefined) {
|
|
91
|
+
return { provider: pending.provider, model: pending.model };
|
|
92
|
+
}
|
|
93
|
+
const header = agent.session.requestHeader?.();
|
|
94
|
+
if (header?.config?.provider !== undefined && header?.config?.model !== undefined) {
|
|
95
|
+
return { provider: header.config.provider, model: header.config.model };
|
|
96
|
+
}
|
|
97
|
+
const fallback = ctx.agentDefaultModel?.currentSelection?.();
|
|
98
|
+
if (fallback?.provider !== undefined && fallback?.model !== undefined) {
|
|
99
|
+
return { provider: fallback.provider, model: fallback.model };
|
|
100
|
+
}
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
package/icon.svg
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
|
|
2
|
+
<path fill="#4176E6" d="M8 1.5l1.6 4.4L14 7.5l-4.4 1.6L8 13.5 6.4 9.1 2 7.5l4.4-1.6z"/>
|
|
3
|
+
<path fill="#4176E6" opacity=".7" d="M12.8 10.2l.6 1.6 1.6.6-1.6.6-.6 1.6-.6-1.6-1.6-.6 1.6-.6z"/>
|
|
4
|
+
</svg>
|
package/index.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-prompt-enhancer — host half.
|
|
3
|
+
*
|
|
4
|
+
* Registers the `/prompt-enhance` command: one auxiliary model call that
|
|
5
|
+
* optimizes and structures the supplied draft, following the official
|
|
6
|
+
* session-title-llm auxiliary-call pattern. The browser half calls it
|
|
7
|
+
* through `ctx.remote.commands.execute`; the command is the only sanctioned
|
|
8
|
+
* Client-to-Host operation path for a plain-JS bundle.
|
|
9
|
+
*/
|
|
10
|
+
import { createUserMessage, BlockAssembler } from '@deepseek-ai/dsh-llm';
|
|
11
|
+
import {
|
|
12
|
+
SYSTEM_PROMPT,
|
|
13
|
+
MAX_INPUT_BYTES,
|
|
14
|
+
MAX_OUTPUT_TOKENS,
|
|
15
|
+
TIMEOUT_MS,
|
|
16
|
+
ENHANCE_SOURCE_KIND,
|
|
17
|
+
trimDraft,
|
|
18
|
+
normalizeEnhancedText,
|
|
19
|
+
finishError,
|
|
20
|
+
resolveRoute,
|
|
21
|
+
resolvePinnedRoute,
|
|
22
|
+
} from './enhance-core.js';
|
|
23
|
+
|
|
24
|
+
export const inject = ['llm', 'commands', 'sessionProjections', 'agentDefaultModel'];
|
|
25
|
+
|
|
26
|
+
export function apply(ctx, config) {
|
|
27
|
+
// Optional pinned route from the plugin row's config: the enhancement call is
|
|
28
|
+
// generation-bound, so a deployment may point it at a fast model instead of
|
|
29
|
+
// the session's current one.
|
|
30
|
+
const pinned = resolvePinnedRoute(config);
|
|
31
|
+
ctx.effect(() => ctx.commands.register({
|
|
32
|
+
name: 'prompt-enhance',
|
|
33
|
+
description: '优化并结构化一段提示词文本',
|
|
34
|
+
input: { hint: '<提示词>' },
|
|
35
|
+
recordInput: false,
|
|
36
|
+
async handler({ agent, rawInput, signal }) {
|
|
37
|
+
const draft = trimDraft(rawInput);
|
|
38
|
+
if (draft === '') return { kind: 'error', text: '输入为空:没有需要增强的提示词' };
|
|
39
|
+
const inputBytes = Buffer.byteLength(draft, 'utf8');
|
|
40
|
+
if (inputBytes > MAX_INPUT_BYTES) {
|
|
41
|
+
return { kind: 'error', text: `输入过长(${inputBytes} 字节,上限 ${MAX_INPUT_BYTES} 字节)` };
|
|
42
|
+
}
|
|
43
|
+
const route = pinned ?? resolveRoute(ctx, agent);
|
|
44
|
+
if (route === undefined) {
|
|
45
|
+
return { kind: 'error', text: '无法确定增强所用模型:请先在会话中选择一个模型' };
|
|
46
|
+
}
|
|
47
|
+
const messages = [createUserMessage({
|
|
48
|
+
content: [{ type: 'text', text: draft }],
|
|
49
|
+
source: { kind: ENHANCE_SOURCE_KIND },
|
|
50
|
+
})];
|
|
51
|
+
const assembler = new BlockAssembler();
|
|
52
|
+
try {
|
|
53
|
+
for await (const chunk of ctx.llm.stream({
|
|
54
|
+
provider: route.provider,
|
|
55
|
+
model: route.model,
|
|
56
|
+
messages,
|
|
57
|
+
system: SYSTEM_PROMPT,
|
|
58
|
+
maxTokens: MAX_OUTPUT_TOKENS,
|
|
59
|
+
sessionId: agent.session.id,
|
|
60
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(TIMEOUT_MS)]),
|
|
61
|
+
})) {
|
|
62
|
+
assembler.push(chunk);
|
|
63
|
+
}
|
|
64
|
+
} catch (error) {
|
|
65
|
+
return { kind: 'error', text: `增强失败:${error?.message ?? String(error)}` };
|
|
66
|
+
}
|
|
67
|
+
const terminal = finishError(assembler.finish);
|
|
68
|
+
if (terminal !== undefined) {
|
|
69
|
+
return { kind: 'error', text: `增强中断:${terminal.message}` };
|
|
70
|
+
}
|
|
71
|
+
const text = normalizeEnhancedText(
|
|
72
|
+
assembler.blocks()
|
|
73
|
+
.filter((block) => block.type === 'text')
|
|
74
|
+
.map((block) => block.text)
|
|
75
|
+
.join(''),
|
|
76
|
+
);
|
|
77
|
+
if (text === '') return { kind: 'error', text: '模型未返回增强结果' };
|
|
78
|
+
return { kind: 'success', text };
|
|
79
|
+
},
|
|
80
|
+
}), 'prompt-enhancer: command');
|
|
81
|
+
}
|
package/locale/en.json
ADDED
package/locale/zh.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,48 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jianyiagi/dsh-prompt-enhancer",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "One-click prompt enhancement for the DSH composer: 输入框一键优化提示词并写回草稿",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"icon": "./icon.svg",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./index.js",
|
|
9
|
+
"./client": "./client.js",
|
|
10
|
+
"./package.json": "./package.json",
|
|
11
|
+
"./locale/*.json": "./locale/*.json"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"index.js",
|
|
15
|
+
"enhance-core.js",
|
|
16
|
+
"client.js",
|
|
17
|
+
"cordis.patch.yml",
|
|
18
|
+
"icon.svg",
|
|
19
|
+
"locale",
|
|
20
|
+
"README.md",
|
|
21
|
+
"LICENSE"
|
|
22
|
+
],
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"@deepseek-ai/dsh-llm": ">=0.2.0-rc.1 <0.4.0"
|
|
25
|
+
},
|
|
26
|
+
"dsh": {
|
|
27
|
+
"bundle": {
|
|
28
|
+
"patch": "./cordis.patch.yml"
|
|
29
|
+
},
|
|
30
|
+
"client": {
|
|
31
|
+
"platform": "web",
|
|
32
|
+
"immediately": true,
|
|
33
|
+
"inject": [
|
|
34
|
+
"@deepseek-ai/dsh-client-ui-conversation"
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"keywords": [
|
|
39
|
+
"dsh",
|
|
40
|
+
"deepseek-harness",
|
|
41
|
+
"plugin",
|
|
42
|
+
"prompt",
|
|
43
|
+
"prompt-engineering",
|
|
44
|
+
"composer"
|
|
45
|
+
],
|
|
46
|
+
"author": "jianyiagi",
|
|
47
|
+
"license": "MIT"
|
|
48
|
+
}
|