dsh-plugin-model-tuning 2.1.0 → 3.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.
Files changed (5) hide show
  1. package/README.md +83 -234
  2. package/client.js +425 -1013
  3. package/cordis.patch.yml +10 -19
  4. package/index.js +20 -263
  5. package/package.json +5 -11
package/cordis.patch.yml CHANGED
@@ -1,24 +1,15 @@
1
- # dsh-plugin-model-tuning v2 — 自 wiring + 显式替换宿主自带的标题生成行。
1
+ # dsh-plugin-model-tuning v3 self-wiring (dsh.bundle.patch mechanism).
2
2
  #
3
- # 顺序即语义:
4
- # 1. 先 disable dsh-base 的 `session-title-llm` 行(@deepseek-ai/dsh-session-title-first-prompt-llm)。
5
- # `ctx.sessionTitle.register()` 只允许一个 provider,第二次注册直接抛错,
6
- # 所以必须先摘掉原行再插入本行;反过来 boot 会失败。
7
- # 2. 再 insert 本包(v2 起一个包承担:模型参数页 + 标题路由 + 绘图工具/面板)。
3
+ # v3 keeps only the drawing capability: the `generate_image` /
4
+ # `list_image_models` tools plus the `imageGeneration` Remote service behind the
5
+ # settings page. The per-model compatibility switches on the official Models
6
+ # page and the drawing page need no extra rows.
8
7
  #
9
- # config 中标题预算字段与 dsh-base 的 `session-title-llm` 完全一致,因此无论
10
- # 设置处于默认「继承」还是被卸载回退,标题生成的提示词预算、字节上限、
11
- # 超时行为都不变。绘图 models/defaultModel 与标题 titleMode/titleProvider/
12
- # titleModel 是 volatile 字段,热更新即时生效。
13
- - id: session-title-llm
14
- disabled: true
15
-
8
+ # The v2 patch disabled the shipped `session-title-llm` row and inserted this
9
+ # bundle as a replacement title provider. That is gone: the shipped row already
10
+ # supports a provider/model route plus budgets in its Config, so title routing
11
+ # is configured again in the profile's cordis.patch.yml (and the official
12
+ # "标题随对话更新" optional bundle can be used normally).
16
13
  - insert:
17
14
  - id: model-tuning
18
15
  name: dsh-plugin-model-tuning
19
- config:
20
- targetWords: 5
21
- targetCjkCharacters: 10
22
- maxInputBytes: 4096
23
- maxOutputTokens: 64
24
- timeoutMs: 60000
package/index.js CHANGED
@@ -1,34 +1,17 @@
1
1
  /**
2
- * Host half of `dsh-plugin-model-tuning` (v2: merged with the former
3
- * `dsh-plugin-title-model` and `dsh-plugin-image-generation`).
2
+ * Host half of `dsh-plugin-model-tuning` v3.
4
3
  *
5
- * Three responsibilities, one entry:
6
- *
7
- * 1. Session-title routing (ex dsh-plugin-title-model). This plugin REPLACES
8
- * the deployment's shipped title provider row
9
- * (`@deepseek-ai/dsh-session-title-first-prompt-llm`) through the bundle
10
- * patch in `cordis.patch.yml`. It is still exactly one provider:
11
- * `ctx.sessionTitle.register()` throws on a second registration, so a
12
- * competing generator cannot coexist. Everything except the route is
13
- * delegated to the public shared policy (`executeSessionTitleLlm`, or
14
- * `generateSessionTitleWithLlm` before dsh 0.2.1) from
15
- * `@deepseek-ai/dsh-session-title-llm`, resolved from the RUNNING
16
- * deployment (see hostRequire) — never from this package's directory, which
17
- * for a linked workspace package would risk a second copy of the host's
18
- * shared module graph.
19
- *
20
- * 2. Image generation (ex dsh-plugin-image-generation): the `generate_image`
21
- * and `list_image_models` tools plus the `imageGeneration` Remote service
22
- * that the 直接绘图 panel drives. Transport lives in `./core.js`.
23
- *
24
- * 3. Nothing else: the settings surface (模型参数 / 用途 / 绘图 tabs) lives in
25
- * the browser implementation exported from `./client`.
4
+ * v3 keeps exactly one capability here: image generation (the former
5
+ * `dsh-plugin-image-generation`). Session-title routing was removed because the
6
+ * host's own `@deepseek-ai/dsh-session-title-first-prompt-llm` row already
7
+ * supports a provider/model route plus budgets in its Config; configure it in
8
+ * the profile's `cordis.patch.yml`. The per-model compatibility switches and
9
+ * the catalog-route reasoning override need no host half: the browser half
10
+ * writes the provider's settings namespace directly.
26
11
  *
27
12
  * @module dsh-plugin-model-tuning
28
13
  */
