dsh-llm-workbuddy 0.1.6 → 0.1.7

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 (3) hide show
  1. package/README.md +11 -3
  2. package/lib/index.js +85 -21
  3. package/package.json +4 -4
package/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # dsh-llm-workbuddy
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/dsh-llm-workbuddy.svg)](https://www.npmjs.com/package/dsh-llm-workbuddy)
4
+ [![license](https://img.shields.io/npm/l/dsh-llm-workbuddy.svg)](https://github.com/zdk119746/dsh-llm-workbuddy/blob/main/LICENSE)
5
+
3
6
  在 DeepSeek Harness 中使用你的 **WorkBuddy / CodeBuddy** 账号模型的 LLM 适配器插件。
4
7
 
5
8
  它把 `workbuddy` 这个 provider 路由指向本地运行的
@@ -12,7 +15,8 @@
12
15
 
13
16
  1. **模型能力**:Web 界面的模型选择器(composer 模型菜单或 `/model` 命令)会多出
14
17
  一个 **WorkBuddy** 分组,模型(DeepSeek-V4、GLM-5.x、Kimi-K2.x、MiniMax-M3、
15
- Hy3、Hunyuan…)随账号可用列表实时同步,点一下即可切换。
18
+ Hy3、Hunyuan…)随账号可用列表实时同步,点一下即可切换;支持推理的模型还会
19
+ 显示**推理等级**选择器(低 / 中 / 高)。
16
20
  2. **Web 登录状态小组件**:在 Web GUI 右下角常驻一个状态胶囊,**实时显示登录/
17
21
  代理状态**,未登录时一键在新标签页打开 WorkBuddy 登录页,登录完成后自动变绿。
18
22
  无需再回到终端手动跑登录脚本。
@@ -269,13 +273,15 @@ DSH 的 `dsh.client` 机制只要求 `package.json` 里:
269
273
  # defaultContextWindow: 200000 # 未在目录中标明容量的模型使用
270
274
  # discovery: true # 实时拉取代理的 /v1/models(30s 缓存)
271
275
  # models: [...] # 静态目录(代理不可达时的兜底)
276
+ # 每个条目可带 reasoningEffort: 'low'|'medium'|'high'(默认推理等级)
272
277
  # loginScript: '' # 登录脚本绝对/相对路径;默认用包内 login_workbuddy.py
273
278
  # sessionFile: ~/.codebuddy-session.json # 会话文件路径;默认同上
274
279
  ```
275
280
 
276
281
  模型列表默认取插件内置目录;代理可达时改为实时拉取 `/v1/models`(支持
277
282
  `{"models": [...]}` / `{"data": [...]}` 两种返回),未列出的模型 id 仍可原样
278
- 传递。
283
+ 传递。每个模型的推理等级(`reasoningEffort`)默认来自内置目录,代理可达时优先
284
+ 沿用目录值(代理 `/v1/models` 当前只上报 `High`,不区分模型)。
279
285
 
280
286
  ---
281
287
 
@@ -297,7 +303,9 @@ DSH 的 `dsh.client` 机制只要求 `package.json` 里:
297
303
  ## 限制
298
304
 
299
305
  - 当前为纯文本适配器:图片输入会以 `UNSUPPORTED_CONTENT` 拒绝(后续可加)。
300
- - 不公布 reasoning effort 选择器;模型按平台默认强度运行。
306
+ - 推理等级(reasoning effort):支持推理的模型(如 DeepSeek-V4、GLM、Kimi、MiniMax、
307
+ Hy3 等)会显示推理等级下拉(低 / 中 / 高),默认值取平台默认强度。`reasoning_effort`
308
+ 会透传给代理;若某模型平台侧只接受平台默认、忽略该参数,则退化为平台默认强度,不影响出字。
301
309
  - 代理未运行时,模型请求会以 `TRANSPORT` 错误快速失败(连接被拒绝);但状态
302
310
  小组件本身不依赖代理——代理挂了它仍能显示「代理未运行」并允许触发登录。
303
311
  - 登录态有效期由 WorkBuddy 云端决定;过期后胶囊变红,重新点「登录」即可,
package/lib/index.js CHANGED
@@ -73,28 +73,77 @@ const DISCOVERY_TIMEOUT_MS = 2_000;
73
73
  * proxy is reachable; entries not announced by the proxy are still listed.
74
74
  */
75
75
  const DEFAULT_MODELS = [
76
- { id: "deepseek-v4-pro", name: "Deepseek-V4-Pro", contextWindow: 1_000_000, maxTokens: 50_000, inputModalities: ["text", "image"] },
77
- { id: "deepseek-v4-flash", name: "Deepseek-V4-Flash", contextWindow: 1_000_000, maxTokens: 50_000, inputModalities: ["text", "image"] },
78
- { id: "glm-5.2", name: "GLM-5.2", contextWindow: 1_000_000, maxTokens: 48_000, inputModalities: ["text", "image"] },
79
- { id: "glm-5.1", name: "GLM-5.1", contextWindow: 200_000, maxTokens: 48_000 },
80
- { id: "glm-5.0", name: "GLM-5.0", contextWindow: 200_000, maxTokens: 48_000 },
81
- { id: "glm-4.7", name: "GLM-4.7", contextWindow: 200_000, maxTokens: 48_000 },
82
- { id: "glm-4.6", name: "GLM-4.6", contextWindow: 168_000, maxTokens: 32_000 },
83
- { id: "glm-5v-turbo", name: "GLM-5v-Turbo", contextWindow: 200_000, maxTokens: 64_000, inputModalities: ["text", "image"] },
84
- { id: "kimi-k3-1", name: "Kimi-K3", contextWindow: 1_000_000, maxTokens: 32_000, inputModalities: ["text", "image"] },
85
- { id: "kimi-k2.7", name: "Kimi-K2.7-Code", contextWindow: 256_000, maxTokens: 32_000, inputModalities: ["text", "image"] },
86
- { id: "kimi-k2.6", name: "Kimi-K2.6", contextWindow: 256_000, maxTokens: 32_000, inputModalities: ["text", "image"] },
87
- { id: "kimi-k2.5", name: "Kimi-K2.5", contextWindow: 164_000, maxTokens: 32_000, inputModalities: ["text", "image"] },
88
- { id: "kimi-k2-thinking", name: "Kimi-K2-Thinking", contextWindow: 164_000, maxTokens: 32_000 },
89
- { id: "minimax-m3", name: "MiniMax-M3", contextWindow: 512_000, maxTokens: 128_000, inputModalities: ["text", "image"] },
90
- { id: "minimax-m2.5", name: "MiniMax-M2.5", contextWindow: 200_000, maxTokens: 48_000 },
91
- { id: "hy3", name: "Hy3", contextWindow: 192_000, maxTokens: 64_000, inputModalities: ["text", "image"] },
92
- { id: "hunyuan-2.0-thinking", name: "Hunyuan-2.0-Thinking", contextWindow: 128_000, maxTokens: 24_000 },
76
+ { id: "deepseek-v4-pro", name: "Deepseek-V4-Pro", contextWindow: 1_000_000, maxTokens: 50_000, inputModalities: ["text", "image"], reasoningEffort: "high" },
77
+ { id: "deepseek-v4-flash", name: "Deepseek-V4-Flash", contextWindow: 1_000_000, maxTokens: 50_000, inputModalities: ["text", "image"], reasoningEffort: "high" },
78
+ { id: "glm-5.2", name: "GLM-5.2", contextWindow: 1_000_000, maxTokens: 48_000, inputModalities: ["text", "image"], reasoningEffort: "medium" },
79
+ { id: "glm-5.1", name: "GLM-5.1", contextWindow: 200_000, maxTokens: 48_000, reasoningEffort: "medium" },
80
+ { id: "glm-5.0", name: "GLM-5.0", contextWindow: 200_000, maxTokens: 48_000, reasoningEffort: "high" },
81
+ { id: "glm-4.7", name: "GLM-4.7", contextWindow: 200_000, maxTokens: 48_000, reasoningEffort: "high" },
82
+ { id: "glm-4.6", name: "GLM-4.6", contextWindow: 168_000, maxTokens: 32_000, reasoningEffort: "high" },
83
+ { id: "glm-5v-turbo", name: "GLM-5v-Turbo", contextWindow: 200_000, maxTokens: 64_000, inputModalities: ["text", "image"], reasoningEffort: "medium" },
84
+ { id: "kimi-k3-1", name: "Kimi-K3", contextWindow: 1_000_000, maxTokens: 32_000, inputModalities: ["text", "image"], reasoningEffort: "medium" },
85
+ { id: "kimi-k2.7", name: "Kimi-K2.7-Code", contextWindow: 256_000, maxTokens: 32_000, inputModalities: ["text", "image"], reasoningEffort: "medium" },
86
+ { id: "kimi-k2.6", name: "Kimi-K2.6", contextWindow: 256_000, maxTokens: 32_000, inputModalities: ["text", "image"], reasoningEffort: "medium" },
87
+ { id: "kimi-k2.5", name: "Kimi-K2.5", contextWindow: 164_000, maxTokens: 32_000, inputModalities: ["text", "image"], reasoningEffort: "medium" },
88
+ { id: "kimi-k2-thinking", name: "Kimi-K2-Thinking", contextWindow: 164_000, maxTokens: 32_000, reasoningEffort: "medium" },
89
+ { id: "minimax-m3", name: "MiniMax-M3", contextWindow: 512_000, maxTokens: 128_000, inputModalities: ["text", "image"], reasoningEffort: "medium" },
90
+ { id: "minimax-m2.5", name: "MiniMax-M2.5", contextWindow: 200_000, maxTokens: 48_000, reasoningEffort: "medium" },
91
+ { id: "hy3", name: "Hy3", contextWindow: 192_000, maxTokens: 64_000, inputModalities: ["text", "image"], reasoningEffort: "high" },
92
+ { id: "hunyuan-2.0-thinking", name: "Hunyuan-2.0-Thinking", contextWindow: 128_000, maxTokens: 24_000, reasoningEffort: "medium" },
93
93
  { id: "hunyuan-chat", name: "Hunyuan-Turbos", contextWindow: 200_000, maxTokens: 8_192 },
94
- { id: "auto", name: "Auto", contextWindow: 168_000, maxTokens: 32_000, inputModalities: ["text", "image"] },
94
+ { id: "auto", name: "Auto", contextWindow: 168_000, maxTokens: 32_000, inputModalities: ["text", "image"], reasoningEffort: "high" },
95
95
  { id: "default", name: "Default", contextWindow: 200_000, maxTokens: 24_000 },
96
96
  ];
97
97
 
98
+ /** Selectable reasoning efforts exposed to the harness UI, in display order. */
99
+ const REASONING_EFFORTS = [
100
+ { id: "low", name: "低" },
101
+ { id: "medium", name: "中" },
102
+ { id: "high", name: "高" },
103
+ ];
104
+
105
+ /**
106
+ * Adapter-owned reasoning-effort identifiers accepted by the wire. The harness
107
+ * `ReasoningEffortId` is opaque; we keep the catalog default and the request
108
+ * param in this vocabulary and map to/from the proxy as needed.
109
+ */
110
+ const REASONING_EFFORT_IDS = new Set(["low", "medium", "high"]);
111
+
112
+ /**
113
+ * Build the `reasoning` capability metadata for a model, per the dsh-llm
114
+ * `LlmModelReasoningInfo` contract. `effort` is the model's platform default
115
+ * (`high`/`medium`/`low`); absent means the model exposes no selectable
116
+ * reasoning (the harness then hides the selector). When the platform does not
117
+ * support per-request switching, callers may still omit an explicit effort and
118
+ * the proxy uses its own default — publishing the default keeps the UI honest
119
+ * about the level actually applied.
120
+ * @returns the `reasoning` block, or `undefined` when the model has none.
121
+ */
122
+ function modelReasoningInfo(effort) {
123
+ if (effort === undefined || !REASONING_EFFORT_IDS.has(effort)) return undefined;
124
+ return {
125
+ efforts: REASONING_EFFORTS,
126
+ defaultEffort: effort,
127
+ };
128
+ }
129
+
130
+ /**
131
+ * Extract a model's default reasoning effort from a live `/v1/models` entry.
132
+ * The proxy reports `supported_reasoning_levels: [{ effort: 'High' }]` and
133
+ * `default_reasoning_level: null`, so we fall back to the catalog's
134
+ * `reasoningEffort` when the live answer is uninformative (as it currently is).
135
+ * @returns a lowercased effort id (`low`/`medium`/`high`) or `undefined`.
136
+ */
137
+ function liveReasoningEffort(entry, catalogEffort) {
138
+ if (catalogEffort !== undefined) return catalogEffort;
139
+ const levels = Array.isArray(entry?.supported_reasoning_levels) ? entry.supported_reasoning_levels : [];
140
+ const wire = levels[0]?.effort;
141
+ if (typeof wire !== "string") return undefined;
142
+ const lower = wire.toLowerCase();
143
+ return lower === "low" || lower === "medium" || lower === "high" ? lower : undefined;
144
+ }
145
+
146
+
98
147
  // #region serialize
99
148
 
100
149
  /** Join the text blocks of a message (used for user/tool-result content). */
@@ -164,6 +213,9 @@ function serializeRequest(options) {
164
213
  ...(options.temperature !== undefined ? { temperature: options.temperature } : {}),
165
214
  ...(options.maxTokens === undefined ? {} : { max_tokens: options.maxTokens }),
166
215
  ...(options.stop !== undefined ? { stop: options.stop } : {}),
216
+ // reasoning_effort is opaque here (low|medium|high); the proxy passes it
217
+ // through verbatim to the upstream /v2/chat/completions body.
218
+ ...(options.reasoningEffort === undefined ? {} : { reasoning_effort: options.reasoningEffort }),
167
219
  };
168
220
  }
169
221
 
@@ -363,12 +415,14 @@ async function* translate(payloads) {
363
415
 
364
416
  /** Display metadata for one catalog entry. */
365
417
  function modelInfo(provider, model) {
418
+ const reasoning = modelReasoningInfo(model.reasoningEffort);
366
419
  return {
367
420
  provider,
368
421
  id: model.id,
369
422
  name: model.name ?? model.id,
370
423
  ...(model.description === undefined ? {} : { description: model.description }),
371
424
  inputModalities: model.inputModalities ?? ["text"],
425
+ ...(reasoning === undefined ? {} : { reasoning }),
372
426
  };
373
427
  }
374
428
 
@@ -457,12 +511,15 @@ export class WorkBuddyAdapter extends LlmAdapter {
457
511
  if (seen.has(entry.id)) continue;
458
512
  seen.add(entry.id);
459
513
  const catalog = byId.get(entry.id);
514
+ const effort = liveReasoningEffort(entry, catalog?.reasoningEffort);
515
+ const reasoning = modelReasoningInfo(effort);
460
516
  merged.push({
461
517
  provider,
462
518
  id: entry.id,
463
519
  name: entry.name ?? catalog?.name ?? entry.id,
464
520
  ...(catalog?.description !== undefined ? { description: catalog.description } : {}),
465
521
  inputModalities: catalog?.inputModalities ?? ["text"],
522
+ ...(reasoning === undefined ? {} : { reasoning }),
466
523
  });
467
524
  }
468
525
  // Catalog entries the proxy did not announce (e.g. unauthenticated or
@@ -482,10 +539,11 @@ export class WorkBuddyAdapter extends LlmAdapter {
482
539
  resolveModel(provider, model, _signal) {
483
540
  const connection = this.config.options();
484
541
  const configured = connection.models.find((entry) => entry.id === model);
542
+ const base = configured === undefined
543
+ ? { provider, id: model, name: model, inputModalities: ["text"] }
544
+ : modelInfo(provider, configured);
485
545
  return Promise.resolve({
486
- ...(configured === undefined
487
- ? { provider, id: model, name: model, inputModalities: ["text"] }
488
- : modelInfo(provider, configured)),
546
+ ...base,
489
547
  context: { contextWindow: configured?.contextWindow ?? connection.defaultContextWindow },
490
548
  defaultMaxTokens: configured?.maxTokens ?? connection.maxTokens,
491
549
  });
@@ -586,6 +644,7 @@ const catalogModel = z.object({
586
644
  contextWindow: z.number().step(1).min(1),
587
645
  maxTokens: z.number().step(1).min(1),
588
646
  inputModalities: z.array(z.union(MODEL_MODALITIES)).min(1).default(["text"]),
647
+ reasoningEffort: z.string(),
589
648
  });
590
649
 
591
650
  export const Config = z.object({
@@ -625,6 +684,10 @@ function resolveModels(models) {
625
684
  }
626
685
  if (seen.has(model.id)) throw new Error(`dsh-llm-workbuddy: duplicate catalog model "${model.id}"`);
627
686
  seen.add(model.id);
687
+ const reasoningEffort = model.reasoningEffort;
688
+ if (reasoningEffort !== undefined && !REASONING_EFFORT_IDS.has(reasoningEffort)) {
689
+ throw new Error(`dsh-llm-workbuddy: catalog model "${model.id}" reasoningEffort must be one of low|medium|high`);
690
+ }
628
691
  return {
629
692
  id: model.id,
630
693
  ...(model.name === undefined ? {} : { name: model.name }),
@@ -632,6 +695,7 @@ function resolveModels(models) {
632
695
  ...(model.contextWindow === undefined ? {} : { contextWindow: model.contextWindow }),
633
696
  ...(model.maxTokens === undefined ? {} : { maxTokens: model.maxTokens }),
634
697
  inputModalities: [...inputModalities],
698
+ ...(reasoningEffort === undefined ? {} : { reasoningEffort }),
635
699
  };
636
700
  });
637
701
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-llm-workbuddy",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "WorkBuddy (via the local workbuddy2api proxy) LLM provider adapter for DeepSeek Harness, with a Web login-status widget",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -48,9 +48,9 @@
48
48
  ],
49
49
  "peerDependencies": {
50
50
  "@deepseek-ai/cordis": "^4.0.1",
51
- "@deepseek-ai/dsh-llm": "^0.1.0-rc.7",
52
- "@deepseek-ai/dsh-settings": "^0.1.0-rc.7",
53
- "@deepseek-ai/dsh-timeout": "^0.1.0-rc.7",
51
+ "@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
52
+ "@deepseek-ai/dsh-settings": "^0.1.1-rc.2",
53
+ "@deepseek-ai/dsh-timeout": "^0.1.1-rc.2",
54
54
  "@deepseek-ai/schemastery": "^3.18.1"
55
55
  }
56
56
  }