dsh-workbuddy-connect 0.2.5 → 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 CHANGED
@@ -12,6 +12,12 @@ Brings every model in the WorkBuddy desktop app (GLM-5.3, GLM-5.2, DeepSeek-V4-P
12
12
 
13
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
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
+
15
21
  - **Info at a glance**: Settings → Plugins → DSH WorkBuddy Connect card
16
22
 
17
23
  ![Settings card showing the plugin](assets/2.png)
package/README.md CHANGED
@@ -18,6 +18,15 @@
18
18
  - **图片输入**:按上游逐模型声明的能力放行图片——绝大多数模型(含 GLM-5.3-Flash、GLM-5.2、DeepSeek-V4 系列等)可直接粘贴或拖入图片;个别纯文本模型(如 GLM-5.1)按上游声明仍会明确提示不支持。
19
19
 
20
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
+
21
30
  - **信息查看**:设置 → 插件 → DSH WorkBuddy Connect 卡片
22
31
 
23
32
 
package/lib/bin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { _ as WorkBuddyCredentialStore, a as readHostHeartbeat, c as WORKBUDDY_CONNECT_VERSION, l as WorkBuddyUpstreamClient, o as workbuddyHostHeartbeatPath, p as FALLBACK_WORKBUDDY_MODELS, r as isHeartbeatProcessAlive, x as workbuddyOwnAuthPath } from "./host-heartbeat-1MO2dZrM.js";
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,10 +314,16 @@ var WorkBuddyCredentialStore = class {
314
314
  //#endregion
315
315
  //#region src/catalog.ts
316
316
  /**
317
- * Static CLI models observed on the CN endpoint (2026-08-17; image flags
318
- * re-verified against the live catalog 2026-08-29). The upstream refresh
319
- * replaces this list at startup; it exists so the provider registers with a
320
- * usable catalog even while the first fetch is in flight or offline.
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.
321
327
  */
322
328
  const FALLBACK_WORKBUDDY_MODELS = [
323
329
  {
@@ -325,77 +331,267 @@ const FALLBACK_WORKBUDDY_MODELS = [
325
331
  name: "Auto",
326
332
  contextWindow: 168e3,
327
333
  maxTokens: 32e3,
328
- supportsImages: true
334
+ supportsImages: true,
335
+ reasoning: {
336
+ supports: true,
337
+ onlyReasoning: true,
338
+ defaultEffort: "high",
339
+ canDisableThinking: false
340
+ },
341
+ billing: { free: false }
329
342
  },
330
343
  {
331
344
  id: "hy3",
332
345
  name: "Hy3",
333
346
  contextWindow: 192e3,
334
347
  maxTokens: 64e3,
335
- supportsImages: true
336
- },
337
- {
338
- id: "glm-5v-turbo",
339
- name: "GLM-5v-Turbo",
340
- contextWindow: 2e5,
341
- maxTokens: 64e3,
342
- supportsImages: true
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
+ }
343
360
  },
344
361
  {
345
362
  id: "glm-5.2",
346
363
  name: "GLM-5.2",
347
364
  contextWindow: 1e6,
348
365
  maxTokens: 48e3,
349
- supportsImages: true
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
+ }
350
378
  },
351
379
  {
352
380
  id: "glm-5.1",
353
381
  name: "GLM-5.1",
354
382
  contextWindow: 2e5,
355
383
  maxTokens: 48e3,
356
- supportsImages: false
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
+ }
357
395
  },
358
396
  {
359
- id: "minimax-m3",
360
- name: "MiniMax-M3",
361
- contextWindow: 512e3,
362
- maxTokens: 128e3,
363
- supportsImages: true
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
+ }
364
412
  },
365
413
  {
366
414
  id: "kimi-k3-1",
367
415
  name: "Kimi-K3",
368
416
  contextWindow: 1e6,
369
417
  maxTokens: 32e3,
370
- supportsImages: true
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
+ }
371
429
  },
372
430
  {
373
431
  id: "kimi-k2.7",
374
432
  name: "Kimi-K2.7-Code",
375
433
  contextWindow: 256e3,
376
434
  maxTokens: 32e3,
377
- supportsImages: true
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
+ }
378
446
  },
