dsh-llm-workbuddy 0.1.14 → 0.1.16
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 +51 -0
- package/README.md +17 -4
- package/lib/index.js +138 -26
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,55 @@
|
|
|
4
4
|
每次发版请同步 `package.json` 的 `version` 并打一个 `git tag`(如 `v0.1.13`),
|
|
5
5
|
在 GitHub 创建 Release 时本文件即为更新说明来源。
|
|
6
6
|
|
|
7
|
+
## [0.1.16] - 2026-09-10
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- **适配 DeepSeek Harness `0.1.5-rc.1`(此前 0.1.15 在该版本上完全无法加载)**。
|
|
11
|
+
0.1.5-rc.1 移动了两个导出符号,而具名导入一个已不存在的符号会触发 ESM
|
|
12
|
+
**加载期 `SyntaxError`**,整个插件在 `import` 阶段即崩溃(provider、状态胶囊、
|
|
13
|
+
用量面板全部消失),不是运行期告警。现改为经命名空间对象取值并带兜底:
|
|
14
|
+
- `@deepseek-ai/dsh-llm`:`CallId` 在 0.1.5-rc.1 改名为 `ToolCallId`。
|
|
15
|
+
- `@deepseek-ai/dsh-settings`:`settingsNamespace` 已移除(改由注册内部
|
|
16
|
+
`parseSettingsNamespace` 校验,规则同为 `/^[a-z][a-z0-9-]*$/`),
|
|
17
|
+
`installSettingsSection` 改为 settings 服务的 `installSection` 方法。
|
|
18
|
+
- `settings` 分区改走 `ctx.inject(["settings"], (c) => c.settings.installSection(...))`,
|
|
19
|
+
与官方 `dsh-llm-deepseek` / `dsh-llm-pi-ai` 在 0.1.5-rc.1 上的写法一致。
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- `peerDependencies` 放宽为 `^0.1.1-rc.2 || ^0.1.5-rc.1`(`dsh-llm` /
|
|
23
|
+
`dsh-settings` / `dsh-timeout`),`@deepseek-ai/cordis` 放宽为 `^4.0.1 || ^4.0.2`,
|
|
24
|
+
以显式声明对 0.1.5-rc.1 的支持。修复为运行时取值,**同一个包在
|
|
25
|
+
0.1.1-rc.2 与 0.1.5-rc.1 上均可加载**。
|
|
26
|
+
|
|
27
|
+
## [0.1.15] - 2026-09-10
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
- **刷新模型看不到新上架模型**:内置目录此前同时充当白名单,代理
|
|
31
|
+
`/v1/models` 里凡不在目录中的模型一律被丢弃,所以平台新上架的模型
|
|
32
|
+
(如 `deepseek-v4.1-flash`)刷新多少次都不会出现。现改为**黑名单**
|
|
33
|
+
`RETIRED_MODEL_IDS`:只隐藏实测不可用(上游 `service info not found`)的
|
|
34
|
+
旧模型 id,其余照单显示。顺带放出 7 个别名仍在服务、却被旧白名单误藏的模型
|
|
35
|
+
(`minimax-m2.7`、`glm-5.0-turbo`、`hy3-preview`、`deepseek-v3-1-lkeap`、
|
|
36
|
+
`deepseek-v3-0324-lkeap`、`deepseek-r1-0528-lkeap`、`hunyuan-2.0-instruct`)。
|
|
37
|
+
- 内置目录新增 `deepseek-v4.1-flash`,并在代理不可达时作为兜底条目。
|
|
38
|
+
- 🔍 诊断的「模型出字」误报修复:探针用 `hy3` + `max_tokens: 4` 发一次请求,
|
|
39
|
+
而推理模型会把这 4 个 token 全花在思考上、`content` 返回空串,于是
|
|
40
|
+
"代理正常"被误判成「代理返回了空响应」。现承认"有计费 completion token +
|
|
41
|
+
有 choice"即为正常。
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
- **模型名后显示积分消耗倍率**:读取代理 `/v1/models` 的 `credits` 字段
|
|
45
|
+
(`"x0.06 credits"`)并拼成 `Deepseek-V4-Flash ×0.06`;实时值优先,内置目录
|
|
46
|
+
`credits` 兜底。平台未声明倍率的模型只显示模型名——不会用 0 或猜测值冒充。
|
|
47
|
+
- 内置目录补齐各模型 `credits`(取自 WorkBuddy 应用 `product.json`)。
|
|
48
|
+
|
|
49
|
+
### Changed(代理侧,`.workbuddy-src` / workbuddy2api)
|
|
50
|
+
- `/v1/models` 透出 `credits` 字段(`model_to_codex_format`),倍率不再由各客户端
|
|
51
|
+
各自维护。
|
|
52
|
+
- 官方应用 `product.json` 与本地 `models_config.json` 合并时倍率单独回填:官方
|
|
53
|
+
条目为空则沿用本地倍率,避免官方合并把倍率抹平。
|
|
54
|
+
- `models_config.json` 补充 `deepseek-v4.1-flash`(1M 上下文 / 50k 输出)。
|
|
55
|
+
|
|
7
56
|
## [0.1.14] - 2026-08-28
|
|
8
57
|
|
|
9
58
|
### Changed
|
|
@@ -50,6 +99,8 @@
|
|
|
50
99
|
---
|
|
51
100
|
|
|
52
101
|
<!-- 历史版本锚点(便于生成 Release 时对比区间) -->
|
|
102
|
+
[0.1.16]: https://github.com/zdk119746/dsh-llm-workbuddy/compare/v0.1.15...v0.1.16
|
|
103
|
+
[0.1.15]: https://github.com/zdk119746/dsh-llm-workbuddy/compare/v0.1.14...v0.1.15
|
|
53
104
|
[0.1.14]: https://github.com/zdk119746/dsh-llm-workbuddy/compare/v0.1.13...v0.1.14
|
|
54
105
|
[0.1.13]: https://github.com/zdk119746/dsh-llm-workbuddy/compare/v0.1.12...v0.1.13
|
|
55
106
|
[0.1.12]: https://github.com/zdk119746/dsh-llm-workbuddy/compare/v0.1.11...v0.1.12
|
package/README.md
CHANGED
|
@@ -284,16 +284,29 @@ DSH 的 `dsh.client` 机制只要求 `package.json` 里:
|
|
|
284
284
|
# discovery: true # 实时拉取代理的 /v1/models(30s 缓存)
|
|
285
285
|
# models: [...] # 静态目录(代理不可达时的兜底)
|
|
286
286
|
# 每个条目可带 reasoningEffort: 'low'|'medium'|'high'(默认推理等级)
|
|
287
|
+
# 以及 credits: 'x0.06 credits'(显示为模型名后的 ×0.06)
|
|
287
288
|
# loginScript: '' # 登录脚本绝对/相对路径;默认用包内 login_workbuddy.py
|
|
288
289
|
# sessionFile: ~/.codebuddy-session.json # 会话文件路径;默认同上
|
|
289
290
|
```
|
|
290
291
|
|
|
291
292
|
模型列表默认取插件内置目录;代理可达时改为实时拉取 `/v1/models`(支持
|
|
292
293
|
`{"models": [...]}` / `{"data": [...]}` 两种返回),未列出的模型 id 仍可原样
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
294
|
+
传递。**内置目录不再是白名单**:平台新上架的模型刷新后即出现在选择器里,
|
|
295
|
+
无需等插件发版;只有已下架/不可用的模型 id(`RETIRED_MODEL_IDS`,上游返回
|
|
296
|
+
`service info not found`)会被隐藏。每个模型的推理等级(`reasoningEffort`)
|
|
297
|
+
默认来自内置目录,代理可达时优先沿用目录值(代理 `/v1/models` 当前只上报
|
|
298
|
+
`High`,不区分模型)。
|
|
299
|
+
|
|
300
|
+
**积分倍率**:模型名后面会拼上平台声明的积分倍率,例如
|
|
301
|
+
`Deepseek-V4-Flash ×0.06`。倍率来自代理 `/v1/models` 的 `credits` 字段
|
|
302
|
+
(`"x0.06 credits"`,平台写在 WorkBuddy 应用 `product.json` /
|
|
303
|
+
`models_config.json` 里),实时值优先、内置目录兜底;平台没声明的模型
|
|
304
|
+
(如个别新模型尚未写入 `product.json`)只显示模型名,不会猜一个数字。
|
|
305
|
+
|
|
306
|
+
**刷新模型**:⚙️ 设置 → 🔄 刷新模型 会清掉插件的 30s 发现缓存并重新拉取
|
|
307
|
+
代理列表。注意代理自身的模型清单来自本机 WorkBuddy 应用的 `product.json`
|
|
308
|
+
与 `models_config.json`——两者都没有的模型,刷新也不会出现,需先更新应用或
|
|
309
|
+
把模型补进代理配置。
|
|
297
310
|
|
|
298
311
|
---
|
|
299
312
|
|
package/lib/index.js
CHANGED
|
@@ -23,7 +23,6 @@ import { fileURLToPath } from "node:url";
|
|
|
23
23
|
import z from "@deepseek-ai/schemastery";
|
|
24
24
|
import {
|
|
25
25
|
CONTEXT_WINDOW_EXCEEDED_CODE,
|
|
26
|
-
CallId,
|
|
27
26
|
EMPTY_RESPONSE_CODE,
|
|
28
27
|
LlmAdapter,
|
|
29
28
|
LlmError,
|
|
@@ -33,9 +32,50 @@ import {
|
|
|
33
32
|
isContextWindowExceededError,
|
|
34
33
|
isQuotaExceededError,
|
|
35
34
|
} from "@deepseek-ai/dsh-llm";
|
|
36
|
-
|
|
35
|
+
// Namespace imports for symbols that moved between harness releases. A named
|
|
36
|
+
// import of a symbol that no longer exists is an ESM load-time SyntaxError that
|
|
37
|
+
// takes the whole plugin down, so these resolve through the namespace object
|
|
38
|
+
// with a fallback instead.
|
|
39
|
+
import * as dshLlm from "@deepseek-ai/dsh-llm";
|
|
40
|
+
import * as dshSettings from "@deepseek-ai/dsh-settings";
|
|
37
41
|
import { MAX_TIMER_DELAY_MS, idleWatchdog, timeoutOf } from "@deepseek-ai/dsh-timeout";
|
|
38
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Brand a tool-call id. Harness 0.1.1-rc.2 exports `CallId`; 0.1.5-rc.1 renamed
|
|
45
|
+
* it to `ToolCallId`. Both brands are string-preserving, so the first one the
|
|
46
|
+
* loaded harness provides is correct on either release.
|
|
47
|
+
*/
|
|
48
|
+
const CallId = dshLlm.CallId ?? dshLlm.ToolCallId;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Validate a settings namespace. Harness 0.1.5-rc.1 stopped exporting
|
|
52
|
+
* `settingsNamespace` and validates inside registration instead; this mirrors
|
|
53
|
+
* the same lowercase-hyphenated grammar so a bad namespace still fails loudly.
|
|
54
|
+
*/
|
|
55
|
+
const settingsNamespace = dshSettings.settingsNamespace ?? ((value) => {
|
|
56
|
+
if (typeof value !== "string" || !/^[a-z][a-z0-9-]*$/.test(value)) {
|
|
57
|
+
throw new TypeError(`settings namespace "${value}" must match /^[a-z][a-z0-9-]*$/`);
|
|
58
|
+
}
|
|
59
|
+
return value;
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Wire this plugin's settings section to the optional settings service.
|
|
64
|
+
* Harness 0.1.1-rc.2 exports the free function `installSettingsSection`;
|
|
65
|
+
* 0.1.5-rc.1 replaced it with the `installSection` method on the settings
|
|
66
|
+
* service, reached through `ctx.inject(["settings"], …)`. Both register the
|
|
67
|
+
* plugin's composition config as the namespace base and fall back to it when
|
|
68
|
+
* the service is absent, so behaviour is identical either way.
|
|
69
|
+
*/
|
|
70
|
+
function installSettingsSection(ctx, ns, schema, entry, hooks) {
|
|
71
|
+
if (typeof dshSettings.installSettingsSection === "function") {
|
|
72
|
+
return dshSettings.installSettingsSection(ctx, ns, schema, entry, hooks);
|
|
73
|
+
}
|
|
74
|
+
ctx.inject(["settings"], (settingsCtx) => {
|
|
75
|
+
settingsCtx.settings.installSection(ctx, ns, schema, entry, hooks);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
39
79
|
/** Plugin identity (Cordis convention). */
|
|
40
80
|
export const name = "llm-workbuddy";
|
|
41
81
|
// `llm` is a hard requirement (the adapter must always register). `webServer`
|
|
@@ -77,26 +117,53 @@ const DISCOVERY_TIMEOUT_MS = 2_000;
|
|
|
77
117
|
* CodeBuddy/WorkBuddy platform (from workbuddy2api's models_config.json).
|
|
78
118
|
* It is advisory and replaced by the live `/v1/models` answer whenever the
|
|
79
119
|
* proxy is reachable; entries not announced by the proxy are still listed.
|
|
120
|
+
*
|
|
121
|
+
* `credits` is the platform's declared multiplier (`"x0.06 credits"`), shown
|
|
122
|
+
* after the model name. The live answer wins when it carries one — this copy
|
|
123
|
+
* only covers the proxy-down / discovery-off fallback.
|
|
80
124
|
*/
|
|
81
125
|
const DEFAULT_MODELS = [
|
|
82
|
-
{ id: "deepseek-v4-
|
|
83
|
-
{ id: "deepseek-v4-
|
|
84
|
-
{ id: "deepseek-
|
|
85
|
-
{ id: "
|
|
86
|
-
{ id: "glm-5.
|
|
87
|
-
{ id: "glm-
|
|
88
|
-
{ id: "
|
|
89
|
-
{ id: "kimi-
|
|
90
|
-
{ id: "kimi-k2.
|
|
91
|
-
{ id: "kimi-k2.
|
|
92
|
-
{ id: "
|
|
93
|
-
{ id: "
|
|
94
|
-
{ id: "
|
|
126
|
+
{ id: "deepseek-v4.1-flash", name: "Deepseek-V4.1-Flash", contextWindow: 1_000_000, maxTokens: 50_000, inputModalities: ["text", "image"], reasoningEffort: "high" },
|
|
127
|
+
{ id: "deepseek-v4-pro", name: "Deepseek-V4-Pro", contextWindow: 1_000_000, maxTokens: 50_000, inputModalities: ["text", "image"], reasoningEffort: "high", credits: "x0.16 credits" },
|
|
128
|
+
{ id: "deepseek-v4-flash", name: "Deepseek-V4-Flash", contextWindow: 1_000_000, maxTokens: 50_000, inputModalities: ["text", "image"], reasoningEffort: "high", credits: "x0.06 credits" },
|
|
129
|
+
{ id: "deepseek-v3-2-volc", name: "DeepSeek-V3.2", contextWindow: 96_000, maxTokens: 32_000, reasoningEffort: "medium", credits: "x0.29 credits" },
|
|
130
|
+
{ id: "glm-5.2", name: "GLM-5.2", contextWindow: 1_000_000, maxTokens: 48_000, inputModalities: ["text", "image"], reasoningEffort: "medium", credits: "x0.79 credits" },
|
|
131
|
+
{ id: "glm-5.1", name: "GLM-5.1", contextWindow: 200_000, maxTokens: 48_000, reasoningEffort: "medium", credits: "x0.79 credits" },
|
|
132
|
+
{ id: "glm-5v-turbo", name: "GLM-5v-Turbo", contextWindow: 200_000, maxTokens: 64_000, inputModalities: ["text", "image"], reasoningEffort: "medium", credits: "x0.95 credits" },
|
|
133
|
+
{ id: "kimi-k3-1", name: "Kimi-K3", contextWindow: 1_000_000, maxTokens: 32_000, inputModalities: ["text", "image"], reasoningEffort: "medium", credits: "x1.62 credits" },
|
|
134
|
+
{ id: "kimi-k2.7", name: "Kimi-K2.7-Code", contextWindow: 256_000, maxTokens: 32_000, inputModalities: ["text", "image"], reasoningEffort: "medium", credits: "x0.57 credits" },
|
|
135
|
+
{ id: "kimi-k2.6", name: "Kimi-K2.6", contextWindow: 256_000, maxTokens: 32_000, inputModalities: ["text", "image"], reasoningEffort: "medium", credits: "x0.52 credits" },
|
|
136
|
+
{ id: "kimi-k2.5", name: "Kimi-K2.5", contextWindow: 164_000, maxTokens: 32_000, inputModalities: ["text", "image"], reasoningEffort: "medium", credits: "x0.45 credits" },
|
|
137
|
+
{ id: "minimax-m3", name: "MiniMax-M3", contextWindow: 512_000, maxTokens: 128_000, inputModalities: ["text", "image"], reasoningEffort: "medium", credits: "x0.25 credits" },
|
|
138
|
+
{ id: "hy3", name: "Hy3", contextWindow: 192_000, maxTokens: 64_000, inputModalities: ["text", "image"], reasoningEffort: "high", credits: "x0.00 credits" },
|
|
139
|
+
{ id: "hunyuan-2.0-thinking", name: "Hunyuan-2.0-Thinking", contextWindow: 128_000, maxTokens: 24_000, reasoningEffort: "medium", credits: "x0.04 credits" },
|
|
95
140
|
{ id: "hunyuan-chat", name: "Hunyuan-Turbos", contextWindow: 200_000, maxTokens: 8_192 },
|
|
96
141
|
{ id: "auto", name: "Auto", contextWindow: 168_000, maxTokens: 32_000, inputModalities: ["text", "image"], reasoningEffort: "high" },
|
|
97
|
-
{ id: "default", name: "Default", contextWindow: 200_000, maxTokens: 24_000 },
|
|
142
|
+
{ id: "default", name: "Default", contextWindow: 200_000, maxTokens: 24_000, credits: "x2.00 credits" },
|
|
98
143
|
];
|
|
99
144
|
|
|
145
|
+
/**
|
|
146
|
+
* Models the proxy still announces but that the upstream platform rejects with
|
|
147
|
+
* `service info not found` (each id re-probed 2026-09-10 against the live
|
|
148
|
+
* endpoint). The catalog is no longer a whitelist — a model the platform
|
|
149
|
+
* launches shows up after a refresh without a plugin update — so dead ids are
|
|
150
|
+
* hidden here by name instead.
|
|
151
|
+
*/
|
|
152
|
+
const RETIRED_MODEL_IDS = new Set([
|
|
153
|
+
"minimax-m2.5",
|
|
154
|
+
"glm-5.0",
|
|
155
|
+
"glm-4.7",
|
|
156
|
+
"glm-4.6",
|
|
157
|
+
"glm-4.6v",
|
|
158
|
+
"kimi-k2-thinking",
|
|
159
|
+
"deepseek-v3-1",
|
|
160
|
+
"deepseek-v3-1-volc",
|
|
161
|
+
"kimi-k2-instruct-taiji",
|
|
162
|
+
"completion-gf",
|
|
163
|
+
"default-1.1",
|
|
164
|
+
"default-1.2",
|
|
165
|
+
]);
|
|
166
|
+
|
|
100
167
|
/** Selectable reasoning efforts exposed to the harness UI, in display order. */
|
|
101
168
|
const REASONING_EFFORTS = [
|
|
102
169
|
{ id: "low", name: "Low" },
|
|
@@ -156,6 +223,26 @@ function liveInputModalities(entry) {
|
|
|
156
223
|
return valid.length > 0 ? [...new Set(valid)] : ["text"];
|
|
157
224
|
}
|
|
158
225
|
|
|
226
|
+
/**
|
|
227
|
+
* Parse the platform's credit declaration into the `×0.06` suffix appended to
|
|
228
|
+
* a model name. The platform writes these as `"x0.06 credits"` (product.json /
|
|
229
|
+
* models_config.json), and the proxy relays the string verbatim; a bare number
|
|
230
|
+
* or an already-compact `×1.62` also parses. Anything without a recognizable
|
|
231
|
+
* number yields `undefined`, so callers omit the suffix rather than guess —
|
|
232
|
+
* an unknown multiplier must not be rendered as 0.
|
|
233
|
+
*/
|
|
234
|
+
function creditsMultiplier(value) {
|
|
235
|
+
if (typeof value !== "string") return undefined;
|
|
236
|
+
const match = /^[x×*]?\s*(\d+(?:\.\d+)?)\s*(?:credits?)?$/i.exec(value.trim());
|
|
237
|
+
return match === null ? undefined : `×${match[1]}`;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Append the credit multiplier to a model's display name, when declared. */
|
|
241
|
+
function withCredits(name, credits) {
|
|
242
|
+
const multiplier = creditsMultiplier(credits);
|
|
243
|
+
return multiplier === undefined ? name : `${name} ${multiplier}`;
|
|
244
|
+
}
|
|
245
|
+
|
|
159
246
|
|
|
160
247
|
// #region serialize
|
|
161
248
|
|
|
@@ -580,7 +667,7 @@ function modelInfo(provider, model) {
|
|
|
580
667
|
return {
|
|
581
668
|
provider,
|
|
582
669
|
id: model.id,
|
|
583
|
-
name: model.name ?? model.id,
|
|
670
|
+
name: withCredits(model.name ?? model.id, model.credits),
|
|
584
671
|
...(model.description === undefined ? {} : { description: model.description }),
|
|
585
672
|
inputModalities: model.inputModalities ?? ["text"],
|
|
586
673
|
...(reasoning === undefined ? {} : { reasoning }),
|
|
@@ -673,6 +760,10 @@ export class WorkBuddyAdapter extends LlmAdapter {
|
|
|
673
760
|
description: typeof entry.description === "string" && entry.description.length > 0
|
|
674
761
|
? entry.description
|
|
675
762
|
: undefined,
|
|
763
|
+
// Platform-declared credit multiplier, relayed by the proxy.
|
|
764
|
+
credits: typeof entry.credits === "string" && entry.credits.trim().length > 0
|
|
765
|
+
? entry.credits.trim()
|
|
766
|
+
: undefined,
|
|
676
767
|
modalities: liveInputModalities(entry),
|
|
677
768
|
contextWindow: Number.isInteger(entry.context_window) && entry.context_window > 0
|
|
678
769
|
? entry.context_window
|
|
@@ -689,19 +780,23 @@ export class WorkBuddyAdapter extends LlmAdapter {
|
|
|
689
780
|
if (seen.has(entry.id)) continue;
|
|
690
781
|
seen.add(entry.id);
|
|
691
782
|
const catalog = byId.get(entry.id);
|
|
692
|
-
//
|
|
693
|
-
//
|
|
694
|
-
//
|
|
695
|
-
//
|
|
696
|
-
//
|
|
697
|
-
|
|
698
|
-
if (catalog === undefined) continue;
|
|
783
|
+
// Retired ids are hidden by name (RETIRED_MODEL_IDS) rather than by
|
|
784
|
+
// absence from the catalog: the proxy still announces models upstream
|
|
785
|
+
// rejects, but a model the platform launches must appear after a
|
|
786
|
+
// refresh without waiting for a new plugin release. The catalog is now
|
|
787
|
+
// only a metadata overlay plus a fallback for unannounced entries.
|
|
788
|
+
if (RETIRED_MODEL_IDS.has(entry.id)) continue;
|
|
699
789
|
const effort = liveReasoningEffort(entry, catalog?.reasoningEffort);
|
|
700
790
|
const reasoning = modelReasoningInfo(effort);
|
|
701
791
|
merged.push({
|
|
702
792
|
provider,
|
|
703
793
|
id: entry.id,
|
|
704
|
-
|
|
794
|
+
// The proxy's multiplier tracks the platform; the catalog's is a
|
|
795
|
+
// snapshot, so the live value wins and the catalog only fills gaps.
|
|
796
|
+
name: withCredits(
|
|
797
|
+
entry.name ?? catalog?.name ?? entry.id,
|
|
798
|
+
entry.credits ?? catalog?.credits,
|
|
799
|
+
),
|
|
705
800
|
...(catalog?.description !== undefined || entry.description !== undefined
|
|
706
801
|
? { description: catalog?.description ?? entry.description }
|
|
707
802
|
: {}),
|
|
@@ -843,6 +938,9 @@ const catalogModel = z.object({
|
|
|
843
938
|
maxTokens: z.number().step(1).min(1),
|
|
844
939
|
inputModalities: z.array(z.union(MODEL_MODALITIES)).min(1).default(["text"]),
|
|
845
940
|
reasoningEffort: z.string(),
|
|
941
|
+
// Platform-declared credit multiplier, e.g. "x0.06 credits"; displayed after
|
|
942
|
+
// the model name as "×0.06".
|
|
943
|
+
credits: z.string(),
|
|
846
944
|
});
|
|
847
945
|
|
|
848
946
|
export const Config = z.object({
|
|
@@ -890,6 +988,10 @@ function resolveModels(models) {
|
|
|
890
988
|
if (reasoningEffort !== undefined && !REASONING_EFFORT_IDS.has(reasoningEffort)) {
|
|
891
989
|
throw new Error(`dsh-llm-workbuddy: catalog model "${model.id}" reasoningEffort must be one of low|medium|high`);
|
|
892
990
|
}
|
|
991
|
+
const credits = typeof model.credits === "string" ? model.credits.trim() : undefined;
|
|
992
|
+
if (credits !== undefined && credits.length > 0 && creditsMultiplier(credits) === undefined) {
|
|
993
|
+
throw new Error(`dsh-llm-workbuddy: catalog model "${model.id}" credits must read like "x0.06 credits"`);
|
|
994
|
+
}
|
|
893
995
|
return {
|
|
894
996
|
id: model.id,
|
|
895
997
|
...(model.name === undefined ? {} : { name: model.name }),
|
|
@@ -898,6 +1000,7 @@ function resolveModels(models) {
|
|
|
898
1000
|
...(model.maxTokens === undefined ? {} : { maxTokens: model.maxTokens }),
|
|
899
1001
|
inputModalities: [...inputModalities],
|
|
900
1002
|
...(reasoningEffort === undefined ? {} : { reasoningEffort }),
|
|
1003
|
+
...(credits === undefined || credits.length === 0 ? {} : { credits }),
|
|
901
1004
|
};
|
|
902
1005
|
});
|
|
903
1006
|
}
|
|
@@ -1234,10 +1337,19 @@ async function probeChatWorking(baseURL) {
|
|
|
1234
1337
|
return { chatWorking: false, chatError: `HTTP ${response.status}${text ? `: ${text.slice(0, 200)}` : ""}` };
|
|
1235
1338
|
}
|
|
1236
1339
|
const body = await response.json().catch(() => null);
|
|
1237
|
-
const
|
|
1340
|
+
const choice = body?.choices?.[0];
|
|
1341
|
+
const content = choice?.message?.content;
|
|
1238
1342
|
if (typeof content === "string" && content.length > 0) {
|
|
1239
1343
|
return { chatWorking: true };
|
|
1240
1344
|
}
|
|
1345
|
+
// A reasoning model spends the whole 4-token cap on thinking and answers
|
|
1346
|
+
// with `content: ""` + `finish_reason: "length"` — that is a healthy proxy,
|
|
1347
|
+
// not an empty response (probed against hy3, which is all-thinking at this
|
|
1348
|
+
// cap). Any billed completion token proves the upstream round trip worked.
|
|
1349
|
+
const completionTokens = body?.usage?.completion_tokens;
|
|
1350
|
+
if (choice !== undefined && typeof completionTokens === "number" && completionTokens > 0) {
|
|
1351
|
+
return { chatWorking: true };
|
|
1352
|
+
}
|
|
1241
1353
|
return { chatWorking: false, chatError: "代理返回了空响应" };
|
|
1242
1354
|
} catch (error) {
|
|
1243
1355
|
return { chatWorking: false, chatError: String(error?.message ?? error) };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-llm-workbuddy",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
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,10 +48,10 @@
|
|
|
48
48
|
"CHANGELOG.md"
|
|
49
49
|
],
|
|
50
50
|
"peerDependencies": {
|
|
51
|
-
"@deepseek-ai/cordis": "^4.0.1",
|
|
52
|
-
"@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
|
|
53
|
-
"@deepseek-ai/dsh-settings": "^0.1.1-rc.2",
|
|
54
|
-
"@deepseek-ai/dsh-timeout": "^0.1.1-rc.2",
|
|
51
|
+
"@deepseek-ai/cordis": "^4.0.1 || ^4.0.2",
|
|
52
|
+
"@deepseek-ai/dsh-llm": "^0.1.1-rc.2 || ^0.1.5-rc.1",
|
|
53
|
+
"@deepseek-ai/dsh-settings": "^0.1.1-rc.2 || ^0.1.5-rc.1",
|
|
54
|
+
"@deepseek-ai/dsh-timeout": "^0.1.1-rc.2 || ^0.1.5-rc.1",
|
|
55
55
|
"@deepseek-ai/schemastery": "^3.18.1"
|
|
56
56
|
}
|
|
57
57
|
}
|