29
14
  import { randomUUID } from 'node:crypto'
30
- import { pathToFileURL } from 'node:url'
31
- import { createRequire } from 'node:module'
32
15
  import z from '@deepseek-ai/schemastery'
33
16
  import { defineTool } from '@deepseek-ai/dsh-tools'
34
17
  import { TypertRemoteService, RemoteError } from '@deepseek-ai/dsh-typert-protocol'
@@ -37,27 +20,12 @@ import { discoverImageModels, generateImages, resolveRequest, validateConfig } f
37
20
  export const name = 'dsh-plugin-model-tuning'
38
21
 
39
22
  /** Services this plugin reads. */
40
- export const inject = ['tools', 'attachments', 'sessionTitle', 'llm']
23
+ export const inject = ['tools', 'attachments']
41
24
 
42
- /** Settings namespace owned by this plugin (one entry, one schema). */
25
+ /** Settings namespace owned by this plugin. */
43
26
  export const NS = 'model-tuning'
44
27
 
45
- /**
46
- * Provider identity recorded with generated titles. Kept equal to the row this
47
- * plugin replaces so durable logs and the folded title source stay unchanged.
48
- */
49
- const TITLE_PROVIDER_ID = 'session-title-llm'
50
-
51
- /**
52
- * Unified Config: image-generation fields (models / defaultModel) plus the
53
- * title route fields (titleMode / titleProvider / titleModel), all volatile.
54
- *
55
- * The title budget fields mirror the `session-title-llm` config in
56
- * `@deepseek-ai/dsh-base` so that removing this bundle's `config:` block
57
- * cannot change title behaviour. Title fields are prefixed `title` so the
58
- * pre-merge `title-model` section could be migrated mechanically; the former
59
- * `image-generation` section's `models`/`defaultModel` keys are kept verbatim.
60
- */
28
+ /** One drawing model. `source` is legacy provenance kept so old linked entries still parse. */
61
29
  const imageModelSchema = z.object({
62
30
  id: z.string().required(), name: z.string(), model: z.string().required(),
63
31
  api: z.union(['openai-images']).default('openai-images'),
@@ -72,219 +40,16 @@ const imageModelSchema = z.object({
72
40
  output_format: z.string(), response_format: z.string(), style: z.string(),
73
41
  })
74
42
 
43
+ /**
44
+ * Config: drawing models only. Removed v2 title fields (`titleMode` etc.) are
45
+ * tolerated by the loader's schema and ignored, so an existing profile keeps
46
+ * loading without a migration step.
47
+ */
75
48
  export const Config = z.object({
76
- // ── 绘图(ex image-generation;键名原样保留,旧数据可直接搬入) ──
77
49
  models: z.array(imageModelSchema).default([]).volatile(),
78
50
  defaultModel: z.string().volatile(),
79
- // ── 标题生成(ex title-model;title 前缀迁移自 mode/provider/model) ──
80
- titleMode: z.union([z.const('inherit'), z.const('custom')]).default('inherit').volatile(),
81
- titleProvider: z.string().volatile(),
82
- titleModel: z.string().volatile(),
83
- // ── 标题请求预算(用户可在「用途」页改;默认值与 dsh-base 的 session-title-llm 行一致) ──
84
- targetWords: z.number().step(1).min(1).default(5).volatile(),
85
- targetCjkCharacters: z.number().step(1).min(1).default(10).volatile(),
86
- maxInputBytes: z.number().step(1).min(1).default(4096).volatile(),
87
- maxOutputTokens: z.number().step(1).min(1).default(64).volatile(),
88
- timeoutMs: z.number().step(1).min(1).default(60000).volatile(),
89
51
  })
90
52
 
91
- /** Reject a custom title mode that cannot name both halves of a route. */
92
- export function validateTitleModel(value) {
93
- const provider = value?.titleProvider
94
- const model = value?.titleModel
95
- const hasProvider = provider !== undefined && provider !== null && provider !== ''
96
- const hasModel = model !== undefined && model !== null && model !== ''
97
- if (hasProvider !== hasModel) throw new Error('model-tuning: titleProvider 与 titleModel 必须同时填写或同时留空')
98
- if (value?.titleMode === 'custom' && !hasProvider) throw new Error('model-tuning: titleMode 为 custom 时必须同时选择 provider 与 model')
99
- }
100
-
101
- /**
102
- * Resolve the exact route recorded for one generation request.
103
- * @param settings - resolved title fields (`titleMode`/`titleProvider`/`titleModel`).
104
- * @param request - service-owned request carrying the session's logged route.
105
- * @returns the provider/model pair to dispatch.
106
- */
107
- export function resolveTitleRoute(settings, request) {
108
- validateTitleModel(settings)
109
- if (settings?.titleMode === 'custom') {
110
- return { provider: settings.titleProvider, model: settings.titleModel }
111
- }
112
- if (request?.route === undefined) {
113
- throw new Error('session-title-llm: no logged request route is available; configure provider and model together')
114
- }
115
- return { provider: request.route.provider, model: request.route.model }
116
- }
117
-
118
- /** The shared title policy module, loaded once per process. */
119
- const HELPER_PACKAGE = '@deepseek-ai/dsh-session-title-llm'
120
-
121
- /**
122
- * Resolve the shared title policy.
123
- *
124
- * A plain `import '@deepseek-ai/dsh-session-title-llm'` would have to be
125
- * resolvable from THIS package's directory, which for a linked workspace package
126
- * means risking another installation of the host's shared module graph. The
127
- * helper therefore resolves from the deployment that loads this plugin.
128
- */
129
- function hostRequire() {
130
- const anchors = [process.argv[1], process.execPath, import.meta.url]
131
- for (const anchor of anchors) {
132
- if (anchor === undefined) continue
133
- try {
134
- const require = createRequire(anchor)
135
- require.resolve(HELPER_PACKAGE)
136
- return require
137
- } catch {
138
- // Try the next anchor: a test or tool process may not sit inside the deployment.
139
- }
140
- }
141
- return undefined
142
- }
143
-
144
- /** Title-service package that owns `normalizeSessionTitle`; resolved next to the helper. */
145
- const TITLE_SERVICE_PACKAGE = '@deepseek-ai/dsh-session-title'
146
-
147
- /**
148
- * Language-aware system instruction, mirroring the shipped first-prompt
149
- * provider (dsh ≥ 0.2.1 hands prompt ownership to each provider).
150
- * @param budget - the live title budget.
151
- * @returns the system prompt.
152
- */
153
- export function titleSystemPrompt(budget) {
154
- return [
155
- 'Create a concise title for an AI coding-assistant session from the supplied human messages.',
156
- 'Return only the title on one line, **in plain text of natural language**, with no quotes, prefix, explanation, Markdown, XML, or terminal control codes. No code is allowed.',
157
- 'Use the language of the messages.',
158
- `Aim for about ${budget.targetWords} words in non-CJK languages or ${budget.targetCjkCharacters} CJK characters.`,
159
- 'If the messages give little to name, still return a short best-effort title, such as Greeting, instead of explaining.',
160
- ].join('\n')
161
- }
162
-
163
- /** Asterisk emphasis wrapping a whole line, such as `**Title**`. */
164
- const EMPHASIS_WRAPPER = /^(?<marker>\*{1,3})(?<inner>\S(?:.*\S)?)\k<marker>$/u
165
-
166
- /**
167
- * Take the title from model output: the first non-empty line, without
168
- * asterisk emphasis wrapping that whole line (same rule as the shipped provider).
169
- * @param text - the joined text blocks.
170
- * @returns the raw title line.
171
- */
172
- export function titleFromOutput(text) {
173
- const line = text.split(/\r?\n/u).map(item => item.trim()).find(item => item.length > 0) ?? ''
174
- const groups = EMPHASIS_WRAPPER.exec(line)?.groups
175
- const marker = groups?.marker
176
- const inner = groups?.inner
177
- return marker === undefined || inner === undefined || inner.includes(marker) ? line : inner
178
- }
179
-
180
- /**
181
- * Adapt the prepared-request API (`executeSessionTitleLlm`, dsh ≥ 0.2.1) to the
182
- * one-call shape this plugin drives.
183
- * @param execute - the host's `executeSessionTitleLlm`.
184
- * @param normalize - the host's `normalizeSessionTitle`.
185
- * @returns `(ctx, budget, request, selected, providerId) => { title, messageSeqs, model }`.
186
- */
187
- export function preparedTitleGenerator(execute, normalize) {
188
- return async (ctx, budget, request, selected, providerId) => {
189
- const config = { maxInputBytes: budget.maxInputBytes, maxOutputTokens: budget.maxOutputTokens, timeoutMs: budget.timeoutMs }
190
- const messageSeqs = selected.map(message => message.seq)
191
- const response = await execute(ctx, config, request, providerId, {
192
- system: titleSystemPrompt(budget),
193
- input: `Generate the session title from this JSON array of human messages:\n${JSON.stringify(selected)}`,
194
- messageSeqs,
195
- // A title needs no deliberation: take the route's least effort.
196
- selectReasoningEffort: model => model.reasoning?.efforts?.[0]?.id,
197
- })
198
- if (response.finish.kind === 'tool-calls' || response.blocks.some(block => block.type === 'tool-call')) {
199
- throw new Error('session-title-llm: title output must contain text only')
200
- }
201
- if (response.finish.kind === 'max-tokens') {
202
- throw new Error('session-title-llm: title output reached maxOutputTokens')
203
- }
204
- const text = response.blocks.filter(block => block.type === 'text').map(block => block.text).join(' ')
205
- const title = normalize(titleFromOutput(text), Number.MAX_SAFE_INTEGER)
206
- if (title.length === 0) throw new Error('session-title-llm: title model produced no text')
207
- return { title, messageSeqs, model: response.model }
208
- }
209
- }
210
-
211
- let helperPromise
212
- /**
213
- * Load a title generator from the running deployment: the prepared-request
214
- * API when the host has it (≥ 0.2.1), else the one-call
215
- * `generateSessionTitleWithLlm` (0.1.7 – 0.2.0).
216
- */
217
- function loadTitleHelper() {
218
- helperPromise ??= (async () => {
219
- const require = hostRequire()
220
- if (require === undefined) {
221
- throw new Error(
222
- `model-tuning: 无法从宿主解析 ${HELPER_PACKAGE};本插件必须与提供该包的部署一起运行。`,
223
- )
224
- }
225
- const helperPath = require.resolve(HELPER_PACKAGE)
226
- const loaded = await import(pathToFileURL(helperPath).href)
227
- if (typeof loaded.executeSessionTitleLlm === 'function') {
228
- const service = await import(pathToFileURL(createRequire(helperPath).resolve(TITLE_SERVICE_PACKAGE)).href)
229
- if (typeof service.normalizeSessionTitle !== 'function') {
230
- throw new Error(`model-tuning: ${TITLE_SERVICE_PACKAGE} 未导出 normalizeSessionTitle`)
231
- }
232
- return preparedTitleGenerator(loaded.executeSessionTitleLlm, service.normalizeSessionTitle)
233
- }
234
- if (typeof loaded.generateSessionTitleWithLlm === 'function') return loaded.generateSessionTitleWithLlm
235
- throw new Error(`model-tuning: ${HELPER_PACKAGE} 既没有 executeSessionTitleLlm 也没有 generateSessionTitleWithLlm`)
236
- })()
237
- return helperPromise
238
- }
239
-
240
- /**
241
- * Read the live title budget.
242
- *
243
- * Every field is user-editable from the 用途 tab, so each generation re-reads
244
- * them instead of freezing the apply-time values. A reasoning model that only
245
- * starts answering after a long think needs a larger `maxOutputTokens` here;
246
- * that is the user's call, not a constant in this file.
247
- * @param config - the plugin's live Config.
248
- * @returns the prompt/byte/token/timeout policy for one title request.
249
- */
250
- export function titleBudgetOf(config) {
251
- const policy = {
252
- targetWords: config.targetWords.get(),
253
- targetCjkCharacters: config.targetCjkCharacters.get(),
254
- maxInputBytes: config.maxInputBytes.get(),
255
- maxOutputTokens: config.maxOutputTokens.get(),
256
- timeoutMs: config.timeoutMs.get(),
257
- }
258
- for (const [key, value] of Object.entries(policy)) {
259
- if (!Number.isInteger(value) || value < 1) throw new Error(`model-tuning: ${key} 必须是正整数`)
260
- }
261
- return policy
262
- }
263
-
264
- /**
265
- * Register the settings-owned title provider.
266
- * @param ctx - context exposing session-title and LLM services.
267
- * @param config - the live Config (route and budget are re-read per generation).
268
- */
269
- export async function registerTitleProvider(ctx, config) {
270
- const generateTitle = await loadTitleHelper()
271
- validateTitleModel({ titleMode: config.titleMode.get(), titleProvider: config.titleProvider.get(), titleModel: config.titleModel.get() })
272
- titleBudgetOf(config)
273
- ctx.sessionTitle.register({
274
- id: TITLE_PROVIDER_ID,
275
- automatic: 'first-prompt',
276
- async generate(request) {
277
- const route = resolveTitleRoute({
278
- titleMode: config.titleMode.get(), titleProvider: config.titleProvider.get(), titleModel: config.titleModel.get(),
279
- }, request)
280
- const selected = request.messages.slice(0, 1)
281
- return await generateTitle(ctx, titleBudgetOf(config), { ...request, route }, selected, TITLE_PROVIDER_ID)
282
- },
283
- })
284
- }
285
-
286
- // ── 绘图:工具 + Remote 服务(ex dsh-plugin-image-generation/index.js) ──
287
-
288
53
  const IMAGE_PARAMETERS = {
289
54
  model: { type: 'string', description: 'Configuration ID from list_image_models, not the upstream model ID. Omit to use the configured default.' },
290
55
  prompt: { type: 'string', required: true, description: 'Full self-contained description of the image to generate. Chat history is not sent.' },
@@ -434,18 +199,10 @@ export function registerImageTools(ctx, config) {
434
199
  }
435
200
 
436
201
  /**
437
- * Register everything: title provider, image tools, and the Remote service.
438
- * @param ctx - context exposing tools, attachments, sessionTitle and llm.
439
- * @param config - the unified Config described above.
202
+ * Register the drawing tools and their Remote service.
203
+ * @param ctx - context exposing tools, attachments and llm.
204
+ * @param config - the drawing Config.
440
205
  */
441
- export async function apply(ctx, config) {
442
- // The title route leans on a host helper whose API has changed between
443
- // releases. If it breaks again, lose only title routing — never the image
444
- // tools and settings page that ride on the same row.
445
- try {
446
- await registerTitleProvider(ctx, config)
447
- } catch (error) {
448
- ctx.logger?.warn?.(`model-tuning: 标题路由未启用(${error instanceof Error ? error.message : String(error)});绘图与设置页照常工作`)
449
- }
206
+ export function apply(ctx, config) {
450
207
  registerImageTools(ctx, config)
451
208
  }
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "dsh-plugin-model-tuning",
3
- "version": "2.1.0",
3
+ "version": "3.0.0",
4
4
  "license": "MIT",
5
- "keywords": ["dsh-plugin", "deepseek-harness", "dsh", "model", "settings", "reasoning", "context", "session-title", "image-generation"],
5
+ "keywords": ["dsh-plugin", "deepseek-harness", "dsh", "model", "settings", "reasoning", "compat", "image-generation"],
6
6
  "publishConfig": { "registry": "https://registry.npmjs.org" },
7
7
  "repository": { "type": "git", "url": "git+https://github.com/tofu-zyc/dsh-plugin-tofu-box.git", "directory": "dsh-plugin-model-tuning" },
8
8
  "bugs": { "url": "https://github.com/tofu-zyc/dsh-plugin-tofu-box/issues" },
9
9
  "homepage": "https://github.com/tofu-zyc/dsh-plugin-tofu-box/tree/main/dsh-plugin-model-tuning",
10
- "description": "「设置 → 模型调参」页:模型参数(推理档位/上下文/最大输出/输入模态)、「用途」页(标题生成独立路由,替换宿主标题行)、「绘图」页(绘图模型配置 + generate_image 工具 + 直接绘图面板)。v2 合并了原 dsh-plugin-title-model 与 dsh-plugin-image-generation。",
10
+ "description": "只补官方没有的两块:①「设置 → 模型」卡片里的兼容开关与内置目录路由推理覆盖(官方 Models 页没有的 compat.supportsDeveloperRole / compat.forceAdaptiveThinking / modelOverrides.reasoningEfforts);②「设置 → 绘图」页(绘图模型配置 + generate_image / list_image_models 工具 + 直接绘图面板)。v3 起标题生成路由交还官方 session-title 行,模型参数页交还官方 Models 页。",
11
11
  "type": "module",
12
12
  "main": "index.js",
13
13
  "exports": {
@@ -24,7 +24,7 @@
24
24
  "platform": "web",
25
25
  "inject": [
26
26
  "@deepseek-ai/dsh-client-ui-settings",
27
- "@deepseek-ai/dsh-client-ui-tool",
27
+ "@deepseek-ai/dsh-client-ui-settings-models",
28
28
  "@deepseek-ai/dsh-api-remotes"
29
29
  ]
30
30
  }
@@ -32,13 +32,7 @@
32
32
  "peerDependencies": {
33
33
  "@deepseek-ai/dsh-tools": ">=0.1.6-alpha.1",
34
34
  "@deepseek-ai/dsh-typert-protocol": ">=0.1.6-alpha.1",
35
- "@deepseek-ai/schemastery": "^3.18.2",
36
- "@deepseek-ai/dsh-session-title-llm": ">=0.1.6-alpha.2"
37
- },
38
- "peerDependenciesMeta": {
39
- "@deepseek-ai/dsh-session-title-llm": {
40
- "optional": true
41
- }
35
+ "@deepseek-ai/schemastery": "^3.18.2"
42
36
  },
43
37
  "scripts": {
44
38
  "test": "node --test tests/*.test.mjs"