379
447
  {
380
448
  id: "kimi-k2.6",
381
449
  name: "Kimi-K2.6",
382
450
  contextWindow: 256e3,
383
451
  maxTokens: 32e3,
384
- supportsImages: true
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
+ }
385
480
  },
386
481
  {
387
482
  id: "deepseek-v4-flash",
388
483
  name: "Deepseek-V4-Flash",
389
484
  contextWindow: 1e6,
390
485
  maxTokens: 5e4,
391
- supportsImages: true
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
+ }
392
497
  },
393
498
  {
394
499
  id: "deepseek-v4-pro",
395
500
  name: "Deepseek-V4-Pro",
396
501
  contextWindow: 1e6,
397
502
  maxTokens: 5e4,
398
- supportsImages: true
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
+ }
399
595
  }
400
596
  ];
401
597
  /** Mutable catalog shared by the shim's `/v1/models` and the adapter. */
@@ -436,6 +632,84 @@ const HARD_CREDIT_MARKERS = [
436
632
  "额度用尽",
437
633
  "没有积分"
438
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
+ }
439
713
  /** Session-invalidation markers that mean "sign in again in the WorkBuddy app". */
440
714
  const SESSION_DEAD_MARKERS = ["Offline user session not found", "12153"];
441
715
  /** Classify an upstream failure from its HTTP status and body excerpt. */
@@ -513,8 +787,15 @@ function billingHeaders(credential) {
513
787
  }
514
788
  /**
515
789
  * Normalize an OpenAI chat-completions body for the WorkBuddy upstream:
516
- * force `stream: true` (the upstream rejects non-streaming) and flatten
517
- * `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.
518
799
  */
519
800
  function prepareChatBody(source) {
520
801
  let body;
@@ -526,9 +807,20 @@ function prepareChatBody(source) {
526
807
  if (typeof body !== "object" || body === null || Array.isArray(body)) return source;
527
808
  const obj = body;
528
809
  obj["stream"] = true;
810
+ normalizeDeveloperRole(obj);
529
811
  normalizeToolChoice(obj);
530
812
  return JSON.stringify(obj);
531
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
+ }
532
824
  /** Rewrite OpenAI `tool_choice` spellings into the upstream's string form. */
533
825
  function normalizeToolChoice(obj) {
534
826
  const suppress = () => {
@@ -679,7 +971,9 @@ var WorkBuddyUpstreamClient = class {
679
971
  name: typeof wrapped["name"] === "string" && wrapped["name"] !== "" ? wrapped["name"] : id,
680
972
  contextWindow: input,
681
973
  maxTokens: output,
682
- supportsImages: wrapped["supportsImages"] === true && wrapped["disabledMultimodal"] !== true
974
+ supportsImages: wrapped["supportsImages"] === true && wrapped["disabledMultimodal"] !== true,
975
+ ...resolveUpstreamReasoning(wrapped),
976
+ ...resolveUpstreamBilling(wrapped)
683
977
  });
684
978
  }
685
979
  const models = cliIds.map((id) => byId.get(id)).filter((model) => model !== void 0);
@@ -747,7 +1041,7 @@ var WorkBuddyUpstreamClient = class {
747
1041
  };
748
1042
  //#endregion
749
1043
  //#region src/version.ts
750
- const WORKBUDDY_CONNECT_VERSION = "0.2.5";
1044
+ const WORKBUDDY_CONNECT_VERSION = "0.2.6";
751
1045
  //#endregion
752
1046
  //#region src/host-heartbeat.ts
753
1047
  /**
@@ -890,4 +1184,4 @@ function isHeartbeatProcessAlive(heartbeat) {
890
1184
  return startAtMs <= heartbeat.registeredAt;
891
1185
  }
892
1186
  //#endregion
893
- export { WorkBuddyCredentialStore as _, readHostHeartbeat as a, parseWorkBuddyAuth as b, WORKBUDDY_CONNECT_VERSION as c, prepareChatBody as d, regionOf as f, WORKBUDDY_AUTH_FILE_ENV as g, WORKBUDDY_AUTH_FILENAME as h, processStartTimeMs as i, WorkBuddyUpstreamClient as l, WorkBuddyCatalog as m, clearHostHeartbeat as n, workbuddyHostHeartbeatPath as o, FALLBACK_WORKBUDDY_MODELS as p, isHeartbeatProcessAlive as r, writeHostHeartbeat as s, WORKBUDDY_HOST_HEARTBEAT_FILENAME as t, classifyUpstreamError as u, defaultDesktopAuthCandidates as v, workbuddyOwnAuthPath as x, defaultDesktopAuthPath as y };
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
@@ -19,6 +19,49 @@ interface WorkBuddyUpstreamModel {
19
19
  * admits an image the provider then rejects after the message is durable.
20
20
  */
21
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;
22
65
  }
