dsh-llm-workbuddy 0.1.12 → 0.1.13
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 +42 -0
- package/README.md +20 -3
- package/lib/client.js +16 -2
- package/lib/index.js +18 -5
- package/package.json +3 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
本文件记录每个版本的改动。版本号遵循语义化版本(MAJOR.MINOR.PATCH),
|
|
4
|
+
每次发版请同步 `package.json` 的 `version` 并打一个 `git tag`(如 `v0.1.13`),
|
|
5
|
+
在 GitHub 创建 Release 时本文件即为更新说明来源。
|
|
6
|
+
|
|
7
|
+
## [0.1.13] - 2026-08-28
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- 用量面板新增**积分消耗**统计。WorkBuddy 上游在每次对话的 usage 中返回
|
|
11
|
+
`credit` 字段(如 `deepseek-v4-pro` → `0.02`),插件此前丢弃了该字段;
|
|
12
|
+
现将其写入本地台账,并在 📊 用量面板展示今日/累计/按模型的积分消耗。
|
|
13
|
+
|
|
14
|
+
### Notes
|
|
15
|
+
- 代理未暴露账户余额接口,因此只能统计**已消耗**积分,无法显示剩余余额。
|
|
16
|
+
- 免费/折扣档模型上游回报 `credit: 0`,面板会提示"当前模型积分为 0"。
|
|
17
|
+
|
|
18
|
+
## [0.1.12] - 2026-08-28
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- 恢复模型目录即白名单的过滤逻辑,模型选择器不再显示已下架/不可用的
|
|
22
|
+
老模型(`deepseek-v3-1`、`glm-4.6`、`kimi-k2`、`minimax-m2.5`、
|
|
23
|
+
`kimi-k2-thinking`、`hunyuan-image-v3.0` 等)。
|
|
24
|
+
|
|
25
|
+
### Added(随此前未提交批次一并发布)
|
|
26
|
+
- 本地 token 用量台账(`$DSH_HOME/llm-workbuddy/usage.jsonl`)与
|
|
27
|
+
`GET /api/workbuddy/usage` 接口,📊 用量面板展示今日/累计/按模型明细。
|
|
28
|
+
- 一键诊断端点 `POST /api/workbuddy/diagnose`,真实发请求探测模型能否出字。
|
|
29
|
+
- 请求图片压缩,确保上游接受多模态输入。
|
|
30
|
+
|
|
31
|
+
## [0.1.11] 及更早
|
|
32
|
+
|
|
33
|
+
- `0.1.11`:适配代理动态模型列表(product.json 透传)。
|
|
34
|
+
- `0.1.10`:过滤已下架模型(glm-5.0/4.7/4.6、minimax-m2.5 等)。
|
|
35
|
+
- `0.1.9` 及之前:见 git 提交历史(`git log`)。
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
<!-- 历史版本锚点(便于生成 Release 时对比区间) -->
|
|
40
|
+
[0.1.13]: https://github.com/zdk119746/dsh-llm-workbuddy/compare/v0.1.12...v0.1.13
|
|
41
|
+
[0.1.12]: https://github.com/zdk119746/dsh-llm-workbuddy/compare/v0.1.11...v0.1.12
|
|
42
|
+
[0.1.11]: https://github.com/zdk119746/dsh-llm-workbuddy/releases/tag/v0.1.11
|
package/README.md
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/dsh-llm-workbuddy)
|
|
4
4
|
[](https://github.com/zdk119746/dsh-llm-workbuddy/blob/main/LICENSE)
|
|
5
|
+
[](./CHANGELOG.md)
|
|
5
6
|
|
|
6
7
|
在 DeepSeek Harness 中使用你的 **WorkBuddy / CodeBuddy** 账号模型的 LLM 适配器插件。
|
|
7
8
|
|
|
@@ -199,7 +200,7 @@ Web 登录后终端脚本也读得到同一份会话。
|
|
|
199
200
|
| `GET /api/workbuddy/status` | 读取会话文件(默认 `~/.codebuddy-session.json`,或配置的 `sessionFile`)的 `auth.expiresAt` 判断会话是否有效,并 `fetch` 代理 `/health` 判断 `proxyUp`;返回 JSON:`{ sessionFile, authenticated, expiresAt, account, proxyUp, tokenValid, loginScriptAvailable }`;非 GET 返回 405 |
|
|
200
201
|
| `POST /api/workbuddy/login` | 若已有有效会话则直接返回 `alreadyLoggedIn`;否则用系统 `python3` `spawn` 包内 `login_workbuddy.py --session-file <sessionFile>`,从子进程 stdout 解析出 `authUrl` 立即返回 `{ authUrl, pending:true }`(设备流在后台继续,前端轮询 status 感知完成);非 POST 返回 405 |
|
|
201
202
|
| `POST /api/workbuddy/diagnose` | **一键诊断**:真实探测健康状态,返回 `{ ok, session, health, chat, loginScriptAvailable, restartCommand }`。与 `/status` 不同,它除了探 `/health`,还会**真实发一次最小模型请求**(`chat.chatWorking`),能戳穿"胶囊显示成功但模型全 500"的假象;`ok:false` 时附带 `restartCommand`(自动区分本地 monorepo 布局与标准安装);非 POST 返回 405 |
|
|
202
|
-
| `GET /api/workbuddy/usage` | **用量统计**:读取本地 token 用量台账(`$DSH_HOME/llm-workbuddy/usage.jsonl`),返回 `{ today, byModel, total }`(今日/按模型/累计的 input/output tokens
|
|
203
|
+
| `GET /api/workbuddy/usage` | **用量统计**:读取本地 token 用量台账(`$DSH_HOME/llm-workbuddy/usage.jsonl`),返回 `{ today, byModel, total }`(今日/按模型/累计的 input/output tokens、请求次数,以及**积分消耗 `credit`**);非 GET 返回 405 |
|
|
203
204
|
|
|
204
205
|
> 会话文件与登录脚本路径的解析顺序:
|
|
205
206
|
> 1. 配置里显式指定的 `sessionFile` / `loginScript`;
|
|
@@ -216,8 +217,9 @@ Web 登录后终端脚本也读得到同一份会话。
|
|
|
216
217
|
显示**真实健康状态**(登录、会话文件、代理进程、登录令牌、模型能否出字),
|
|
217
218
|
发现问题时附带**可复制的重启命令**(一键复制到终端执行)。
|
|
218
219
|
- 胶囊里有个 **📊 用量** 按钮:点它 `GET /api/workbuddy/usage`,弹出一个面板
|
|
219
|
-
显示**今日/累计 token
|
|
220
|
-
`$DSH_HOME/llm-workbuddy/usage.jsonl`)。
|
|
220
|
+
显示**今日/累计 token 用量**、**积分消耗**(上游每次返回的 `credit` 累加)与
|
|
221
|
+
**按模型明细**(数据来自本地台账 `$DSH_HOME/llm-workbuddy/usage.jsonl`)。
|
|
222
|
+
注:代理不暴露余额/剩余积分接口,只能统计**已消耗**积分,无法显示账户剩余。
|
|
221
223
|
- 状态映射:
|
|
222
224
|
- `authenticated && proxyUp` → 🟢 绿,显示 `WorkBuddy · <昵称>`
|
|
223
225
|
- 否则 → 🔴 红,显示「登录」按钮;`proxyUp` 为 false 时额外提示 `代理未运行`
|
|
@@ -335,3 +337,18 @@ glm-5.0、minimax-m2.5 等,上游返回 `service info not found`)会被过
|
|
|
335
337
|
> 第三方 workbuddy2api 代理**不在包内**,需单独安装(见「安装代理」章节)。
|
|
336
338
|
> 仓库根目录的 `start-workbuddy.sh` / `login-workbuddy.sh` / `.workbuddy/` 是本地
|
|
337
339
|
> 开发用的配套文件,不随 npm 包发布。
|
|
340
|
+
|
|
341
|
+
## 更新日志 / Changelog
|
|
342
|
+
|
|
343
|
+
每次发版的改动记录见 [CHANGELOG.md](./CHANGELOG.md)。
|
|
344
|
+
|
|
345
|
+
### 发版流程(确保 GitHub 有更新说明)
|
|
346
|
+
|
|
347
|
+
1. 改完代码后,在 `package.json` 里 `version` 自增(语义化版本)。
|
|
348
|
+
2. 在 `CHANGELOG.md` 顶部补一段本次版本(`## [x.y.z] - 日期`)的 Added / Fixed / Changed。
|
|
349
|
+
3. 提交并打 tag:`git commit -am "release: x.y.z"` 然后 `git tag vx.y.z`。
|
|
350
|
+
4. `git push origin main --tags`,到 GitHub 用该 tag 创建 **Release**——
|
|
351
|
+
Release 的描述直接引用 CHANGELOG 对应段落,仓库页面就有了「改了啥」的说明。
|
|
352
|
+
|
|
353
|
+
> 注意:本机 `dsh web` 通过符号链接直接加载本地目录,因此**本地改动能立刻重启生效**,
|
|
354
|
+
> GitHub 上的版本只影响通过 `dsh plugin add dsh-llm-workbuddy` 安装的其他用户。
|
package/lib/client.js
CHANGED
|
@@ -242,19 +242,27 @@ function runWidget() {
|
|
|
242
242
|
.then(function (r) { return r.ok ? r.json() : null; })
|
|
243
243
|
.then(function (d) {
|
|
244
244
|
if (!d) throw new Error("空响应");
|
|
245
|
+
var hasCredit = (d.total.credit || 0) > 0;
|
|
245
246
|
var lines = [];
|
|
246
247
|
lines.push("今日:输入 " + d.today.inputTokens + " / 输出 " + d.today.outputTokens + " tokens(" + d.today.requests + " 次请求)");
|
|
247
248
|
lines.push("累计:输入 " + d.total.inputTokens + " / 输出 " + d.total.outputTokens + " tokens(" + d.total.requests + " 次请求)");
|
|
249
|
+
if (hasCredit) {
|
|
250
|
+
lines.push("今日积分消耗:" + fmtCredit(d.today.credit) + " | 累计:" + fmtCredit(d.total.credit));
|
|
251
|
+
}
|
|
248
252
|
lines.push("");
|
|
249
253
|
lines.push("按模型明细:");
|
|
250
254
|
if (d.byModel.length === 0) lines.push(" 暂无数据");
|
|
251
255
|
for (var i = 0; i < d.byModel.length; i += 1) {
|
|
252
256
|
var m = d.byModel[i];
|
|
253
|
-
|
|
257
|
+
var extra = (m.credit || 0) > 0 ? ",积分 " + fmtCredit(m.credit) : "";
|
|
258
|
+
lines.push(" " + m.model + ":输入 " + m.inputTokens + " / 输出 " + m.outputTokens + "(" + m.requests + " 次" + extra + ")");
|
|
254
259
|
}
|
|
260
|
+
var note = hasCredit
|
|
261
|
+
? "数据来源:本地用量台账(含上游每次返回的积分消耗;代理不暴露余额接口,无法显示剩余积分)"
|
|
262
|
+
: "数据来源:本地 token 用量台账(" + d.total.requests + " 次累计);当前模型为免费/折扣档,上游回报积分为 0";
|
|
255
263
|
el.innerHTML = '<div class="wb-usage-title" style="font-weight:700;margin-bottom:6px">WorkBuddy 用量统计</div>' +
|
|
256
264
|
'<div class="wb-usage-body" style="white-space:pre-wrap;word-break:break-word">' + esc(lines.join("\n")) + "</div>" +
|
|
257
|
-
'<div style="margin-top:8px;color:#64748b;font-size:11px"
|
|
265
|
+
'<div style="margin-top:8px;color:#64748b;font-size:11px">' + esc(note) + "</div>";
|
|
258
266
|
})
|
|
259
267
|
.catch(function (err) {
|
|
260
268
|
el.innerHTML = '<div class="wb-usage-title" style="font-weight:700;color:#f87171">用量加载失败</div>' +
|
|
@@ -268,6 +276,12 @@ function runWidget() {
|
|
|
268
276
|
});
|
|
269
277
|
}
|
|
270
278
|
|
|
279
|
+
/** 格式化积分消耗:保留 2 位小数,去掉无意义的尾零。 */
|
|
280
|
+
function fmtCredit(n) {
|
|
281
|
+
var v = Number(n) || 0;
|
|
282
|
+
return (Math.round(v * 100) / 100).toString();
|
|
283
|
+
}
|
|
284
|
+
|
|
271
285
|
/** 手动领取今日签到(幂等:官方对已签到返回业务拒绝,不会出错)。 */
|
|
272
286
|
function onCheckin() {
|
|
273
287
|
var btn = host.querySelector("#wb-checkin");
|
package/lib/index.js
CHANGED
|
@@ -354,11 +354,17 @@ function mapFinishReason(reason) {
|
|
|
354
354
|
function mapUsage(usage) {
|
|
355
355
|
const cacheRead = usage.prompt_tokens_details?.cached_tokens ?? usage.prompt_cache_hit_tokens;
|
|
356
356
|
const reasoning = usage.completion_tokens_details?.reasoning_tokens;
|
|
357
|
+
// The upstream reports per-request `credit` consumption in the usage block
|
|
358
|
+
// (e.g. deepseek-v4-pro → 0.02). Free/discounted models report 0, so we
|
|
359
|
+
// only keep the field when it is a positive number — that is the real
|
|
360
|
+
// 积分 spend we want to surface (the proxy exposes no balance endpoint).
|
|
361
|
+
const credit = typeof usage.credit === "number" && usage.credit > 0 ? usage.credit : undefined;
|
|
357
362
|
return {
|
|
358
363
|
inputTokens: usage.prompt_tokens - (cacheRead ?? 0),
|
|
359
364
|
outputTokens: usage.completion_tokens,
|
|
360
365
|
...(cacheRead !== undefined ? { cacheReadTokens: cacheRead } : {}),
|
|
361
366
|
...(reasoning !== undefined ? { reasoningTokens: reasoning } : {}),
|
|
367
|
+
...(credit !== undefined ? { credit } : {}),
|
|
362
368
|
};
|
|
363
369
|
}
|
|
364
370
|
|
|
@@ -387,6 +393,7 @@ function recordUsage(model, usage) {
|
|
|
387
393
|
output: usage.outputTokens ?? 0,
|
|
388
394
|
cacheRead: usage.cacheReadTokens ?? 0,
|
|
389
395
|
reasoning: usage.reasoningTokens ?? 0,
|
|
396
|
+
credit: usage.credit ?? 0,
|
|
390
397
|
});
|
|
391
398
|
appendFileSync(file, `${line}\n`);
|
|
392
399
|
// Trim the ledger when it exceeds the cap: keep only the newest lines.
|
|
@@ -410,12 +417,14 @@ function readUsageLedger() {
|
|
|
410
417
|
const byModel = new Map();
|
|
411
418
|
let todayInput = 0;
|
|
412
419
|
let todayOutput = 0;
|
|
420
|
+
let todayCredit = 0;
|
|
413
421
|
let todayRequests = 0;
|
|
414
422
|
let totalInput = 0;
|
|
415
423
|
let totalOutput = 0;
|
|
424
|
+
let totalCredit = 0;
|
|
416
425
|
let totalRequests = 0;
|
|
417
426
|
if (!existsSync(file)) {
|
|
418
|
-
return { today: { inputTokens: 0, outputTokens: 0, requests: 0 }, byModel: [], total: { inputTokens: 0, outputTokens: 0, requests: 0 } };
|
|
427
|
+
return { today: { inputTokens: 0, outputTokens: 0, credit: 0, requests: 0 }, byModel: [], total: { inputTokens: 0, outputTokens: 0, credit: 0, requests: 0 } };
|
|
419
428
|
}
|
|
420
429
|
const lines = readFileSync(file, "utf8").split("\n").filter(Boolean);
|
|
421
430
|
for (const line of lines) {
|
|
@@ -423,28 +432,32 @@ function readUsageLedger() {
|
|
|
423
432
|
try { entry = JSON.parse(line); } catch { continue; }
|
|
424
433
|
const input = entry.input ?? 0;
|
|
425
434
|
const output = entry.output ?? 0;
|
|
435
|
+
const credit = entry.credit ?? 0;
|
|
426
436
|
totalInput += input;
|
|
427
437
|
totalOutput += output;
|
|
438
|
+
totalCredit += credit;
|
|
428
439
|
totalRequests += 1;
|
|
429
440
|
if ((entry.ts ?? 0) >= todayMs) {
|
|
430
441
|
todayInput += input;
|
|
431
442
|
todayOutput += output;
|
|
443
|
+
todayCredit += credit;
|
|
432
444
|
todayRequests += 1;
|
|
433
445
|
}
|
|
434
446
|
const model = String(entry.model ?? "unknown");
|
|
435
|
-
const agg = byModel.get(model) ?? { inputTokens: 0, outputTokens: 0, requests: 0 };
|
|
447
|
+
const agg = byModel.get(model) ?? { inputTokens: 0, outputTokens: 0, credit: 0, requests: 0 };
|
|
436
448
|
agg.inputTokens += input;
|
|
437
449
|
agg.outputTokens += output;
|
|
450
|
+
agg.credit += credit;
|
|
438
451
|
agg.requests += 1;
|
|
439
452
|
byModel.set(model, agg);
|
|
440
453
|
}
|
|
441
454
|
const byModelList = [...byModel.entries()]
|
|
442
455
|
.map(([model, agg]) => ({ model, ...agg }))
|
|
443
|
-
.sort((a, b) => b.inputTokens + b.outputTokens - (a.inputTokens + a.outputTokens));
|
|
456
|
+
.sort((a, b) => b.credit - a.credit || b.inputTokens + b.outputTokens - (a.inputTokens + a.outputTokens));
|
|
444
457
|
return {
|
|
445
|
-
today: { inputTokens: todayInput, outputTokens: todayOutput, requests: todayRequests },
|
|
458
|
+
today: { inputTokens: todayInput, outputTokens: todayOutput, credit: todayCredit, requests: todayRequests },
|
|
446
459
|
byModel: byModelList,
|
|
447
|
-
total: { inputTokens: totalInput, outputTokens: totalOutput, requests: totalRequests },
|
|
460
|
+
total: { inputTokens: totalInput, outputTokens: totalOutput, credit: totalCredit, requests: totalRequests },
|
|
448
461
|
};
|
|
449
462
|
}
|
|
450
463
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-llm-workbuddy",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.13",
|
|
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",
|
|
@@ -44,7 +44,8 @@
|
|
|
44
44
|
"lib",
|
|
45
45
|
"login_workbuddy.py",
|
|
46
46
|
"cordis.patch.yml",
|
|
47
|
-
"README.md"
|
|
47
|
+
"README.md",
|
|
48
|
+
"CHANGELOG.md"
|
|
48
49
|
],
|
|
49
50
|
"peerDependencies": {
|
|
50
51
|
"@deepseek-ai/cordis": "^4.0.1",
|