dsh-workbuddy-connect 0.2.4 → 0.2.6
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/README.en.md +8 -0
- package/README.md +12 -0
- package/lib/bin.js +1 -1
- package/lib/client.js +80 -2
- package/lib/{host-heartbeat-okrdovCF.js → host-heartbeat-sa98SyZ9.js} +334 -27
- package/lib/index.d.ts +84 -6
- package/lib/index.js +152 -10
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -10,6 +10,14 @@ Brings every model in the WorkBuddy desktop app (GLM-5.3, GLM-5.2, DeepSeek-V4-P
|
|
|
10
10
|
|
|
11
11
|

|
|
12
12
|
|
|
13
|
+
- **Image input**: image messages are admitted per the upstream's per-model capability flag — most models (GLM-5.3-Flash, GLM-5.2, the DeepSeek-V4 series, etc.) accept pasted or dragged-in images, while text-only models (e.g. GLM-5.1) keep a clear refusal.
|
|
14
|
+
|
|
15
|
+
- **Thinking effort**: the model picker exposes the per-model effort levels the upstream declares (e.g. GLM-5.3 offers low / high / xhigh, GLM-5.3-Flash low / high / max), forwarded as `reasoning_effort` on the wire.
|
|
16
|
+
|
|
17
|
+
- **Limited-time free at a glance**: the status card marks models that are currently free / limited-time free / on a night discount (following the upstream `credits` and `tags` live).
|
|
18
|
+
|
|
19
|
+
- **Rate ratio at a glance**: every model in the selection list carries its credits multiplier on the name (e.g. `GLM-5.2 · x0.79`, `Hy3 · x0.00`), in both the `/model` popup and the composer seat; the status card adds a localized rate line too. The rate is display-only — requests always use the model id.
|
|
20
|
+
|
|
13
21
|
- **Info at a glance**: Settings → Plugins → DSH WorkBuddy Connect card
|
|
14
22
|
|
|
15
23
|

|
package/README.md
CHANGED
|
@@ -15,6 +15,18 @@
|
|
|
15
15
|

|
|
16
16
|
|
|
17
17
|
|
|
18
|
+
- **图片输入**:按上游逐模型声明的能力放行图片——绝大多数模型(含 GLM-5.3-Flash、GLM-5.2、DeepSeek-V4 系列等)可直接粘贴或拖入图片;个别纯文本模型(如 GLM-5.1)按上游声明仍会明确提示不支持。
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
- **思考强度**:按上游每个模型声明的 `supportedEfforts` 提供思考等级选项(如 GLM-5.3 支持 low / high / xhigh,GLM-5.3-Flash 支持 low / high / max),在 DSH 模型选择器里即可切换,请求以 `reasoning_effort` 转发。
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
- **限时免费一目了然**:状态卡片会标注当前免费 / 限时免费 / 夜间折扣的模型(跟随上游 `credits` 与 `tags` 实时更新)。
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
- **费率比例直接可见**:模型选择列表里每个模型名后直接显示积分倍率(如 `GLM-5.2 · x0.79`、`Hy3 · x0.00`),`/model` 弹窗与 composer 下拉都能看到;设置卡片里也补充了倍率说明。倍率只影响显示,发送请求仍使用模型 id。
|
|
28
|
+
|
|
29
|
+
|
|
18
30
|
- **信息查看**:设置 → 插件 → DSH WorkBuddy Connect 卡片
|
|
19
31
|
|
|
20
32
|
|
package/lib/bin.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { S as workbuddyOwnAuthPath, a as readHostHeartbeat, c as WORKBUDDY_CONNECT_VERSION, l as WorkBuddyUpstreamClient, m as FALLBACK_WORKBUDDY_MODELS, o as workbuddyHostHeartbeatPath, r as isHeartbeatProcessAlive, v as WorkBuddyCredentialStore } from "./host-heartbeat-sa98SyZ9.js";
|
|
3
3
|
import { realpathSync } from "node:fs";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
//#region src/bin.ts
|
package/lib/client.js
CHANGED
|
@@ -124,6 +124,36 @@ window.__ModuleLoader__.load({
|
|
|
124
124
|
lineHeight: "20px",
|
|
125
125
|
color: "var(--dsw-alias-label-secondary)"
|
|
126
126
|
};
|
|
127
|
+
const modelBadgeStyle = {
|
|
128
|
+
display: "flex",
|
|
129
|
+
alignItems: "center",
|
|
130
|
+
gap: 6,
|
|
131
|
+
flexWrap: "wrap"
|
|
132
|
+
};
|
|
133
|
+
const modelOfferStyle = {
|
|
134
|
+
display: "flex",
|
|
135
|
+
flexDirection: "column",
|
|
136
|
+
gap: 2
|
|
137
|
+
};
|
|
138
|
+
const modelRateStyle = {
|
|
139
|
+
fontSize: 12,
|
|
140
|
+
lineHeight: "18px",
|
|
141
|
+
color: "var(--dsw-alias-label-tertiary)"
|
|
142
|
+
};
|
|
143
|
+
const modelBadgeChipStyle = {
|
|
144
|
+
padding: "1px 8px",
|
|
145
|
+
borderRadius: 999,
|
|
146
|
+
fontSize: 11,
|
|
147
|
+
lineHeight: "18px",
|
|
148
|
+
background: "var(--dsw-alias-state-success-subtle, rgba(34, 160, 107, 0.12))",
|
|
149
|
+
color: "var(--dsw-alias-state-success-primary, #22a06b)"
|
|
150
|
+
};
|
|
151
|
+
/** Localize an upstream promotional badge label, with an unknown-badge fallback. */
|
|
152
|
+
function modelBadgeLabel(badge, t) {
|
|
153
|
+
if (badge === "限时免费") return t("badgeLimitedFree");
|
|
154
|
+
if (badge === "夜间折扣") return t("badgeNightDiscount");
|
|
155
|
+
return badge;
|
|
156
|
+
}
|
|
127
157
|
const progressTrackStyle = {
|
|
128
158
|
height: 8,
|
|
129
159
|
overflow: "hidden",
|
|
@@ -187,6 +217,34 @@ window.__ModuleLoader__.load({
|
|
|
187
217
|
]
|
|
188
218
|
});
|
|
189
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* One model offer row: name, promotional badges, and the billing rate.
|
|
222
|
+
*
|
|
223
|
+
* The rate sits under the name rather than beside it because the row already
|
|
224
|
+
* spends its horizontal budget on badges; stacking keeps long model names and
|
|
225
|
+
* several badges from squeezing the rate into an ellipsis.
|
|
226
|
+
*/
|
|
227
|
+
function ModelOfferRow({ model, t }) {
|
|
228
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
229
|
+
style: modelOfferStyle,
|
|
230
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
231
|
+
style: quotaLabelStyle,
|
|
232
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: model.name }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
233
|
+
style: modelBadgeStyle,
|
|
234
|
+
children: [model.badges?.map((badge) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
235
|
+
style: modelBadgeChipStyle,
|
|
236
|
+
children: modelBadgeLabel(badge, t)
|
|
237
|
+
}, badge)), model.free === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
238
|
+
style: modelBadgeChipStyle,
|
|
239
|
+
children: t("freeModel")
|
|
240
|
+
}) : null]
|
|
241
|
+
})]
|
|
242
|
+
}), model.credits === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
243
|
+
style: modelRateStyle,
|
|
244
|
+
children: t("rate", { rate: model.credits })
|
|
245
|
+
})]
|
|
246
|
+
});
|
|
247
|
+
}
|
|
190
248
|
/** Render WorkBuddy sign-in state and credit as one expandable card. */
|
|
191
249
|
function WorkBuddyPluginCard({ t }) {
|
|
192
250
|
if (t === void 0) throw new Error("WorkBuddy plugin card requires its translation function");
|
|
@@ -329,6 +387,16 @@ window.__ModuleLoader__.load({
|
|
|
329
387
|
status.creditsError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
330
388
|
style: errorStyle,
|
|
331
389
|
children: t("creditsError", { message: status.creditsError })
|
|
390
|
+
}),
|
|
391
|
+
status.models === void 0 || status.models.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
392
|
+
style: quotaListStyle,
|
|
393
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
394
|
+
style: quotaTitleStyle,
|
|
395
|
+
children: t("modelsHeading")
|
|
396
|
+
}), status.models.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelOfferRow, {
|
|
397
|
+
model,
|
|
398
|
+
t
|
|
399
|
+
}, model.id))]
|
|
332
400
|
})
|
|
333
401
|
] }) : null,
|
|
334
402
|
status.status === "signed-out" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
@@ -365,7 +433,12 @@ window.__ModuleLoader__.load({
|
|
|
365
433
|
refresh: "Refresh",
|
|
366
434
|
refreshing: "Refreshing…",
|
|
367
435
|
requestFailed: "Request failed",
|
|
368
|
-
accountHeading: "Account"
|
|
436
|
+
accountHeading: "Account",
|
|
437
|
+
modelsHeading: "Model offers",
|
|
438
|
+
freeModel: "Free",
|
|
439
|
+
badgeLimitedFree: "Limited-time free",
|
|
440
|
+
badgeNightDiscount: "Night discount",
|
|
441
|
+
rate: "{rate} credits per message"
|
|
369
442
|
};
|
|
370
443
|
const zh = {
|
|
371
444
|
title: "DSH WorkBuddy Connect",
|
|
@@ -386,7 +459,12 @@ window.__ModuleLoader__.load({
|
|
|
386
459
|
refresh: "刷新",
|
|
387
460
|
refreshing: "正在刷新…",
|
|
388
461
|
requestFailed: "请求失败",
|
|
389
|
-
accountHeading: "账号"
|
|
462
|
+
accountHeading: "账号",
|
|
463
|
+
modelsHeading: "模型优惠",
|
|
464
|
+
freeModel: "免费",
|
|
465
|
+
badgeLimitedFree: "限时免费",
|
|
466
|
+
badgeNightDiscount: "夜间折扣",
|
|
467
|
+
rate: "{rate} 积分/次"
|
|
390
468
|
};
|
|
391
469
|
//#endregion
|
|
392
470
|
//#region src/client/index.tsx
|
|
@@ -314,76 +314,284 @@ var WorkBuddyCredentialStore = class {
|
|
|
314
314
|
//#endregion
|
|
315
315
|
//#region src/catalog.ts
|
|
316
316
|
/**
|
|
317
|
-
* Static CLI models observed on the CN endpoint (
|
|
318
|
-
*
|
|
319
|
-
*
|
|
317
|
+
* Static CLI models observed on the CN endpoint (re-verified against the live
|
|
318
|
+
* catalog 2026-09-01, including the thinking-effort and billing metadata). The
|
|
319
|
+
* upstream refresh replaces this list at startup; it exists so the provider
|
|
320
|
+
* registers with a usable catalog even while the first fetch is in flight or
|
|
321
|
+
* offline.
|
|
322
|
+
*
|
|
323
|
+
* The list tracks the `cli` agent's model roster exactly: the 15 models the
|
|
324
|
+
* desktop CLI offers. Reasoning metadata is taken verbatim from the live
|
|
325
|
+
* endpoint — each model's supported effort set and whether thinking can be
|
|
326
|
+
* disabled — and the `free` flag follows the upstream `x0.00` credits marker.
|
|
320
327
|
*/
|
|
321
328
|
const FALLBACK_WORKBUDDY_MODELS = [
|
|
322
329
|
{
|
|
323
330
|
id: "auto",
|
|
324
331
|
name: "Auto",
|
|
325
332
|
contextWindow: 168e3,
|
|
326
|
-
maxTokens: 32e3
|
|
333
|
+
maxTokens: 32e3,
|
|
334
|
+
supportsImages: true,
|
|
335
|
+
reasoning: {
|
|
336
|
+
supports: true,
|
|
337
|
+
onlyReasoning: true,
|
|
338
|
+
defaultEffort: "high",
|
|
339
|
+
canDisableThinking: false
|
|
340
|
+
},
|
|
341
|
+
billing: { free: false }
|
|
327
342
|
},
|
|
328
343
|
{
|
|
329
344
|
id: "hy3",
|
|
330
345
|
name: "Hy3",
|
|
331
346
|
contextWindow: 192e3,
|
|
332
|
-
maxTokens: 64e3
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
347
|
+
maxTokens: 64e3,
|
|
348
|
+
supportsImages: true,
|
|
349
|
+
reasoning: {
|
|
350
|
+
supports: true,
|
|
351
|
+
onlyReasoning: true,
|
|
352
|
+
defaultEffort: "high",
|
|
353
|
+
canDisableThinking: false
|
|
354
|
+
},
|
|
355
|
+
billing: {
|
|
356
|
+
credits: "x0.00",
|
|
357
|
+
badges: ["限时免费"],
|
|
358
|
+
free: true
|
|
359
|
+
}
|
|
339
360
|
},
|
|
340
361
|
{
|
|
341
362
|
id: "glm-5.2",
|
|
342
363
|
name: "GLM-5.2",
|
|
343
364
|
contextWindow: 1e6,
|
|
344
|
-
maxTokens: 48e3
|
|
365
|
+
maxTokens: 48e3,
|
|
366
|
+
supportsImages: true,
|
|
367
|
+
reasoning: {
|
|
368
|
+
supports: true,
|
|
369
|
+
onlyReasoning: true,
|
|
370
|
+
defaultEffort: "medium",
|
|
371
|
+
canDisableThinking: false
|
|
372
|
+
},
|
|
373
|
+
billing: {
|
|
374
|
+
credits: "x0.79 credits",
|
|
375
|
+
badges: ["夜间折扣"],
|
|
376
|
+
free: false
|
|
377
|
+
}
|
|
345
378
|
},
|
|
346
379
|
{
|
|
347
380
|
id: "glm-5.1",
|
|
348
381
|
name: "GLM-5.1",
|
|
349
382
|
contextWindow: 2e5,
|
|
350
|
-
maxTokens: 48e3
|
|
383
|
+
maxTokens: 48e3,
|
|
384
|
+
supportsImages: false,
|
|
385
|
+
reasoning: {
|
|
386
|
+
supports: true,
|
|
387
|
+
onlyReasoning: true,
|
|
388
|
+
defaultEffort: "medium",
|
|
389
|
+
canDisableThinking: false
|
|
390
|
+
},
|
|
391
|
+
billing: {
|
|
392
|
+
credits: "x0.79 credits",
|
|
393
|
+
free: false
|
|
394
|
+
}
|
|
351
395
|
},
|
|
352
396
|
{
|
|
353
|
-
id: "
|
|
354
|
-
name: "
|
|
355
|
-
contextWindow:
|
|
356
|
-
maxTokens:
|
|
397
|
+
id: "glm-5v-turbo",
|
|
398
|
+
name: "GLM-5v-Turbo",
|
|
399
|
+
contextWindow: 2e5,
|
|
400
|
+
maxTokens: 64e3,
|
|
401
|
+
supportsImages: true,
|
|
402
|
+
reasoning: {
|
|
403
|
+
supports: true,
|
|
404
|
+
onlyReasoning: true,
|
|
405
|
+
defaultEffort: "medium",
|
|
406
|
+
canDisableThinking: false
|
|
407
|
+
},
|
|
408
|
+
billing: {
|
|
409
|
+
credits: "x0.71 credits",
|
|
410
|
+
free: false
|
|
411
|
+
}
|
|
357
412
|
},
|
|
358
413
|
{
|
|
359
414
|
id: "kimi-k3-1",
|
|
360
415
|
name: "Kimi-K3",
|
|
361
416
|
contextWindow: 1e6,
|
|
362
|
-
maxTokens: 32e3
|
|
417
|
+
maxTokens: 32e3,
|
|
418
|
+
supportsImages: true,
|
|
419
|
+
reasoning: {
|
|
420
|
+
supports: true,
|
|
421
|
+
onlyReasoning: true,
|
|
422
|
+
defaultEffort: "medium",
|
|
423
|
+
canDisableThinking: false
|
|
424
|
+
},
|
|
425
|
+
billing: {
|
|
426
|
+
credits: "x1.62 credits",
|
|
427
|
+
free: false
|
|
428
|
+
}
|
|
363
429
|
},
|
|
364
430
|
{
|
|
365
431
|
id: "kimi-k2.7",
|
|
366
432
|
name: "Kimi-K2.7-Code",
|
|
367
433
|
contextWindow: 256e3,
|
|
368
|
-
maxTokens: 32e3
|
|
434
|
+
maxTokens: 32e3,
|
|
435
|
+
supportsImages: true,
|
|
436
|
+
reasoning: {
|
|
437
|
+
supports: true,
|
|
438
|
+
onlyReasoning: true,
|
|
439
|
+
defaultEffort: "medium",
|
|
440
|
+
canDisableThinking: false
|
|
441
|
+
},
|
|
442
|
+
billing: {
|
|
443
|
+
credits: "x0.57 credits",
|
|
444
|
+
free: false
|
|
445
|
+
}
|
|
369
446
|
},
|
|
370
447
|
{
|
|
371
448
|
id: "kimi-k2.6",
|
|
372
449
|
name: "Kimi-K2.6",
|
|
373
450
|
contextWindow: 256e3,
|
|
374
|
-
maxTokens: 32e3
|
|
451
|
+
maxTokens: 32e3,
|
|
452
|
+
supportsImages: true,
|
|
453
|
+
reasoning: {
|
|
454
|
+
supports: true,
|
|
455
|
+
onlyReasoning: true,
|
|
456
|
+
defaultEffort: "medium",
|
|
457
|
+
canDisableThinking: false
|
|
458
|
+
},
|
|
459
|
+
billing: {
|
|
460
|
+
credits: "x0.52 credits",
|
|
461
|
+
free: false
|
|
462
|
+
}
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
id: "minimax-m3",
|
|
466
|
+
name: "MiniMax-M3",
|
|
467
|
+
contextWindow: 512e3,
|
|
468
|
+
maxTokens: 128e3,
|
|
469
|
+
supportsImages: true,
|
|
470
|
+
reasoning: {
|
|
471
|
+
supports: true,
|
|
472
|
+
onlyReasoning: true,
|
|
473
|
+
defaultEffort: "medium",
|
|
474
|
+
canDisableThinking: false
|
|
475
|
+
},
|
|
476
|
+
billing: {
|
|
477
|
+
credits: "x0.25 credits",
|
|
478
|
+
free: false
|
|
479
|
+
}
|
|
375
480
|
},
|
|
376
481
|
{
|
|
377
482
|
id: "deepseek-v4-flash",
|
|
378
483
|
name: "Deepseek-V4-Flash",
|
|
379
484
|
contextWindow: 1e6,
|
|
380
|
-
maxTokens: 5e4
|
|
485
|
+
maxTokens: 5e4,
|
|
486
|
+
supportsImages: true,
|
|
487
|
+
reasoning: {
|
|
488
|
+
supports: true,
|
|
489
|
+
onlyReasoning: true,
|
|
490
|
+
defaultEffort: "high",
|
|
491
|
+
canDisableThinking: false
|
|
492
|
+
},
|
|
493
|
+
billing: {
|
|
494
|
+
credits: "x0.17 credits",
|
|
495
|
+
free: false
|
|
496
|
+
}
|
|
381
497
|
},
|
|
382
498
|
{
|
|
383
499
|
id: "deepseek-v4-pro",
|
|
384
500
|
name: "Deepseek-V4-Pro",
|
|
385
501
|
contextWindow: 1e6,
|
|
386
|
-
maxTokens: 5e4
|
|
502
|
+
maxTokens: 5e4,
|
|
503
|
+
supportsImages: true,
|
|
504
|
+
reasoning: {
|
|
505
|
+
supports: true,
|
|
506
|
+
onlyReasoning: true,
|
|
507
|
+
defaultEffort: "high",
|
|
508
|
+
canDisableThinking: false
|
|
509
|
+
},
|
|
510
|
+
billing: {
|
|
511
|
+
credits: "x0.51 credits",
|
|
512
|
+
free: false
|
|
513
|
+
}
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
id: "hy4-preview",
|
|
517
|
+
name: "Hy4 preview",
|
|
518
|
+
contextWindow: 1e6,
|
|
519
|
+
maxTokens: 64e3,
|
|
520
|
+
supportsImages: true,
|
|
521
|
+
reasoning: {
|
|
522
|
+
supports: true,
|
|
523
|
+
onlyReasoning: true,
|
|
524
|
+
supportedEfforts: ["high"],
|
|
525
|
+
defaultEffort: "high",
|
|
526
|
+
canDisableThinking: false
|
|
527
|
+
},
|
|
528
|
+
billing: {
|
|
529
|
+
credits: "x0.00",
|
|
530
|
+
badges: ["限时免费"],
|
|
531
|
+
free: true
|
|
532
|
+
}
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
id: "hy3-x",
|
|
536
|
+
name: "Hy3",
|
|
537
|
+
contextWindow: 192e3,
|
|
538
|
+
maxTokens: 64e3,
|
|
539
|
+
supportsImages: true,
|
|
540
|
+
reasoning: {
|
|
541
|
+
supports: true,
|
|
542
|
+
onlyReasoning: true,
|
|
543
|
+
supportedEfforts: ["low", "high"],
|
|
544
|
+
defaultEffort: "high",
|
|
545
|
+
canDisableThinking: false
|
|
546
|
+
},
|
|
547
|
+
billing: {
|
|
548
|
+
credits: "x0.05",
|
|
549
|
+
free: false
|
|
550
|
+
}
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
id: "glm-5.3",
|
|
554
|
+
name: "GLM-5.3",
|
|
555
|
+
contextWindow: 1e6,
|
|
556
|
+
maxTokens: 48e3,
|
|
557
|
+
supportsImages: true,
|
|
558
|
+
reasoning: {
|
|
559
|
+
supports: true,
|
|
560
|
+
onlyReasoning: true,
|
|
561
|
+
supportedEfforts: [
|
|
562
|
+
"low",
|
|
563
|
+
"high",
|
|
564
|
+
"xhigh"
|
|
565
|
+
],
|
|
566
|
+
defaultEffort: "high",
|
|
567
|
+
canDisableThinking: true
|
|
568
|
+
},
|
|
569
|
+
billing: {
|
|
570
|
+
credits: "x0.79",
|
|
571
|
+
free: false
|
|
572
|
+
}
|
|
573
|
+
},
|
|
574
|
+
{
|
|
575
|
+
id: "glm-5.3-flash",
|
|
576
|
+
name: "GLM-5.3-Flash",
|
|
577
|
+
contextWindow: 1e6,
|
|
578
|
+
maxTokens: 32e3,
|
|
579
|
+
supportsImages: true,
|
|
580
|
+
reasoning: {
|
|
581
|
+
supports: true,
|
|
582
|
+
onlyReasoning: true,
|
|
583
|
+
supportedEfforts: [
|
|
584
|
+
"low",
|
|
585
|
+
"high",
|
|
586
|
+
"max"
|
|
587
|
+
],
|
|
588
|
+
defaultEffort: "high",
|
|
589
|
+
canDisableThinking: true
|
|
590
|
+
},
|
|
591
|
+
billing: {
|
|
592
|
+
credits: "x0.06",
|
|
593
|
+
free: false
|
|
594
|
+
}
|
|
387
595
|
}
|
|
388
596
|
];
|
|
389
597
|
/** Mutable catalog shared by the shim's `/v1/models` and the adapter. */
|
|
@@ -424,6 +632,84 @@ const HARD_CREDIT_MARKERS = [
|
|
|
424
632
|
"额度用尽",
|
|
425
633
|
"没有积分"
|
|
426
634
|
];
|
|
635
|
+
/** The concrete effort spellings WorkBuddy exposes on the wire. */
|
|
636
|
+
const EFFORT_VALUES = [
|
|
637
|
+
"low",
|
|
638
|
+
"medium",
|
|
639
|
+
"high",
|
|
640
|
+
"xhigh",
|
|
641
|
+
"max"
|
|
642
|
+
];
|
|
643
|
+
/** Promotional badge keys the upstream tags carry, minus their color suffix. */
|
|
644
|
+
const BADGE_PREFIX = "badge:";
|
|
645
|
+
/** Parse the upstream `reasoning` object into {@link WorkBuddyModelReasoning}. */
|
|
646
|
+
function resolveUpstreamReasoning(wrapped) {
|
|
647
|
+
const supports = wrapped["supportsReasoning"] === true;
|
|
648
|
+
const onlyReasoning = wrapped["onlyReasoning"] === true;
|
|
649
|
+
const rawReasoning = wrapped["reasoning"];
|
|
650
|
+
let supportedEfforts;
|
|
651
|
+
let defaultEffort;
|
|
652
|
+
let canDisableThinking = true;
|
|
653
|
+
if (typeof rawReasoning === "object" && rawReasoning !== null && !Array.isArray(rawReasoning)) {
|
|
654
|
+
const reasoning = rawReasoning;
|
|
655
|
+
const rawEfforts = reasoning["supportedEfforts"];
|
|
656
|
+
if (Array.isArray(rawEfforts)) {
|
|
657
|
+
const efforts = rawEfforts.filter((value) => typeof value === "string" && EFFORT_VALUES.includes(value));
|
|
658
|
+
if (efforts.length > 0) supportedEfforts = efforts;
|
|
659
|
+
}
|
|
660
|
+
if (typeof reasoning["defaultEffort"] === "string" && EFFORT_VALUES.includes(reasoning["defaultEffort"])) defaultEffort = reasoning["defaultEffort"];
|
|
661
|
+
else if (typeof reasoning["effort"] === "string" && EFFORT_VALUES.includes(reasoning["effort"])) defaultEffort = reasoning["effort"];
|
|
662
|
+
canDisableThinking = reasoning["canDisableThinking"] === true;
|
|
663
|
+
}
|
|
664
|
+
return { reasoning: {
|
|
665
|
+
supports,
|
|
666
|
+
onlyReasoning,
|
|
667
|
+
...supportedEfforts === void 0 ? {} : { supportedEfforts },
|
|
668
|
+
...defaultEffort === void 0 ? {} : { defaultEffort },
|
|
669
|
+
canDisableThinking
|
|
670
|
+
} };
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Reduce an upstream credits string to its language-neutral display form.
|
|
674
|
+
*
|
|
675
|
+
* The host LLM seam carries this text to the browser, and the host has no
|
|
676
|
+
* locale service — whatever string is produced here is shown verbatim in every
|
|
677
|
+
* UI language. The upstream is inconsistent in a way that matters: some catalog
|
|
678
|
+
* rows report a bare multiplier (`x0.79`) and others append a unit word
|
|
679
|
+
* (`x0.79 credits`), and the unit word would pin the display to English.
|
|
680
|
+
* Dropping a trailing `credits` (case-insensitive, singular or plural) yields
|
|
681
|
+
* the one spelling that reads identically in every language.
|
|
682
|
+
*
|
|
683
|
+
* @param credits - raw upstream credits string, e.g. `"x0.79 credits"`.
|
|
684
|
+
* @returns the bare multiplier, or undefined when nothing displayable remains.
|
|
685
|
+
*/
|
|
686
|
+
function normalizeCredits(credits) {
|
|
687
|
+
if (credits === void 0) return void 0;
|
|
688
|
+
const trimmed = credits.trim();
|
|
689
|
+
if (trimmed === "") return void 0;
|
|
690
|
+
if (/^credits?$/iu.test(trimmed)) return void 0;
|
|
691
|
+
const bare = trimmed.replace(/\s+credits?$/iu, "").trim();
|
|
692
|
+
return bare === "" ? void 0 : bare;
|
|
693
|
+
}
|
|
694
|
+
/** Parse the upstream `tags` / `credits` fields into billing metadata. */
|
|
695
|
+
function resolveUpstreamBilling(wrapped) {
|
|
696
|
+
const rawCredits = wrapped["credits"];
|
|
697
|
+
const credits = typeof rawCredits === "string" && rawCredits.trim() !== "" ? rawCredits.trim() : void 0;
|
|
698
|
+
const badges = [];
|
|
699
|
+
const rawTags = wrapped["tags"];
|
|
700
|
+
if (Array.isArray(rawTags)) for (const tag of rawTags) {
|
|
701
|
+
if (typeof tag !== "string") continue;
|
|
702
|
+
if (!tag.toLowerCase().startsWith(BADGE_PREFIX)) continue;
|
|
703
|
+
const label = tag.slice(6).split(":")[0] ?? tag.slice(6);
|
|
704
|
+
if (label !== "") badges.push(label);
|
|
705
|
+
}
|
|
706
|
+
const free = credits !== void 0 && /^x?0\.0+$/u.test(credits);
|
|
707
|
+
return { billing: {
|
|
708
|
+
...credits === void 0 ? {} : { credits },
|
|
709
|
+
...badges.length === 0 ? {} : { badges },
|
|
710
|
+
free
|
|
711
|
+
} };
|
|
712
|
+
}
|
|
427
713
|
/** Session-invalidation markers that mean "sign in again in the WorkBuddy app". */
|
|
428
714
|
const SESSION_DEAD_MARKERS = ["Offline user session not found", "12153"];
|
|
429
715
|
/** Classify an upstream failure from its HTTP status and body excerpt. */
|
|
@@ -501,8 +787,15 @@ function billingHeaders(credential) {
|
|
|
501
787
|
}
|
|
502
788
|
/**
|
|
503
789
|
* Normalize an OpenAI chat-completions body for the WorkBuddy upstream:
|
|
504
|
-
* force `stream: true` (the upstream rejects non-streaming)
|
|
505
|
-
* `tool_choice` (the upstream's field is a string; object forms return 400)
|
|
790
|
+
* force `stream: true` (the upstream rejects non-streaming), flatten
|
|
791
|
+
* `tool_choice` (the upstream's field is a string; object forms return 400),
|
|
792
|
+
* and rewrite `developer` messages as `system`.
|
|
793
|
+
*
|
|
794
|
+
* The `developer` rewrite is load-bearing: pi-ai emits the system prompt as
|
|
795
|
+
* `role: "developer"` (the OpenAI convention it adopted), but the WorkBuddy
|
|
796
|
+
* upstream rejects that role with HTTP 400 code 11128 ("Illegal API
|
|
797
|
+
* invocation from an unapproved channel"). Rewriting to `system` is the
|
|
798
|
+
* compatible spelling the upstream accepts.
|
|
506
799
|
*/
|
|
507
800
|
function prepareChatBody(source) {
|
|
508
801
|
let body;
|
|
@@ -514,9 +807,20 @@ function prepareChatBody(source) {
|
|
|
514
807
|
if (typeof body !== "object" || body === null || Array.isArray(body)) return source;
|
|
515
808
|
const obj = body;
|
|
516
809
|
obj["stream"] = true;
|
|
810
|
+
normalizeDeveloperRole(obj);
|
|
517
811
|
normalizeToolChoice(obj);
|
|
518
812
|
return JSON.stringify(obj);
|
|
519
813
|
}
|
|
814
|
+
/** Rewrite `role: "developer"` messages to `role: "system"` (upstream rejects developer). */
|
|
815
|
+
function normalizeDeveloperRole(obj) {
|
|
816
|
+
const messages = obj["messages"];
|
|
817
|
+
if (!Array.isArray(messages)) return;
|
|
818
|
+
for (const message of messages) {
|
|
819
|
+
if (typeof message !== "object" || message === null || Array.isArray(message)) continue;
|
|
820
|
+
const wrapped = message;
|
|
821
|
+
if (wrapped["role"] === "developer") wrapped["role"] = "system";
|
|
822
|
+
}
|
|
823
|
+
}
|
|
520
824
|
/** Rewrite OpenAI `tool_choice` spellings into the upstream's string form. */
|
|
521
825
|
function normalizeToolChoice(obj) {
|
|
522
826
|
const suppress = () => {
|
|
@@ -666,7 +970,10 @@ var WorkBuddyUpstreamClient = class {
|
|
|
666
970
|
id,
|
|
667
971
|
name: typeof wrapped["name"] === "string" && wrapped["name"] !== "" ? wrapped["name"] : id,
|
|
668
972
|
contextWindow: input,
|
|
669
|
-
maxTokens: output
|
|
973
|
+
maxTokens: output,
|
|
974
|
+
supportsImages: wrapped["supportsImages"] === true && wrapped["disabledMultimodal"] !== true,
|
|
975
|
+
...resolveUpstreamReasoning(wrapped),
|
|
976
|
+
...resolveUpstreamBilling(wrapped)
|
|
670
977
|
});
|
|
671
978
|
}
|
|
672
979
|
const models = cliIds.map((id) => byId.get(id)).filter((model) => model !== void 0);
|
|
@@ -734,7 +1041,7 @@ var WorkBuddyUpstreamClient = class {
|
|
|
734
1041
|
};
|
|
735
1042
|
//#endregion
|
|
736
1043
|
//#region src/version.ts
|
|
737
|
-
const WORKBUDDY_CONNECT_VERSION = "0.2.
|
|
1044
|
+
const WORKBUDDY_CONNECT_VERSION = "0.2.6";
|
|
738
1045
|
//#endregion
|
|
739
1046
|
//#region src/host-heartbeat.ts
|
|
740
1047
|
/**
|
|
@@ -877,4 +1184,4 @@ function isHeartbeatProcessAlive(heartbeat) {
|
|
|
877
1184
|
return startAtMs <= heartbeat.registeredAt;
|
|
878
1185
|
}
|
|
879
1186
|
//#endregion
|
|
880
|
-
export {
|
|
1187
|
+
export { workbuddyOwnAuthPath as S, WORKBUDDY_AUTH_FILE_ENV as _, readHostHeartbeat as a, defaultDesktopAuthPath as b, WORKBUDDY_CONNECT_VERSION as c, normalizeCredits as d, prepareChatBody as f, WORKBUDDY_AUTH_FILENAME as g, WorkBuddyCatalog as h, processStartTimeMs as i, WorkBuddyUpstreamClient as l, FALLBACK_WORKBUDDY_MODELS as m, clearHostHeartbeat as n, workbuddyHostHeartbeatPath as o, regionOf as p, isHeartbeatProcessAlive as r, writeHostHeartbeat as s, WORKBUDDY_HOST_HEARTBEAT_FILENAME as t, classifyUpstreamError as u, WorkBuddyCredentialStore as v, parseWorkBuddyAuth as x, defaultDesktopAuthCandidates as y };
|
package/lib/index.d.ts
CHANGED
|
@@ -13,6 +13,55 @@ interface WorkBuddyUpstreamModel {
|
|
|
13
13
|
name: string;
|
|
14
14
|
contextWindow: number;
|
|
15
15
|
maxTokens: number;
|
|
16
|
+
/**
|
|
17
|
+
* Upstream-declared image input capability. Missing or false upstream data
|
|
18
|
+
* resolves to false, so an unknown model stays text-only: over-claiming
|
|
19
|
+
* admits an image the provider then rejects after the message is durable.
|
|
20
|
+
*/
|
|
21
|
+
supportsImages: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Reasoning metadata the upstream catalog declares per model. The wire
|
|
24
|
+
* effort values (`low`, `medium`, `high`, `xhigh`, `max`) map directly onto
|
|
25
|
+
* pi-ai's thinking levels, and the supported set decides which levels the
|
|
26
|
+
* DSH model selector offers.
|
|
27
|
+
*/
|
|
28
|
+
reasoning?: WorkBuddyModelReasoning;
|
|
29
|
+
/**
|
|
30
|
+
* Billing convenience metadata: the credits multiplier string the upstream
|
|
31
|
+
* reports (e.g. `"x0.00"` for free) and promotional badges like
|
|
32
|
+
* `badge:限时免费:#FF0000` or `badge:夜间折扣:#1E90FF`.
|
|
33
|
+
*
|
|
34
|
+
* The multiplier reaches the browser through the host LLM seam, which has no
|
|
35
|
+
* locale service, so {@link normalizeCredits} trims it to a
|
|
36
|
+
* language-neutral display form (`x0.79`) that reads the same in every UI
|
|
37
|
+
* language. The raw upstream string (which may spell `x0.79 credits`) stays
|
|
38
|
+
* on {@link WorkBuddyModelBilling.credits} for diagnostics.
|
|
39
|
+
*/
|
|
40
|
+
billing?: WorkBuddyModelBilling;
|
|
41
|
+
}
|
|
42
|
+
/** Reasoning metadata the upstream catalog declares for one model. */
|
|
43
|
+
interface WorkBuddyModelReasoning {
|
|
44
|
+
/** Whether the model does any reasoning at all (upstream `supportsReasoning`). */
|
|
45
|
+
supports: boolean;
|
|
46
|
+
/** Whether the model can only think (upstream `onlyReasoning`). */
|
|
47
|
+
onlyReasoning: boolean;
|
|
48
|
+
/** Selectable effort values; absent means the model has no explicit set. */
|
|
49
|
+
supportedEfforts?: readonly WorkBuddyEffort[];
|
|
50
|
+
/** Default effort the upstream uses when none is chosen. */
|
|
51
|
+
defaultEffort?: WorkBuddyEffort;
|
|
52
|
+
/** Whether thinking can be switched off; false means it is always on. */
|
|
53
|
+
canDisableThinking: boolean;
|
|
54
|
+
}
|
|
55
|
+
/** The concrete effort spellings WorkBuddy exposes on the wire. */
|
|
56
|
+
type WorkBuddyEffort = 'low' | 'medium' | 'high' | 'xhigh' | 'max';
|
|
57
|
+
/** Billing convenience metadata reported for one model. */
|
|
58
|
+
interface WorkBuddyModelBilling {
|
|
59
|
+
/** Credits multiplier, e.g. `"x0.00"` (free) or `"x0.79"`. */
|
|
60
|
+
credits?: string;
|
|
61
|
+
/** Promotional tags, e.g. `"限时免费"`, `"夜间折扣"`. */
|
|
62
|
+
badges?: readonly string[];
|
|
63
|
+
/** Whether the model is currently free (`x0.00` credits). */
|
|
64
|
+
free: boolean;
|
|
16
65
|
}
|
|
17
66
|
/** One billing package and its remaining credit. */
|
|
18
67
|
interface WorkBuddyCreditAccount {
|
|
@@ -42,14 +91,36 @@ type WorkBuddyChatResult = {
|
|
|
42
91
|
kind: UpstreamErrorKind;
|
|
43
92
|
message: string;
|
|
44
93
|
};
|
|
94
|
+
/**
|
|
95
|
+
* Reduce an upstream credits string to its language-neutral display form.
|
|
96
|
+
*
|
|
97
|
+
* The host LLM seam carries this text to the browser, and the host has no
|
|
98
|
+
* locale service — whatever string is produced here is shown verbatim in every
|
|
99
|
+
* UI language. The upstream is inconsistent in a way that matters: some catalog
|
|
100
|
+
* rows report a bare multiplier (`x0.79`) and others append a unit word
|
|
101
|
+
* (`x0.79 credits`), and the unit word would pin the display to English.
|
|
102
|
+
* Dropping a trailing `credits` (case-insensitive, singular or plural) yields
|
|
103
|
+
* the one spelling that reads identically in every language.
|
|
104
|
+
*
|
|
105
|
+
* @param credits - raw upstream credits string, e.g. `"x0.79 credits"`.
|
|
106
|
+
* @returns the bare multiplier, or undefined when nothing displayable remains.
|
|
107
|
+
*/
|
|
108
|
+
declare function normalizeCredits(credits: string | undefined): string | undefined;
|
|
45
109
|
/** Classify an upstream failure from its HTTP status and body excerpt. */
|
|
46
110
|
declare function classifyUpstreamError(status: number, body: string): UpstreamErrorKind;
|
|
47
111
|
/** Region for a login domain; an empty domain means CN (matching upstream tooling). */
|
|
48
112
|
declare function regionOf(domain: string): WorkBuddyRegion;
|
|
49
113
|
/**
|
|
50
114
|
* Normalize an OpenAI chat-completions body for the WorkBuddy upstream:
|
|
51
|
-
* force `stream: true` (the upstream rejects non-streaming)
|
|
52
|
-
* `tool_choice` (the upstream's field is a string; object forms return 400)
|
|
115
|
+
* force `stream: true` (the upstream rejects non-streaming), flatten
|
|
116
|
+
* `tool_choice` (the upstream's field is a string; object forms return 400),
|
|
117
|
+
* and rewrite `developer` messages as `system`.
|
|
118
|
+
*
|
|
119
|
+
* The `developer` rewrite is load-bearing: pi-ai emits the system prompt as
|
|
120
|
+
* `role: "developer"` (the OpenAI convention it adopted), but the WorkBuddy
|
|
121
|
+
* upstream rejects that role with HTTP 400 code 11128 ("Illegal API
|
|
122
|
+
* invocation from an unapproved channel"). Rewriting to `system` is the
|
|
123
|
+
* compatible spelling the upstream accepts.
|
|
53
124
|
*/
|
|
54
125
|
declare function prepareChatBody(source: string): string;
|
|
55
126
|
/**
|
|
@@ -184,9 +255,16 @@ declare class WorkBuddyCredentialStore {
|
|
|
184
255
|
/** One model entry the adapter exposes. */
|
|
185
256
|
type WorkBuddyModelInfo = WorkBuddyUpstreamModel;
|
|
186
257
|
/**
|
|
187
|
-
* Static CLI models observed on the CN endpoint (
|
|
188
|
-
*
|
|
189
|
-
*
|
|
258
|
+
* Static CLI models observed on the CN endpoint (re-verified against the live
|
|
259
|
+
* catalog 2026-09-01, including the thinking-effort and billing metadata). The
|
|
260
|
+
* upstream refresh replaces this list at startup; it exists so the provider
|
|
261
|
+
* registers with a usable catalog even while the first fetch is in flight or
|
|
262
|
+
* offline.
|
|
263
|
+
*
|
|
264
|
+
* The list tracks the `cli` agent's model roster exactly: the 15 models the
|
|
265
|
+
* desktop CLI offers. Reasoning metadata is taken verbatim from the live
|
|
266
|
+
* endpoint — each model's supported effort set and whether thinking can be
|
|
267
|
+
* disabled — and the `free` flag follows the upstream `x0.00` credits marker.
|
|
190
268
|
*/
|
|
191
269
|
declare const FALLBACK_WORKBUDDY_MODELS: readonly WorkBuddyModelInfo[];
|
|
192
270
|
/** Mutable catalog shared by the shim's `/v1/models` and the adapter. */
|
|
@@ -345,4 +423,4 @@ declare const Config: z<Config>;
|
|
|
345
423
|
*/
|
|
346
424
|
declare function apply(ctx: Context, config: Config): void;
|
|
347
425
|
//#endregion
|
|
348
|
-
export { Config, FALLBACK_WORKBUDDY_MODELS, type UpstreamErrorKind, WORKBUDDY_AUTH_FILENAME, WORKBUDDY_AUTH_FILE_ENV, WORKBUDDY_HOST_HEARTBEAT_FILENAME, WORKBUDDY_PROVIDER, WORKBUDDY_SETTINGS_NS, WORKBUDDY_STREAM_IDLE_TIMEOUT_MS, type WorkBuddyAdapter, type WorkBuddyAuthStatus, WorkBuddyCatalog, type WorkBuddyChatResult, type WorkBuddyCredential, WorkBuddyCredentialStore, type WorkBuddyCredits, type WorkBuddyHostHeartbeat, type WorkBuddyModelInfo, type WorkBuddyRefreshOutcome, type WorkBuddyShim, WorkBuddyUpstreamClient, type WorkBuddyUpstreamModel, apply, classifyUpstreamError, clearHostHeartbeat, createWorkBuddyAdapter, createWorkBuddyShim, defaultDesktopAuthCandidates, defaultDesktopAuthPath, inject, isHeartbeatProcessAlive, name, parseWorkBuddyAuth, prepareChatBody, processStartTimeMs, readHostHeartbeat, regionOf, workbuddyHostHeartbeatPath, workbuddyOwnAuthPath };
|
|
426
|
+
export { Config, FALLBACK_WORKBUDDY_MODELS, type UpstreamErrorKind, WORKBUDDY_AUTH_FILENAME, WORKBUDDY_AUTH_FILE_ENV, WORKBUDDY_HOST_HEARTBEAT_FILENAME, WORKBUDDY_PROVIDER, WORKBUDDY_SETTINGS_NS, WORKBUDDY_STREAM_IDLE_TIMEOUT_MS, type WorkBuddyAdapter, type WorkBuddyAuthStatus, WorkBuddyCatalog, type WorkBuddyChatResult, type WorkBuddyCredential, WorkBuddyCredentialStore, type WorkBuddyCredits, type WorkBuddyEffort, type WorkBuddyHostHeartbeat, type WorkBuddyModelBilling, type WorkBuddyModelInfo, type WorkBuddyModelReasoning, type WorkBuddyRefreshOutcome, type WorkBuddyShim, WorkBuddyUpstreamClient, type WorkBuddyUpstreamModel, apply, classifyUpstreamError, clearHostHeartbeat, createWorkBuddyAdapter, createWorkBuddyShim, defaultDesktopAuthCandidates, defaultDesktopAuthPath, inject, isHeartbeatProcessAlive, name, normalizeCredits, parseWorkBuddyAuth, prepareChatBody, processStartTimeMs, readHostHeartbeat, regionOf, workbuddyHostHeartbeatPath, workbuddyOwnAuthPath };
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _ as
|
|
1
|
+
import { S as workbuddyOwnAuthPath, _ as WORKBUDDY_AUTH_FILE_ENV, a as readHostHeartbeat, b as defaultDesktopAuthPath, d as normalizeCredits, f as prepareChatBody, g as WORKBUDDY_AUTH_FILENAME, h as WorkBuddyCatalog, i as processStartTimeMs, l as WorkBuddyUpstreamClient, m as FALLBACK_WORKBUDDY_MODELS, n as clearHostHeartbeat, o as workbuddyHostHeartbeatPath, p as regionOf, r as isHeartbeatProcessAlive, s as writeHostHeartbeat, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, u as classifyUpstreamError, v as WorkBuddyCredentialStore, x as parseWorkBuddyAuth, y as defaultDesktopAuthCandidates } from "./host-heartbeat-sa98SyZ9.js";
|
|
2
2
|
import z from "@deepseek-ai/schemastery";
|
|
3
3
|
import { installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
4
4
|
import { createProvider } from "@earendil-works/pi-ai";
|
|
@@ -22,8 +22,8 @@ const WORKBUDDY_PROVIDER = "workbuddy";
|
|
|
22
22
|
const WORKBUDDY_STREAM_IDLE_TIMEOUT_MS = 3e5;
|
|
23
23
|
/**
|
|
24
24
|
* Image-request budgets at the dsh-llm-pi-ai defaults; the profile type made
|
|
25
|
-
* them required in 0.1.1-rc.2.
|
|
26
|
-
*
|
|
25
|
+
* them required in 0.1.1-rc.2. They bound requests to models whose catalog
|
|
26
|
+
* entry declares `supportsImages`; text-only models never receive images.
|
|
27
27
|
*/
|
|
28
28
|
const REQUEST_IMAGE_BUDGETS = {
|
|
29
29
|
maxRequestImageBytes: 20971520,
|
|
@@ -62,6 +62,82 @@ const NO_COST = {
|
|
|
62
62
|
cacheRead: 0,
|
|
63
63
|
cacheWrite: 0
|
|
64
64
|
};
|
|
65
|
+
/**
|
|
66
|
+
* The suffix appended to a model's display name so its billing rate is visible
|
|
67
|
+
* wherever the name is shown.
|
|
68
|
+
*
|
|
69
|
+
* The separator is a middle dot rather than a hyphen or colon: model names
|
|
70
|
+
* already contain hyphens (`GLM-5.3-Flash`, `Deepseek-V4-Flash`), so a hyphen
|
|
71
|
+
* separator would be ambiguous about where the name ends and the rate begins.
|
|
72
|
+
*/
|
|
73
|
+
const RATE_SEPARATOR = " · ";
|
|
74
|
+
/**
|
|
75
|
+
* Append the billing rate to one model's display name.
|
|
76
|
+
*
|
|
77
|
+
* The rate rides the *name* alone because the DSH model surfaces disagree
|
|
78
|
+
* about which field they render: the composer's model seat (`ModelSelect`)
|
|
79
|
+
* renders `model.name` only and never reads `description`, while the `/model`
|
|
80
|
+
* popup renders BOTH — a rate in `description` would show it twice there, so
|
|
81
|
+
* `description` stays untouched.
|
|
82
|
+
*
|
|
83
|
+
* This is display-only and cannot affect routing: the wire request is built
|
|
84
|
+
* from `model.id` (pi-ai's completions API sets `model: model.id`), the
|
|
85
|
+
* selection a picker submits is `{provider, model: id, reasoningEffort}`, and
|
|
86
|
+
* `dsh-llm` validates `name` as a non-empty string without comparing its
|
|
87
|
+
* contents. Nothing in the host resolves a model *by* name.
|
|
88
|
+
*/
|
|
89
|
+
/**
|
|
90
|
+
* The declared promo badges (`限时免费`, `夜间折扣`) as a display string for the
|
|
91
|
+
* `/model` popup's description slot, which the name does not cover. The
|
|
92
|
+
* labels are the upstream's own spellings and the host seam has no locale
|
|
93
|
+
* service, so non-Chinese UIs see them verbatim — accepted until the picker
|
|
94
|
+
* grows a localized badge slot.
|
|
95
|
+
*/
|
|
96
|
+
function promoDescription(info) {
|
|
97
|
+
const badges = info.billing?.badges;
|
|
98
|
+
return badges === void 0 || badges.length === 0 ? void 0 : badges.join(" · ");
|
|
99
|
+
}
|
|
100
|
+
function withRate(name, info) {
|
|
101
|
+
const rate = normalizeCredits(info.billing?.credits);
|
|
102
|
+
return rate === void 0 ? name : `${name}${RATE_SEPARATOR}${rate}`;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Resolve a WorkBuddy model's reasoning capability into pi-ai's
|
|
106
|
+
* `thinkingLevelMap` (every level pinned to its wire spelling or `null` for
|
|
107
|
+
* unsupported), mirroring `dsh-llm-pi-ai`'s own `resolveModelReasoning`.
|
|
108
|
+
*
|
|
109
|
+
* Declared sets only: a thinking control is offered exactly when the upstream
|
|
110
|
+
* catalog declares a `supportedEfforts` list, and it offers exactly the
|
|
111
|
+
* declared values. Rows without a list (the older `{effort, summary}` shape)
|
|
112
|
+
* get no control at all — their selectable set is client-side knowledge the
|
|
113
|
+
* catalog does not carry (the desktop app differs per model there: GLM-5.2
|
|
114
|
+
* gets a thinking control while MiniMax-M3 and Kimi-K2.6 do not, though their
|
|
115
|
+
* catalog rows are identical), and another implementation against the same
|
|
116
|
+
* upstream (workbuddy2api) gates on the declared set and downgrades
|
|
117
|
+
* out-of-set values rather than passing them through, so sending an
|
|
118
|
+
* undeclared value risks a 400. Such models never carry `reasoning_effort`
|
|
119
|
+
* on the wire; the upstream applies its own default.
|
|
120
|
+
* `off` is offered only when the model explicitly reports thinking can be
|
|
121
|
+
* disabled (`canDisableThinking === true`).
|
|
122
|
+
*/
|
|
123
|
+
function reasoningFields(info) {
|
|
124
|
+
const reasoning = info.reasoning;
|
|
125
|
+
if (reasoning === void 0 || reasoning.supports !== true) return { reasoning: false };
|
|
126
|
+
const efforts = reasoning.supportedEfforts;
|
|
127
|
+
if (efforts === void 0 || efforts.length === 0) return { reasoning: false };
|
|
128
|
+
return {
|
|
129
|
+
reasoning: true,
|
|
130
|
+
thinkingLevelMap: {
|
|
131
|
+
off: reasoning.canDisableThinking === true ? "off" : null,
|
|
132
|
+
minimal: null,
|
|
133
|
+
low: efforts.includes("low") ? "low" : null,
|
|
134
|
+
medium: efforts.includes("medium") ? "medium" : null,
|
|
135
|
+
high: efforts.includes("high") ? "high" : null,
|
|
136
|
+
xhigh: efforts.includes("xhigh") ? "xhigh" : null,
|
|
137
|
+
max: efforts.includes("max") ? "max" : null
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
}
|
|
65
141
|
/** Build one pi-ai model descriptor pointing at the loopback shim. */
|
|
66
142
|
function toPiModel(info, baseUrl) {
|
|
67
143
|
return {
|
|
@@ -70,7 +146,8 @@ function toPiModel(info, baseUrl) {
|
|
|
70
146
|
api: "openai-completions",
|
|
71
147
|
provider: WORKBUDDY_PROVIDER,
|
|
72
148
|
baseUrl,
|
|
73
|
-
input: ["text"],
|
|
149
|
+
input: info.supportsImages === true ? ["text", "image"] : ["text"],
|
|
150
|
+
...reasoningFields(info),
|
|
74
151
|
cost: NO_COST,
|
|
75
152
|
contextWindow: info.contextWindow,
|
|
76
153
|
maxTokens: info.maxTokens
|
|
@@ -117,7 +194,7 @@ function createWorkBuddyAdapter(options) {
|
|
|
117
194
|
};
|
|
118
195
|
let profiles = /* @__PURE__ */ new Map([[WORKBUDDY_PROVIDER, profile]]);
|
|
119
196
|
return {
|
|
120
|
-
adapter: new
|
|
197
|
+
adapter: new WorkBuddyPiAiAdapter(catalog, {
|
|
121
198
|
profiles: () => profiles,
|
|
122
199
|
auth: INERT_AUTH,
|
|
123
200
|
resolveApiKey: async () => shim.token(),
|
|
@@ -128,6 +205,56 @@ function createWorkBuddyAdapter(options) {
|
|
|
128
205
|
}
|
|
129
206
|
};
|
|
130
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* The WorkBuddy route's adapter: `PiAiAdapter` with the billing rate folded
|
|
210
|
+
* into the catalog answers it returns to the DSH model pickers.
|
|
211
|
+
*
|
|
212
|
+
* `PiAiAdapter.listModels()` and `.resolveModel()` build their answers straight
|
|
213
|
+
* from the pi-ai descriptors, which carry no billing fact, so the rate is
|
|
214
|
+
* layered on here by looking the model up in the live catalog. Both overrides
|
|
215
|
+
* delegate to `super` and then rewrite only the display fields, so streaming,
|
|
216
|
+
* capability resolution, and effort mapping stay exactly as `dsh-llm-pi-ai`
|
|
217
|
+
* implements them.
|
|
218
|
+
*
|
|
219
|
+
* A model missing from the catalog (an id the shim would serve but the last
|
|
220
|
+
* upstream refresh did not list) falls through with its name untouched rather
|
|
221
|
+
* than being dropped: catalog membership is advisory, and the seam tolerates
|
|
222
|
+
* serving an unlisted id.
|
|
223
|
+
*/
|
|
224
|
+
var WorkBuddyPiAiAdapter = class extends PiAiAdapter {
|
|
225
|
+
catalog;
|
|
226
|
+
constructor(catalog, options) {
|
|
227
|
+
super(options);
|
|
228
|
+
this.catalog = catalog;
|
|
229
|
+
}
|
|
230
|
+
/** Catalog entry for one model id, or undefined when the catalog omits it. */
|
|
231
|
+
infoFor(model) {
|
|
232
|
+
return this.catalog.current().find((entry) => entry.id === model);
|
|
233
|
+
}
|
|
234
|
+
async listModels(provider) {
|
|
235
|
+
return (await super.listModels(provider)).map((model) => {
|
|
236
|
+
const info = this.infoFor(model.id);
|
|
237
|
+
if (info === void 0) return model;
|
|
238
|
+
const promo = promoDescription(info);
|
|
239
|
+
return {
|
|
240
|
+
...model,
|
|
241
|
+
name: withRate(model.name, info),
|
|
242
|
+
...promo === void 0 ? {} : { description: promo }
|
|
243
|
+
};
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
async resolveModel(provider, model, signal) {
|
|
247
|
+
const resolved = await super.resolveModel(provider, model, signal);
|
|
248
|
+
const info = this.infoFor(model);
|
|
249
|
+
if (info === void 0) return resolved;
|
|
250
|
+
const promo = promoDescription(info);
|
|
251
|
+
return {
|
|
252
|
+
...resolved,
|
|
253
|
+
name: withRate(resolved.name, info),
|
|
254
|
+
...promo === void 0 ? {} : { description: promo }
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
};
|
|
131
258
|
//#endregion
|
|
132
259
|
//#region src/shim.ts
|
|
133
260
|
/**
|
|
@@ -402,22 +529,36 @@ async function workBuddyWebStatus(deps) {
|
|
|
402
529
|
...authStatus.source === void 0 ? {} : { source: authStatus.source },
|
|
403
530
|
...authStatus.expiresAtMs === void 0 ? {} : { expiresAt: authStatus.expiresAtMs }
|
|
404
531
|
};
|
|
532
|
+
const modelsField = deps.models().filter((model) => model.billing?.free === true || (model.billing?.badges?.length ?? 0) > 0).map((model) => {
|
|
533
|
+
const rate = normalizeCredits(model.billing?.credits);
|
|
534
|
+
return {
|
|
535
|
+
id: model.id,
|
|
536
|
+
name: model.name,
|
|
537
|
+
...model.billing?.free === true ? { free: true } : {},
|
|
538
|
+
...model.billing?.badges !== void 0 && model.billing.badges.length > 0 ? { badges: model.billing.badges } : {},
|
|
539
|
+
...rate === void 0 ? {} : { credits: rate }
|
|
540
|
+
};
|
|
541
|
+
});
|
|
542
|
+
const statusWithModels = modelsField.length > 0 ? {
|
|
543
|
+
...status,
|
|
544
|
+
models: modelsField
|
|
545
|
+
} : status;
|
|
405
546
|
try {
|
|
406
547
|
const credential = await deps.store.current();
|
|
407
548
|
if (credential !== void 0) {
|
|
408
549
|
const credits = await deps.client.fetchCredits(credential);
|
|
409
550
|
return {
|
|
410
|
-
...
|
|
551
|
+
...statusWithModels,
|
|
411
552
|
credits
|
|
412
553
|
};
|
|
413
554
|
}
|
|
414
555
|
} catch (error) {
|
|
415
556
|
return {
|
|
416
|
-
...
|
|
557
|
+
...statusWithModels,
|
|
417
558
|
creditsError: safeMessage(error)
|
|
418
559
|
};
|
|
419
560
|
}
|
|
420
|
-
return
|
|
561
|
+
return statusWithModels;
|
|
421
562
|
}
|
|
422
563
|
/** Mount the GET status route on an optional webServer context. */
|
|
423
564
|
function registerWorkBuddyStatusRoute(ctx, deps) {
|
|
@@ -476,7 +617,8 @@ function apply(ctx, config) {
|
|
|
476
617
|
});
|
|
477
618
|
ctx.inject(["webServer"], (webCtx) => registerWorkBuddyStatusRoute(webCtx, {
|
|
478
619
|
store,
|
|
479
|
-
client
|
|
620
|
+
client,
|
|
621
|
+
models: () => catalog.current()
|
|
480
622
|
}));
|
|
481
623
|
let current = () => config;
|
|
482
624
|
installSettingsSection(ctx, WORKBUDDY_SETTINGS_NS, Config, config, {
|
|
@@ -553,4 +695,4 @@ function apply(ctx, config) {
|
|
|
553
695
|
});
|
|
554
696
|
}
|
|
555
697
|
//#endregion
|
|
556
|
-
export { Config, FALLBACK_WORKBUDDY_MODELS, WORKBUDDY_AUTH_FILENAME, WORKBUDDY_AUTH_FILE_ENV, WORKBUDDY_HOST_HEARTBEAT_FILENAME, WORKBUDDY_PROVIDER, WORKBUDDY_SETTINGS_NS, WORKBUDDY_STREAM_IDLE_TIMEOUT_MS, WorkBuddyCatalog, WorkBuddyCredentialStore, WorkBuddyUpstreamClient, apply, classifyUpstreamError, clearHostHeartbeat, createWorkBuddyAdapter, createWorkBuddyShim, defaultDesktopAuthCandidates, defaultDesktopAuthPath, inject, isHeartbeatProcessAlive, name, parseWorkBuddyAuth, prepareChatBody, processStartTimeMs, readHostHeartbeat, regionOf, workbuddyHostHeartbeatPath, workbuddyOwnAuthPath };
|
|
698
|
+
export { Config, FALLBACK_WORKBUDDY_MODELS, WORKBUDDY_AUTH_FILENAME, WORKBUDDY_AUTH_FILE_ENV, WORKBUDDY_HOST_HEARTBEAT_FILENAME, WORKBUDDY_PROVIDER, WORKBUDDY_SETTINGS_NS, WORKBUDDY_STREAM_IDLE_TIMEOUT_MS, WorkBuddyCatalog, WorkBuddyCredentialStore, WorkBuddyUpstreamClient, apply, classifyUpstreamError, clearHostHeartbeat, createWorkBuddyAdapter, createWorkBuddyShim, defaultDesktopAuthCandidates, defaultDesktopAuthPath, inject, isHeartbeatProcessAlive, name, normalizeCredits, parseWorkBuddyAuth, prepareChatBody, processStartTimeMs, readHostHeartbeat, regionOf, workbuddyHostHeartbeatPath, workbuddyOwnAuthPath };
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "dsh-workbuddy-connect",
|
|
3
3
|
"displayName": "DSH WorkBuddy Connect",
|
|
4
4
|
"description": "将 WorkBuddy 桌面 App 包含的模型自动接入 DeepSeek Harness — bring WorkBuddy desktop app models into DeepSeek Harness with zero configuration.",
|
|
5
|
-
"version": "0.2.
|
|
5
|
+
"version": "0.2.6",
|
|
6
6
|
"author": "corrinehu",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"dsh-plugin",
|