23
66
  /** One billing package and its remaining credit. */
24
67
  interface WorkBuddyCreditAccount {
@@ -48,14 +91,36 @@ type WorkBuddyChatResult = {
48
91
  kind: UpstreamErrorKind;
49
92
  message: string;
50
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;
51
109
  /** Classify an upstream failure from its HTTP status and body excerpt. */
52
110
  declare function classifyUpstreamError(status: number, body: string): UpstreamErrorKind;
53
111
  /** Region for a login domain; an empty domain means CN (matching upstream tooling). */
54
112
  declare function regionOf(domain: string): WorkBuddyRegion;
55
113
  /**
56
114
  * Normalize an OpenAI chat-completions body for the WorkBuddy upstream:
57
- * force `stream: true` (the upstream rejects non-streaming) and flatten
58
- * `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.
59
124
  */
60
125
  declare function prepareChatBody(source: string): string;
61
126
  /**
@@ -190,10 +255,16 @@ declare class WorkBuddyCredentialStore {
190
255
  /** One model entry the adapter exposes. */
191
256
  type WorkBuddyModelInfo = WorkBuddyUpstreamModel;
192
257
  /**
193
- * Static CLI models observed on the CN endpoint (2026-08-17; image flags
194
- * re-verified against the live catalog 2026-08-29). The upstream refresh
195
- * replaces this list at startup; it exists so the provider registers with a
196
- * usable catalog even while the first fetch is in flight or offline.
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.
197
268
  */
198
269
  declare const FALLBACK_WORKBUDDY_MODELS: readonly WorkBuddyModelInfo[];
199
270
  /** Mutable catalog shared by the shim's `/v1/models` and the adapter. */
@@ -352,4 +423,4 @@ declare const Config: z<Config>;
352
423
  */
353
424
  declare function apply(ctx: Context, config: Config): void;
354
425
  //#endregion
355
- 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 WorkBuddyCredentialStore, a as readHostHeartbeat, b as parseWorkBuddyAuth, d as prepareChatBody, f as regionOf, g as WORKBUDDY_AUTH_FILE_ENV, h as WORKBUDDY_AUTH_FILENAME, i as processStartTimeMs, l as WorkBuddyUpstreamClient, m as WorkBuddyCatalog, n as clearHostHeartbeat, o as workbuddyHostHeartbeatPath, p as FALLBACK_WORKBUDDY_MODELS, r as isHeartbeatProcessAlive, s as writeHostHeartbeat, t as WORKBUDDY_HOST_HEARTBEAT_FILENAME, u as classifyUpstreamError, v as defaultDesktopAuthCandidates, x as workbuddyOwnAuthPath, y as defaultDesktopAuthPath } from "./host-heartbeat-1MO2dZrM.js";
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";
@@ -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 {
@@ -71,6 +147,7 @@ function toPiModel(info, baseUrl) {
71
147
  provider: WORKBUDDY_PROVIDER,
72
148
  baseUrl,
73
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 PiAiAdapter({
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
- ...status,
551
+ ...statusWithModels,
411
552
  credits
412
553
  };
413
554
  }
414
555
  } catch (error) {
415
556
  return {
416
- ...status,
557
+ ...statusWithModels,
417
558
  creditsError: safeMessage(error)
418
559
  };
419
560
  }
420
- return status;
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",
5
+ "version": "0.2.6",
6
6
  "author": "corrinehu",
7
7
  "keywords": [
8
8
  "dsh-plugin",