dsh-workbuddy-connect 0.5.2 → 0.5.4
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 +4 -1
- package/README.md +4 -1
- package/lib/bin.js +10 -3
- package/lib/client.js +130 -30
- package/lib/index.d.ts +74 -19
- package/lib/index.js +92 -37
- package/lib/{variants-CExA7lJt.js → variants-XUStsapF.js} +138 -3
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -22,13 +22,15 @@ Both the CN **WorkBuddy** and the international **WorkBuddy AI** apps are suppor
|
|
|
22
22
|
|
|
23
23
|
- **Status and detection**: Settings → Plugins → the matching card shows the account, token validity, remaining credit, and model offers. It also lets you refresh the model list manually and shows whether the current list came from the upstream or from the built-in fallback, and provides manual reasoning-level detection for eligible models.
|
|
24
24
|
|
|
25
|
+
- **Enterprise credit**: on the CN product, enterprise accounts (non-empty `enterpriseId`) read their cycle quota from the enterprise billing endpoint, and the card shows an "enterprise quota" row with the cycle reset time.
|
|
26
|
+
|
|
25
27
|
- **Rate**: every model name carries its credits multiplier (e.g. `GLM-5.2 · x0.79`, `Hy3 · x0.00`) in both the `/model` popup and the composer's model dropdown. The rate is display-only and never affects requests.
|
|
26
28
|
|
|
27
29
|
- **Promo badges**: promo badges (`限时免费`, `夜间折扣`) ride the model name itself (e.g. `Hy4 preview · x0.00 · 限时免费`), visible wherever you pick a model; the status card also collects currently-discounted models. Per the WorkBuddy service data, synced each time DSH starts. The international version's promotions come from the service's `modelPromotions` (which carry an effective window). Once a promotion lapses its badge is withdrawn; because the service writes the discounted value into the model's own rate field, the original price cannot be reconstructed, so that model then reports "price unavailable — refresh to update" rather than repeating the discounted rate or claiming the model is free.
|
|
28
30
|
|
|
29
31
|

|
|
30
32
|
|
|
31
|
-
The expanded card has three tabs: **Status** shows the account, token validity, total credit, catalog source, and reasoning-level detection; **Context** lists each model's context window (the international version
|
|
33
|
+
The expanded card has three tabs: **Status** shows the account, token validity, total credit, catalog source, and reasoning-level detection; **Context** lists each model's context window (where the international version offers a larger declared window, the "Use the largest declared context window" switch lives here — it is **on by default**, so DSH sizes context compression to the largest window the upstream declares; turn it off to follow the upstream default instead, and the preference persists across restarts); **Details** shows per-package credit and model offers. The CN and international versions each get their own card, showing their own account's information.
|
|
32
34
|
|
|
33
35
|

|
|
34
36
|
|
|
@@ -110,6 +112,7 @@ dsh plugin --profile web exec dsh-workbuddy-connect doctor --provider workbuddy-
|
|
|
110
112
|
- **The international version's model catalog comes from the app's own interface**: the service splits it by User-Agent, which is a private implementation detail that a server-side change can break. When that happens the plugin degrades to this account's last successful catalog and then to its built-in roster, showing the source (live / saved / built-in), the fetch time, and the failure reason on the card — but long-term compatibility is not guaranteed. The CN version's catalog uses the same interface as the official CLI and is unaffected.
|
|
111
113
|
- **International-version environments not yet covered**: on Windows / WSL / Linux no reliable source for the international app's version has been located yet, so the saved value or the built-in default is used. On macOS, real-shim checks covered complete GPT-family replies, tool calls, and continued turns.
|
|
112
114
|
- **Behaviour change with no credentials**: a version whose app was never signed in — and that left no plugin-owned copy — no longer shows a model group. The CN version used to display a built-in fallback list, but every model on it failed when selected.
|
|
115
|
+
- **The enterprise credit path currently covers the CN product only**: the international enterprise billing interface is unverified, so those accounts still read through the personal endpoint pending measurement. The enterprise branch could not be tested locally (the development machine holds a personal account); it was implemented from the official app's interface contract, and reports from enterprise users are welcome.
|
|
113
116
|
- Relies on WorkBuddy client interfaces (not a public API); the plugin may need updates as WorkBuddy changes.
|
|
114
117
|
|
|
115
118
|
## Disclaimer
|
package/README.md
CHANGED
|
@@ -30,6 +30,8 @@
|
|
|
30
30
|
|
|
31
31
|
- **信息查看与检测**:设置 → 插件 → 对应卡片可查看账号、令牌有效期、剩余积分和模型优惠;也可以手动刷新模型列表,并在卡片上看到当前列表来自上游还是内置兜底。对于可检测模型,也可以在这里手动检测推理档位。
|
|
32
32
|
|
|
33
|
+
- **企业账号积分**:国内版企业账号(`enterpriseId` 非空)走企业专用计费接口读取周期额度,卡片显示「企业额度」与周期重置时间。
|
|
34
|
+
|
|
33
35
|
- **费率比例**:模型选择列表里每个模型名后直接显示积分倍率(如 `GLM-5.2 · x0.79`、`Hy3 · x0.00`),`/model` 弹窗与输入框的模型下拉都能看到。倍率只是显示,不影响实际请求。
|
|
34
36
|
|
|
35
37
|
|
|
@@ -37,7 +39,7 @@
|
|
|
37
39
|
|
|
38
40
|

|
|
39
41
|
|
|
40
|
-
卡片展开后分为「状态 / 上下文 /
|
|
42
|
+
卡片展开后分为「状态 / 上下文 / 明细」三个标签:状态页展示账号、令牌有效期、合计积分、模型列表来源与推理档位检测;上下文页列出各模型的上下文窗口。国际版在上游声明了更大可选窗口时,可在这里切换「使用上游声明的最大上下文窗口」;该开关**默认开启**,DSH 会按上游声明的最大窗口安排上下文压缩;想改用上游的默认窗口就在这里关掉,偏好会持久化,重启后保持。明细页展示各套餐余量与模型优惠。国内版与国际版各有一张自己的卡片,各显示自己账号的信息。
|
|
41
43
|
|
|
42
44
|

|
|
43
45
|
|
|
@@ -120,6 +122,7 @@ dsh plugin --profile web exec dsh-workbuddy-connect doctor --provider workbuddy-
|
|
|
120
122
|
- **国际版的模型目录来自 App 界面接口**:服务端按 User-Agent 分流下发,属私有实现,上游改动可能使其失效。届时插件按「本账号上次成功目录 → 内置目录」降级,并在卡片上标明来源(实时 / 已保存 / 内置)、更新时间与失败原因,但不能保证长期兼容。国内版目录走官方 CLI 同款接口,不受此影响。
|
|
121
123
|
- **国际版仍未覆盖的环境**:Windows / WSL / Linux 下国际版 App 的版本读取尚未找到可靠来源,会退回最近保存的版本或内置值。macOS 上已通过真实 shim 验证 GPT 系完整回复、工具调用与续轮。
|
|
122
124
|
- **无凭据时的行为变化**:某版 App 从未登录、也没留下插件自留副本时,该版模型分组不再显示。此前国内版会显示一份内置兜底列表,但那些模型选了必然报错。
|
|
125
|
+
- **企业账号积分目前仅覆盖国内版**:国际版企业账号的计费接口尚未验证,仍按个人版接口读取;待有实测结论后再扩展。企业账号分支在本机无法自测(开发机为个人账号),依据官方 App 的接口契约实现,欢迎企业账号用户反馈实测结果。
|
|
123
126
|
- 依赖 WorkBuddy 客户端接口(非官方开放 API),WorkBuddy 更新后插件可能需要随之调整。
|
|
124
127
|
|
|
125
128
|
## 免责声明
|
package/lib/bin.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { E as WorkBuddyUpstreamClient, J as resolveAppVersion, b as WorkBuddyCredentialStore, f as WORKBUDDY_CONNECT_VERSION, g as FALLBACK_WORKBUDDY_MODELS, h as FALLBACK_WORKBUDDY_AI_MODELS, i as variantFor, l as readHostHeartbeat, n as CN_VARIANT, r as WORKBUDDY_VARIANTS, s as isHeartbeatProcessAlive, u as workbuddyHostHeartbeatPath } from "./variants-
|
|
2
|
+
import { E as WorkBuddyUpstreamClient, J as resolveAppVersion, b as WorkBuddyCredentialStore, f as WORKBUDDY_CONNECT_VERSION, g as FALLBACK_WORKBUDDY_MODELS, h as FALLBACK_WORKBUDDY_AI_MODELS, i as variantFor, l as readHostHeartbeat, n as CN_VARIANT, r as WORKBUDDY_VARIANTS, s as isHeartbeatProcessAlive, u as workbuddyHostHeartbeatPath } from "./variants-XUStsapF.js";
|
|
3
3
|
import { realpathSync } from "node:fs";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
//#region src/bin.ts
|
|
@@ -118,7 +118,13 @@ async function status(jsonOutput, variant) {
|
|
|
118
118
|
let credits;
|
|
119
119
|
try {
|
|
120
120
|
const credential = await store.current();
|
|
121
|
-
if (credential !== void 0)
|
|
121
|
+
if (credential !== void 0) {
|
|
122
|
+
const fetched = await client.fetchCredits(credential);
|
|
123
|
+
credits = {
|
|
124
|
+
total: fetched.total,
|
|
125
|
+
...fetched.unlimited === true ? { unlimited: true } : {}
|
|
126
|
+
};
|
|
127
|
+
}
|
|
122
128
|
} catch (error) {
|
|
123
129
|
credits = {
|
|
124
130
|
total: 0,
|
|
@@ -138,6 +144,7 @@ async function status(jsonOutput, variant) {
|
|
|
138
144
|
...authStatus.domain === void 0 || authStatus.domain === "" ? {} : { domain: authStatus.domain },
|
|
139
145
|
source: authStatus.source,
|
|
140
146
|
credits: credits?.total,
|
|
147
|
+
...credits?.unlimited === true ? { creditsUnlimited: true } : {},
|
|
141
148
|
...credits?.error === void 0 ? {} : { creditsError: credits.error },
|
|
142
149
|
hostBundle: hostState
|
|
143
150
|
});
|
|
@@ -146,7 +153,7 @@ async function status(jsonOutput, variant) {
|
|
|
146
153
|
process.stdout.write([
|
|
147
154
|
`${variant.displayName} Connect: signed in${authStatus.nickname === void 0 ? "" : ` as ${authStatus.nickname}`}`,
|
|
148
155
|
...expiresAt === void 0 ? [] : [`Access token expires ${expiresAt} (refresh is automatic)`],
|
|
149
|
-
credits?.error
|
|
156
|
+
credits?.error !== void 0 ? `Remaining credit: unavailable (${credits.error})` : credits?.unlimited === true ? "Remaining credit: unlimited" : `Remaining credit: ${credits?.total ?? "unknown"}`,
|
|
150
157
|
`Host bundle: ${hostAlive ? `running (pid ${heartbeat.pid})` : hostState === "stale" ? "stale heartbeat (DSH process exited)" : "not started in this profile"}`,
|
|
151
158
|
"Client card: load failures are logged to the browser console only; the host provider is unaffected.",
|
|
152
159
|
""
|
package/lib/client.js
CHANGED
|
@@ -63,15 +63,17 @@ window.__ModuleLoader__.load({
|
|
|
63
63
|
statusPath: WORKBUDDY_STATUS_PATH,
|
|
64
64
|
probePath: WORKBUDDY_PROBE_PATH
|
|
65
65
|
};
|
|
66
|
-
/**
|
|
67
|
-
const
|
|
66
|
+
/** International WorkBuddy AI. */
|
|
67
|
+
const AI_CARD_VARIANT = {
|
|
68
68
|
id: "workbuddy-ai",
|
|
69
69
|
titleKey: "titleAI",
|
|
70
70
|
introKey: "introAI",
|
|
71
71
|
signedOutKey: "signedOutHintAI",
|
|
72
72
|
statusPath: WORKBUDDY_AI_STATUS_PATH,
|
|
73
73
|
probePath: WORKBUDDY_AI_PROBE_PATH
|
|
74
|
-
}
|
|
74
|
+
};
|
|
75
|
+
/** Both cards, in display order. */
|
|
76
|
+
const CARD_VARIANTS = [CN_CARD_VARIANT, AI_CARD_VARIANT];
|
|
75
77
|
const POLL_INTERVAL_MS = 6e4;
|
|
76
78
|
const cardStyle = {
|
|
77
79
|
overflow: "hidden",
|
|
@@ -199,6 +201,22 @@ window.__ModuleLoader__.load({
|
|
|
199
201
|
lineHeight: "18px",
|
|
200
202
|
color: "var(--dsw-alias-label-tertiary)"
|
|
201
203
|
};
|
|
204
|
+
const contextPreferenceStyle = {
|
|
205
|
+
display: "flex",
|
|
206
|
+
alignItems: "flex-start",
|
|
207
|
+
gap: 9,
|
|
208
|
+
padding: "10px 12px",
|
|
209
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
210
|
+
borderRadius: 8,
|
|
211
|
+
color: "var(--dsw-alias-label-primary)",
|
|
212
|
+
fontSize: 13,
|
|
213
|
+
lineHeight: "20px"
|
|
214
|
+
};
|
|
215
|
+
const contextPreferenceCopyStyle = {
|
|
216
|
+
display: "flex",
|
|
217
|
+
flexDirection: "column",
|
|
218
|
+
gap: 2
|
|
219
|
+
};
|
|
202
220
|
const modelBadgeChipStyle = {
|
|
203
221
|
padding: "1px 8px",
|
|
204
222
|
borderRadius: 999,
|
|
@@ -336,6 +354,11 @@ window.__ModuleLoader__.load({
|
|
|
336
354
|
timeStyle: "short"
|
|
337
355
|
}).format(new Date(ms));
|
|
338
356
|
}
|
|
357
|
+
function formatCycleReset(time) {
|
|
358
|
+
const parsed = Date.parse(time);
|
|
359
|
+
if (!Number.isNaN(parsed)) return formatTime(parsed);
|
|
360
|
+
return time;
|
|
361
|
+
}
|
|
339
362
|
/**
|
|
340
363
|
* One billing package as a labeled progress bar.
|
|
341
364
|
*
|
|
@@ -345,7 +368,29 @@ window.__ModuleLoader__.load({
|
|
|
345
368
|
* honest "remaining N" line printed below it. Unknown size therefore renders the
|
|
346
369
|
* percent slot as unknown copy and an unfilled, indeterminate track.
|
|
347
370
|
*/
|
|
348
|
-
function CreditBar({ label, remain, size, t }) {
|
|
371
|
+
function CreditBar({ label, remain, size, unlimited, t }) {
|
|
372
|
+
if (unlimited === true) {
|
|
373
|
+
const quotaText = t("unlimitedQuota");
|
|
374
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
375
|
+
style: quotaGroupStyle,
|
|
376
|
+
children: [
|
|
377
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
378
|
+
style: quotaLabelStyle,
|
|
379
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: quotaText })]
|
|
380
|
+
}),
|
|
381
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
382
|
+
style: progressTrackStyle,
|
|
383
|
+
role: "progressbar",
|
|
384
|
+
"aria-label": label,
|
|
385
|
+
"aria-valuetext": quotaText
|
|
386
|
+
}),
|
|
387
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
388
|
+
style: bodyStyle,
|
|
389
|
+
children: quotaText
|
|
390
|
+
})
|
|
391
|
+
]
|
|
392
|
+
});
|
|
393
|
+
}
|
|
349
394
|
const sizeKnown = size > 0;
|
|
350
395
|
const detail = sizeKnown ? t("exactRemaining", {
|
|
351
396
|
remain: formatNumber(remain),
|
|
@@ -425,31 +470,56 @@ window.__ModuleLoader__.load({
|
|
|
425
470
|
* the default is the budget actually requested, while the larger value is a
|
|
426
471
|
* ceiling the upstream would accept.
|
|
427
472
|
*/
|
|
428
|
-
function ContextTable({ models, t }) {
|
|
473
|
+
function ContextTable({ models, t, useMaximumContextWindow, disabled, onUseMaximumContextWindow }) {
|
|
429
474
|
const known = (models ?? []).filter((model) => model.contextWindow !== void 0).sort((a, b) => b.contextWindow - a.contextWindow);
|
|
430
|
-
|
|
475
|
+
const canSelectMaximum = known.some((model) => model.maxContextWindow !== void 0 && model.maxContextWindow > (model.defaultContextWindow ?? model.contextWindow ?? 0));
|
|
476
|
+
const showPreference = onUseMaximumContextWindow !== void 0 && (canSelectMaximum || useMaximumContextWindow === true);
|
|
477
|
+
if (known.length === 0 && !showPreference) return null;
|
|
431
478
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
432
479
|
style: quotaListStyle,
|
|
433
|
-
children: [
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
}
|
|
480
|
+
children: [
|
|
481
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
482
|
+
style: quotaTitleStyle,
|
|
483
|
+
children: t("contextHeading")
|
|
484
|
+
}),
|
|
485
|
+
showPreference && onUseMaximumContextWindow !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
486
|
+
style: contextPreferenceStyle,
|
|
487
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
488
|
+
type: "checkbox",
|
|
489
|
+
checked: useMaximumContextWindow === true,
|
|
490
|
+
disabled,
|
|
491
|
+
onChange: (event) => {
|
|
492
|
+
onUseMaximumContextWindow(event.currentTarget.checked);
|
|
493
|
+
}
|
|
494
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
495
|
+
style: contextPreferenceCopyStyle,
|
|
496
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("useMaximumContextWindow") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
447
497
|
style: modelRateStyle,
|
|
448
|
-
children: t("
|
|
498
|
+
children: t("useMaximumContextWindowHint")
|
|
449
499
|
})]
|
|
450
500
|
})]
|
|
451
|
-
},
|
|
452
|
-
|
|
501
|
+
}) : null,
|
|
502
|
+
known.map((model) => {
|
|
503
|
+
const capacity = model.contextWindow;
|
|
504
|
+
const alternative = model.maxContextWindow !== void 0 && model.maxContextWindow > capacity ? model.maxContextWindow : void 0;
|
|
505
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
506
|
+
style: quotaLabelStyle,
|
|
507
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: model.name }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
508
|
+
style: modelOfferStyle,
|
|
509
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
510
|
+
style: { textAlign: "right" },
|
|
511
|
+
children: formatTokens(capacity)
|
|
512
|
+
}), alternative !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
513
|
+
style: modelRateStyle,
|
|
514
|
+
children: t("contextUpTo", { size: formatTokens(alternative) })
|
|
515
|
+
}) : model.defaultContextWindow !== void 0 && model.defaultContextWindow < capacity ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
516
|
+
style: modelRateStyle,
|
|
517
|
+
children: t("contextDefault", { size: formatTokens(model.defaultContextWindow) })
|
|
518
|
+
}) : null]
|
|
519
|
+
})]
|
|
520
|
+
}, model.id);
|
|
521
|
+
})
|
|
522
|
+
]
|
|
453
523
|
});
|
|
454
524
|
}
|
|
455
525
|
/**
|
|
@@ -791,6 +861,10 @@ window.__ModuleLoader__.load({
|
|
|
791
861
|
const message = typeof value === "object" && value !== null && "error" in value ? String(value["error"]) : `HTTP ${response.status}`;
|
|
792
862
|
throw new Error(message);
|
|
793
863
|
}
|
|
864
|
+
if (action.action === "set-maximum-context-window" && (typeof value !== "object" || value === null || value["state"] !== "updated")) {
|
|
865
|
+
const reason = typeof value === "object" && value !== null && "reason" in value ? String(value["reason"]) : t("requestFailed");
|
|
866
|
+
throw new Error(reason);
|
|
867
|
+
}
|
|
794
868
|
await refresh(controller.signal);
|
|
795
869
|
} catch (error) {
|
|
796
870
|
if (mounted.current && controller.signal.aborted !== true) setReadFailure(error instanceof Error ? error.message : t("requestFailed"));
|
|
@@ -923,18 +997,21 @@ window.__ModuleLoader__.load({
|
|
|
923
997
|
tab === "status" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
924
998
|
style: tabPanelStyle,
|
|
925
999
|
children: [
|
|
926
|
-
status.credits === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.
|
|
1000
|
+
status.credits === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
927
1001
|
style: quotaListStyle,
|
|
928
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1002
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
929
1003
|
style: rowStyle,
|
|
930
1004
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
931
1005
|
style: quotaTitleStyle,
|
|
932
1006
|
children: t("creditsHeading")
|
|
933
1007
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
934
1008
|
style: bodyStyle,
|
|
935
|
-
children: t("creditsTotal", { total: formatNumber(status.credits.total) })
|
|
1009
|
+
children: status.credits.unlimited === true ? t("creditsTotalUnlimited") : t("creditsTotal", { total: formatNumber(status.credits.total) })
|
|
936
1010
|
})]
|
|
937
|
-
})
|
|
1011
|
+
}), status.credits.cycleResetTime === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1012
|
+
style: descriptionStyle,
|
|
1013
|
+
children: t("cycleResetAt", { time: formatCycleReset(status.credits.cycleResetTime) })
|
|
1014
|
+
})]
|
|
938
1015
|
}),
|
|
939
1016
|
status.creditsError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
940
1017
|
style: errorStyle,
|
|
@@ -954,7 +1031,15 @@ window.__ModuleLoader__.load({
|
|
|
954
1031
|
style: tabPanelStyle,
|
|
955
1032
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ContextTable, {
|
|
956
1033
|
models: status.models,
|
|
957
|
-
t
|
|
1034
|
+
t,
|
|
1035
|
+
disabled: busy,
|
|
1036
|
+
...status.useMaximumContextWindow === void 0 ? {} : { useMaximumContextWindow: status.useMaximumContextWindow },
|
|
1037
|
+
...variant.id === AI_CARD_VARIANT.id ? { onUseMaximumContextWindow: (enabled) => {
|
|
1038
|
+
control({
|
|
1039
|
+
action: "set-maximum-context-window",
|
|
1040
|
+
enabled
|
|
1041
|
+
});
|
|
1042
|
+
} } : {}
|
|
958
1043
|
})
|
|
959
1044
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
960
1045
|
style: tabPanelStyle,
|
|
@@ -963,10 +1048,11 @@ window.__ModuleLoader__.load({
|
|
|
963
1048
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
964
1049
|
style: quotaTitleStyle,
|
|
965
1050
|
children: t("creditsDetailHeading")
|
|
966
|
-
}), status.credits.accounts.filter((account) => account.remain > 0).map((account, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CreditBar, {
|
|
967
|
-
label: account.packageName,
|
|
1051
|
+
}), status.credits.accounts.filter((account) => account.packageName === "enterprise" || account.remain > 0 || account.unlimited === true).map((account, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CreditBar, {
|
|
1052
|
+
label: account.packageName === "enterprise" ? t("packageEnterprise") : account.packageName,
|
|
968
1053
|
remain: account.remain,
|
|
969
1054
|
size: account.size,
|
|
1055
|
+
unlimited: account.unlimited,
|
|
970
1056
|
t
|
|
971
1057
|
}, `${account.packageName}-${String(index)}`))]
|
|
972
1058
|
}), status.models === void 0 || status.models.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -1445,6 +1531,10 @@ window.__ModuleLoader__.load({
|
|
|
1445
1531
|
tabDetails: "Credit details",
|
|
1446
1532
|
creditsDetailHeading: "By package",
|
|
1447
1533
|
creditsTotal: "Total: {total}",
|
|
1534
|
+
creditsTotalUnlimited: "Total: Unlimited",
|
|
1535
|
+
unlimitedQuota: "Unlimited",
|
|
1536
|
+
packageEnterprise: "Enterprise quota",
|
|
1537
|
+
cycleResetAt: "Resets {time}",
|
|
1448
1538
|
percentRemaining: "{percent}% remaining",
|
|
1449
1539
|
percentUnknown: "Remaining share unknown",
|
|
1450
1540
|
exactRemaining: "{remain} / {size} remaining",
|
|
@@ -1466,6 +1556,9 @@ window.__ModuleLoader__.load({
|
|
|
1466
1556
|
modelsHeading: "Model offers",
|
|
1467
1557
|
contextHeading: "Context window",
|
|
1468
1558
|
contextUpTo: "up to {size}",
|
|
1559
|
+
contextDefault: "default {size}",
|
|
1560
|
+
useMaximumContextWindow: "Use the largest declared context window",
|
|
1561
|
+
useMaximumContextWindowHint: "Applies to WorkBuddy AI models that offer a larger window.",
|
|
1469
1562
|
freeModel: "Free",
|
|
1470
1563
|
badgeLimitedFree: "Limited-time free",
|
|
1471
1564
|
badgeNightDiscount: "Night discount",
|
|
@@ -1521,6 +1614,10 @@ window.__ModuleLoader__.load({
|
|
|
1521
1614
|
tabDetails: "积分详情",
|
|
1522
1615
|
creditsDetailHeading: "按套餐",
|
|
1523
1616
|
creditsTotal: "合计:{total}",
|
|
1617
|
+
creditsTotalUnlimited: "合计:不限额",
|
|
1618
|
+
unlimitedQuota: "不限额",
|
|
1619
|
+
packageEnterprise: "企业额度",
|
|
1620
|
+
cycleResetAt: "重置时间:{time}",
|
|
1524
1621
|
percentRemaining: "剩余 {percent}%",
|
|
1525
1622
|
percentUnknown: "剩余占比未知",
|
|
1526
1623
|
exactRemaining: "剩余 {remain} / {size}",
|
|
@@ -1542,6 +1639,9 @@ window.__ModuleLoader__.load({
|
|
|
1542
1639
|
modelsHeading: "模型优惠",
|
|
1543
1640
|
contextHeading: "上下文窗口",
|
|
1544
1641
|
contextUpTo: "最高 {size}",
|
|
1642
|
+
contextDefault: "默认 {size}",
|
|
1643
|
+
useMaximumContextWindow: "使用上游声明的最大上下文窗口",
|
|
1644
|
+
useMaximumContextWindowHint: "仅作用于 WorkBuddy AI 中声明了更大窗口的模型。",
|
|
1545
1645
|
freeModel: "免费",
|
|
1546
1646
|
badgeLimitedFree: "限时免费",
|
|
1547
1647
|
badgeNightDiscount: "夜间折扣",
|
package/lib/index.d.ts
CHANGED
|
@@ -225,6 +225,8 @@ interface WorkBuddyUpstreamModel {
|
|
|
225
225
|
id: string;
|
|
226
226
|
name: string;
|
|
227
227
|
contextWindow: number;
|
|
228
|
+
/** The upstream's preferred window before an optional maximum is selected. */
|
|
229
|
+
defaultContextWindow?: number;
|
|
228
230
|
maxInputTokens?: number;
|
|
229
231
|
supportedContextWindows?: readonly number[];
|
|
230
232
|
promotions?: readonly WorkBuddyPromotion[];
|
|
@@ -295,11 +297,24 @@ interface WorkBuddyCreditAccount {
|
|
|
295
297
|
packageName: string;
|
|
296
298
|
remain: number;
|
|
297
299
|
size: number;
|
|
300
|
+
unlimited?: true;
|
|
298
301
|
}
|
|
299
302
|
/** Aggregated credit answer for one credential. */
|
|
300
303
|
interface WorkBuddyCredits {
|
|
301
304
|
total: number;
|
|
302
305
|
accounts: readonly WorkBuddyCreditAccount[];
|
|
306
|
+
/**
|
|
307
|
+
* The account's cycle quota is uncapped (`limitNum === -1` on the CN
|
|
308
|
+
* enterprise endpoint).
|
|
309
|
+
*
|
|
310
|
+
* A separate flag rather than a `-1`/`0` sentinel in {@link total}: the two
|
|
311
|
+
* mean opposite things to a reader ("no limit" vs "nothing left"), and the
|
|
312
|
+
* existing negative-clamp in the personal branch would turn a sentinel into
|
|
313
|
+
* a plausible-looking zero. Every renderer must therefore test this flag
|
|
314
|
+
* first and not fall back to `total` when it is set.
|
|
315
|
+
*/
|
|
316
|
+
unlimited?: true;
|
|
317
|
+
cycleResetTime?: string;
|
|
303
318
|
}
|
|
304
319
|
/** Token refresh answer; fields the upstream omits stay absent. */
|
|
305
320
|
interface WorkBuddyRefreshOutcome {
|
|
@@ -409,8 +424,41 @@ declare class WorkBuddyUpstreamClient {
|
|
|
409
424
|
* such rather than as a generic catalog failure.
|
|
410
425
|
*/
|
|
411
426
|
fetchModels(credential: WorkBuddyCredential, signal?: AbortSignal): Promise<readonly WorkBuddyUpstreamModel[]>;
|
|
412
|
-
/**
|
|
427
|
+
/**
|
|
428
|
+
* POST the billing endpoint for the aggregated remaining credit.
|
|
429
|
+
*
|
|
430
|
+
* Two upstream shapes, chosen by account type:
|
|
431
|
+
*
|
|
432
|
+
* - **CN enterprise** (`regionOf === 'cn'` and `enterpriseId` non-empty) asks
|
|
433
|
+
* `/v2/billing/meter/get-enterprise-user-usage`, which answers with a single
|
|
434
|
+
* cycle quota. The personal endpoint serves these accounts an empty
|
|
435
|
+
* `Accounts` list, which the card then renders as "0 credit" — a wrong
|
|
436
|
+
* number rather than a visible failure (issue #31).
|
|
437
|
+
* - **Everyone else** keeps the personal endpoint unchanged.
|
|
438
|
+
*
|
|
439
|
+
* The region gate is load-bearing: the enterprise endpoint is unverified for
|
|
440
|
+
* the global region, so an international credential that happens to carry an
|
|
441
|
+
* `enterpriseId` must stay on the measured personal path instead of being
|
|
442
|
+
* moved onto an unmeasured one.
|
|
443
|
+
*/
|
|
413
444
|
fetchCredits(credential: WorkBuddyCredential): Promise<WorkBuddyCredits>;
|
|
445
|
+
/**
|
|
446
|
+
* CN enterprise credit read: a single cycle quota instead of a package list.
|
|
447
|
+
*
|
|
448
|
+
* Verified against the WorkBuddy desktop app (`app.asar`,
|
|
449
|
+
* `BackendProvider.getEnterpriseUsage` and `CloudAccountRepo.billing`): the
|
|
450
|
+
* body is an empty object and the account identity travels only in the
|
|
451
|
+
* headers. The two official call sites disagree on the field spelling
|
|
452
|
+
* (`limitNum`/`credit` vs `limit_num`/`used_num`), so both are accepted.
|
|
453
|
+
*
|
|
454
|
+
* A body carrying no recognisable quota field is a hard error rather than a
|
|
455
|
+
* zero. Rendering `0` for "we did not understand the answer" is exactly how
|
|
456
|
+
* issue #31 stayed invisible while users saw a plausible wrong number.
|
|
457
|
+
*
|
|
458
|
+
* The error names fields and types only: it reaches the browser, and the
|
|
459
|
+
* response body may describe the account's usage.
|
|
460
|
+
*/
|
|
461
|
+
private fetchEnterpriseCredits;
|
|
414
462
|
/**
|
|
415
463
|
* One probe request: a real streaming chat call carrying the effort under
|
|
416
464
|
* test.
|
|
@@ -704,6 +752,7 @@ declare const FALLBACK_WORKBUDDY_AI_MODELS: readonly WorkBuddyModelInfo[];
|
|
|
704
752
|
declare class WorkBuddyCatalog {
|
|
705
753
|
private models;
|
|
706
754
|
private visible;
|
|
755
|
+
private useMaximumContextWindow;
|
|
707
756
|
constructor(initial?: readonly WorkBuddyModelInfo[]);
|
|
708
757
|
/** Current entries; empty while the variant has no usable credential. */
|
|
709
758
|
current(): readonly WorkBuddyModelInfo[];
|
|
@@ -716,6 +765,8 @@ declare class WorkBuddyCatalog {
|
|
|
716
765
|
* caller can skip an invalidation that would re-render an identical list.
|
|
717
766
|
*/
|
|
718
767
|
setVisible(visible: boolean): boolean;
|
|
768
|
+
/** Select the largest declared international window where the upstream offers one. */
|
|
769
|
+
setUseMaximumContextWindow(useMaximum: boolean): boolean;
|
|
719
770
|
/** Models to fall back to when the upstream fetch fails; ignores visibility. */
|
|
720
771
|
fallback(): readonly WorkBuddyModelInfo[];
|
|
721
772
|
}
|
|
@@ -750,12 +801,12 @@ interface WorkBuddyProbeRecord {
|
|
|
750
801
|
*
|
|
751
802
|
* An effort set is a fact about one account's entitlement as much as about
|
|
752
803
|
* the model: the same model id can accept different levels under a different
|
|
753
|
-
* subscription.
|
|
754
|
-
*
|
|
755
|
-
*
|
|
756
|
-
*
|
|
804
|
+
* subscription. Records are stored under this identity and only ever served
|
|
805
|
+
* back to it, so one account never inherits another's detected levels — and
|
|
806
|
+
* because the store nests by this identity, switching back finds this
|
|
807
|
+
* account's own records intact rather than re-probing from scratch.
|
|
757
808
|
*/
|
|
758
|
-
account
|
|
809
|
+
account: string;
|
|
759
810
|
}
|
|
760
811
|
/**
|
|
761
812
|
* Plugin-owned probe record path inside the Harness home.
|
|
@@ -788,8 +839,9 @@ interface WorkBuddyProbeStoreOptions {
|
|
|
788
839
|
now?: () => number;
|
|
789
840
|
}
|
|
790
841
|
/**
|
|
791
|
-
* The plugin's probe records: read once, written atomically,
|
|
792
|
-
*
|
|
842
|
+
* The plugin's probe records: read once, written atomically, keyed by the
|
|
843
|
+
* account that produced each observation, and never trusted across a
|
|
844
|
+
* fingerprint change or past the TTL.
|
|
793
845
|
*/
|
|
794
846
|
declare class WorkBuddyProbeStore {
|
|
795
847
|
private readonly path;
|
|
@@ -802,24 +854,25 @@ declare class WorkBuddyProbeStore {
|
|
|
802
854
|
filePath(): string;
|
|
803
855
|
private load;
|
|
804
856
|
/**
|
|
805
|
-
* The usable record for
|
|
806
|
-
* expired, it was taken against a different catalog row, or it
|
|
807
|
-
* different account.
|
|
857
|
+
* The usable record for one account and model, or `undefined` when there is
|
|
858
|
+
* none, it is expired, it was taken against a different catalog row, or it
|
|
859
|
+
* belongs to a different account.
|
|
808
860
|
*
|
|
809
861
|
* @param account - the account in effect, as `uid:enterpriseId`. Records are
|
|
810
862
|
* only returned for the account that produced them.
|
|
811
863
|
*/
|
|
812
864
|
get(modelId: string, fingerprint: string, account: string): WorkBuddyProbeRecord | undefined;
|
|
813
865
|
/**
|
|
814
|
-
* Store one observation. Only a decisive
|
|
815
|
-
* `non-validating`) replaces an existing decisive
|
|
816
|
-
* `unknown` must not erase
|
|
866
|
+
* Store one observation under the account stamped on it. Only a decisive
|
|
867
|
+
* answer (`validating` / `non-validating`) replaces an existing decisive
|
|
868
|
+
* record *of the same account*: a transient `unknown` must not erase
|
|
869
|
+
* knowledge the user already paid for.
|
|
817
870
|
*/
|
|
818
871
|
set(modelId: string, record: WorkBuddyProbeRecord): void;
|
|
819
|
-
/** Drop every record; used by the card's explicit "clear" action. */
|
|
872
|
+
/** Drop every record of every account; used by the card's explicit "clear" action. */
|
|
820
873
|
clear(): void;
|
|
821
|
-
/** Every record currently held, for status display. */
|
|
822
|
-
all(): Readonly<Record<string, WorkBuddyProbeRecord
|
|
874
|
+
/** Every record currently held, grouped by account, for status display. */
|
|
875
|
+
all(): Readonly<Record<string, Readonly<Record<string, WorkBuddyProbeRecord>>>>;
|
|
823
876
|
/** Build a record stamped with this store's clock, version, and account. */
|
|
824
877
|
record(fingerprint: string, validation: WorkBuddyProbeValidation, efforts: readonly WorkBuddyEffort[], account: string): WorkBuddyProbeRecord;
|
|
825
878
|
/**
|
|
@@ -981,8 +1034,8 @@ interface WorkBuddyProbeServiceOptions {
|
|
|
981
1034
|
* Records are read and written against this identity, and it is re-checked
|
|
982
1035
|
* after the sweep finishes: an observation produced under account A must not
|
|
983
1036
|
* be stored once account B is in effect, however long the probe took. The
|
|
984
|
-
*
|
|
985
|
-
*
|
|
1037
|
+
* store's per-account keying alone cannot catch that, because an in-flight
|
|
1038
|
+
* probe completes *after* the switch has already happened.
|
|
986
1039
|
*/
|
|
987
1040
|
account: () => string | undefined;
|
|
988
1041
|
sentinel?: SentinelFactory;
|
|
@@ -1126,6 +1179,8 @@ interface Config {
|
|
|
1126
1179
|
* until the user explicitly agrees.
|
|
1127
1180
|
*/
|
|
1128
1181
|
probeConsent?: boolean;
|
|
1182
|
+
/** Use the largest context window the international catalog explicitly offers. */
|
|
1183
|
+
useMaximumContextWindow?: boolean;
|
|
1129
1184
|
}
|
|
1130
1185
|
declare const Config: z<Config>;
|
|
1131
1186
|
/**
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as parseModelCatalog, B as fallbackChatIdentity, C as desktopAuthCandidatesFor, D as classifyUpstreamError, E as WorkBuddyUpstreamClient, F as probeModel, G as appUserAgent, H as resolveChatIdentity, I as randomSentinel, J as resolveAppVersion, K as installedAppVersion, L as CN_APP_VERSION_FILENAME, M as prepareInternationalChatBody, N as regionOf, O as modelWithCurrentPromotion, P as PROBE_EFFORT_CANDIDATES, R as FALLBACK_CN_APP_VERSION, S as defaultDesktopAuthPath, T as workbuddyOwnAuthPath, U as validCliVersion, V as readCliVersion, W as WORKBUDDY_APP_VERSION_FILENAME, Y as validAppVersion, _ as WorkBuddyCatalog, a as WORKBUDDY_HOST_HEARTBEAT_FILENAME, b as WorkBuddyCredentialStore, c as processStartTimeMs, d as writeHostHeartbeat, f as WORKBUDDY_CONNECT_VERSION, g as FALLBACK_WORKBUDDY_MODELS, h as FALLBACK_WORKBUDDY_AI_MODELS, i as variantFor, j as prepareChatBody, k as normalizeCredits, l as readHostHeartbeat, n as CN_VARIANT, o as clearHostHeartbeat, q as readBundleVersion, r as WORKBUDDY_VARIANTS, s as isHeartbeatProcessAlive, t as AI_VARIANT, u as workbuddyHostHeartbeatPath, v as WORKBUDDY_AUTH_FILENAME, w as parseWorkBuddyAuth, x as defaultDesktopAuthCandidates, y as WORKBUDDY_AUTH_FILE_ENV, z as chatUserAgent } from "./variants-
|
|
1
|
+
import { A as parseModelCatalog, B as fallbackChatIdentity, C as desktopAuthCandidatesFor, D as classifyUpstreamError, E as WorkBuddyUpstreamClient, F as probeModel, G as appUserAgent, H as resolveChatIdentity, I as randomSentinel, J as resolveAppVersion, K as installedAppVersion, L as CN_APP_VERSION_FILENAME, M as prepareInternationalChatBody, N as regionOf, O as modelWithCurrentPromotion, P as PROBE_EFFORT_CANDIDATES, R as FALLBACK_CN_APP_VERSION, S as defaultDesktopAuthPath, T as workbuddyOwnAuthPath, U as validCliVersion, V as readCliVersion, W as WORKBUDDY_APP_VERSION_FILENAME, Y as validAppVersion, _ as WorkBuddyCatalog, a as WORKBUDDY_HOST_HEARTBEAT_FILENAME, b as WorkBuddyCredentialStore, c as processStartTimeMs, d as writeHostHeartbeat, f as WORKBUDDY_CONNECT_VERSION, g as FALLBACK_WORKBUDDY_MODELS, h as FALLBACK_WORKBUDDY_AI_MODELS, i as variantFor, j as prepareChatBody, k as normalizeCredits, l as readHostHeartbeat, n as CN_VARIANT, o as clearHostHeartbeat, q as readBundleVersion, r as WORKBUDDY_VARIANTS, s as isHeartbeatProcessAlive, t as AI_VARIANT, u as workbuddyHostHeartbeatPath, v as WORKBUDDY_AUTH_FILENAME, w as parseWorkBuddyAuth, x as defaultDesktopAuthCandidates, y as WORKBUDDY_AUTH_FILE_ENV, z as chatUserAgent } from "./variants-XUStsapF.js";
|
|
2
2
|
import z from "@deepseek-ai/schemastery";
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
4
|
import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
|
|
@@ -656,8 +656,17 @@ function createWorkBuddyShim(options) {
|
|
|
656
656
|
*/
|
|
657
657
|
/** Basename of the probe record inside the Harness home. */
|
|
658
658
|
const WORKBUDDY_PROBE_FILENAME = ".workbuddy-probe.json";
|
|
659
|
-
/**
|
|
660
|
-
|
|
659
|
+
/**
|
|
660
|
+
* On-disk format this reader accepts; other versions are discarded.
|
|
661
|
+
*
|
|
662
|
+
* Version 2 nested the records under the account that produced them
|
|
663
|
+
* (`records[account][modelId]`), so two accounts no longer overwrite each
|
|
664
|
+
* other's observations for the same model. Version 1 files (flat, one record
|
|
665
|
+
* per model) are deliberately not migrated: they read as empty and the
|
|
666
|
+
* affected models are re-probed on demand, which keeps the reader free of
|
|
667
|
+
* half-understood compatibility paths.
|
|
668
|
+
*/
|
|
669
|
+
const PROBE_FORMAT_VERSION = 2;
|
|
661
670
|
/**
|
|
662
671
|
* How long an observation stays usable. Conservative on purpose: the plan's
|
|
663
672
|
* whole argument is that upstream metadata moves fast, so a result that has
|
|
@@ -718,13 +727,15 @@ function isRecord(value) {
|
|
|
718
727
|
if (typeof wrapped["fingerprint"] !== "string") return false;
|
|
719
728
|
if (typeof wrapped["probedAtMs"] !== "number" || !Number.isFinite(wrapped["probedAtMs"])) return false;
|
|
720
729
|
if (typeof wrapped["pluginVersion"] !== "string") return false;
|
|
730
|
+
if (typeof wrapped["account"] !== "string" || wrapped["account"] === "") return false;
|
|
721
731
|
const efforts = wrapped["efforts"];
|
|
722
732
|
if (!Array.isArray(efforts) || efforts.some((effort) => typeof effort !== "string")) return false;
|
|
723
733
|
return true;
|
|
724
734
|
}
|
|
725
735
|
/**
|
|
726
|
-
* The plugin's probe records: read once, written atomically,
|
|
727
|
-
*
|
|
736
|
+
* The plugin's probe records: read once, written atomically, keyed by the
|
|
737
|
+
* account that produced each observation, and never trusted across a
|
|
738
|
+
* fingerprint change or past the TTL.
|
|
728
739
|
*/
|
|
729
740
|
var WorkBuddyProbeStore = class {
|
|
730
741
|
path;
|
|
@@ -750,21 +761,26 @@ var WorkBuddyProbeStore = class {
|
|
|
750
761
|
if (this.records === void 0) {
|
|
751
762
|
const document = readDocument(this.path);
|
|
752
763
|
const records = {};
|
|
753
|
-
for (const [
|
|
764
|
+
for (const [account, bucket] of Object.entries(document?.records ?? {})) {
|
|
765
|
+
if (typeof bucket !== "object" || bucket === null || Array.isArray(bucket)) continue;
|
|
766
|
+
const parsed = {};
|
|
767
|
+
for (const [modelId, record] of Object.entries(bucket)) if (isRecord(record)) parsed[modelId] = record;
|
|
768
|
+
records[account] = parsed;
|
|
769
|
+
}
|
|
754
770
|
this.records = records;
|
|
755
771
|
}
|
|
756
772
|
return this.records;
|
|
757
773
|
}
|
|
758
774
|
/**
|
|
759
|
-
* The usable record for
|
|
760
|
-
* expired, it was taken against a different catalog row, or it
|
|
761
|
-
* different account.
|
|
775
|
+
* The usable record for one account and model, or `undefined` when there is
|
|
776
|
+
* none, it is expired, it was taken against a different catalog row, or it
|
|
777
|
+
* belongs to a different account.
|
|
762
778
|
*
|
|
763
779
|
* @param account - the account in effect, as `uid:enterpriseId`. Records are
|
|
764
780
|
* only returned for the account that produced them.
|
|
765
781
|
*/
|
|
766
782
|
get(modelId, fingerprint, account) {
|
|
767
|
-
const record = this.load()[modelId];
|
|
783
|
+
const record = this.load()[account]?.[modelId];
|
|
768
784
|
if (record === void 0) return void 0;
|
|
769
785
|
if (record.fingerprint !== fingerprint) return void 0;
|
|
770
786
|
if (record.account !== account) return void 0;
|
|
@@ -772,25 +788,28 @@ var WorkBuddyProbeStore = class {
|
|
|
772
788
|
return record;
|
|
773
789
|
}
|
|
774
790
|
/**
|
|
775
|
-
* Store one observation. Only a decisive
|
|
776
|
-
* `non-validating`) replaces an existing decisive
|
|
777
|
-
* `unknown` must not erase
|
|
791
|
+
* Store one observation under the account stamped on it. Only a decisive
|
|
792
|
+
* answer (`validating` / `non-validating`) replaces an existing decisive
|
|
793
|
+
* record *of the same account*: a transient `unknown` must not erase
|
|
794
|
+
* knowledge the user already paid for.
|
|
778
795
|
*/
|
|
779
796
|
set(modelId, record) {
|
|
780
797
|
const records = this.load();
|
|
781
|
-
const
|
|
798
|
+
const bucket = records[record.account] ?? (records[record.account] = {});
|
|
799
|
+
const existing = bucket[modelId];
|
|
782
800
|
if (record.validation === "unknown" && existing !== void 0 && existing.fingerprint === record.fingerprint && existing.validation !== "unknown") return;
|
|
783
|
-
|
|
801
|
+
bucket[modelId] = record;
|
|
784
802
|
this.persist();
|
|
785
803
|
}
|
|
786
|
-
/** Drop every record; used by the card's explicit "clear" action. */
|
|
804
|
+
/** Drop every record of every account; used by the card's explicit "clear" action. */
|
|
787
805
|
clear() {
|
|
788
806
|
this.records = {};
|
|
789
807
|
this.persist();
|
|
790
808
|
}
|
|
791
|
-
/** Every record currently held, for status display. */
|
|
809
|
+
/** Every record currently held, grouped by account, for status display. */
|
|
792
810
|
all() {
|
|
793
|
-
|
|
811
|
+
const records = this.load();
|
|
812
|
+
return Object.fromEntries(Object.entries(records).map(([account, bucket]) => [account, { ...bucket }]));
|
|
794
813
|
}
|
|
795
814
|
/** Build a record stamped with this store's clock, version, and account. */
|
|
796
815
|
record(fingerprint, validation, efforts, account) {
|
|
@@ -1001,6 +1020,7 @@ async function workBuddyWebStatus(deps) {
|
|
|
1001
1020
|
const rate = normalizeCredits(model.billing?.credits);
|
|
1002
1021
|
const supported = model.supportedContextWindows ?? [];
|
|
1003
1022
|
const maxContextWindow = supported.length > 0 ? Math.max(...supported) : void 0;
|
|
1023
|
+
const defaultContextWindow = model.defaultContextWindow ?? model.contextWindow;
|
|
1004
1024
|
return {
|
|
1005
1025
|
id: model.id,
|
|
1006
1026
|
name: model.name,
|
|
@@ -1009,7 +1029,8 @@ async function workBuddyWebStatus(deps) {
|
|
|
1009
1029
|
...rate === void 0 ? {} : { credits: rate },
|
|
1010
1030
|
...model.billing?.rateUnknown === true ? { rateUnknown: true } : {},
|
|
1011
1031
|
...typeof model.contextWindow === "number" && model.contextWindow > 0 ? { contextWindow: model.contextWindow } : {},
|
|
1012
|
-
...
|
|
1032
|
+
...typeof defaultContextWindow === "number" && defaultContextWindow > 0 && defaultContextWindow < model.contextWindow ? { defaultContextWindow } : {},
|
|
1033
|
+
...maxContextWindow === void 0 || maxContextWindow <= defaultContextWindow ? {} : { maxContextWindow },
|
|
1013
1034
|
...typeof model.maxInputTokens === "number" && model.maxInputTokens > 0 ? { maxInputTokens: model.maxInputTokens } : {}
|
|
1014
1035
|
};
|
|
1015
1036
|
});
|
|
@@ -1025,7 +1046,8 @@ async function workBuddyWebStatus(deps) {
|
|
|
1025
1046
|
const probed = deps.probe === void 0 ? statusWithModels : {
|
|
1026
1047
|
...statusWithModels,
|
|
1027
1048
|
probe: deps.probe(),
|
|
1028
|
-
...deps.probeKey === void 0 ? {} : { probeKey: deps.probeKey }
|
|
1049
|
+
...deps.probeKey === void 0 ? {} : { probeKey: deps.probeKey },
|
|
1050
|
+
...deps.useMaximumContextWindow === void 0 ? {} : { useMaximumContextWindow: deps.useMaximumContextWindow() }
|
|
1029
1051
|
};
|
|
1030
1052
|
try {
|
|
1031
1053
|
const credential = await deps.store.current();
|
|
@@ -1145,6 +1167,10 @@ function parseAction(text) {
|
|
|
1145
1167
|
const action = wrapped["action"];
|
|
1146
1168
|
if (action === "clear") return { action: "clear" };
|
|
1147
1169
|
if (action === "refresh") return { action: "refresh" };
|
|
1170
|
+
if (action === "set-maximum-context-window") return typeof wrapped["enabled"] === "boolean" ? {
|
|
1171
|
+
action: "set-maximum-context-window",
|
|
1172
|
+
enabled: wrapped["enabled"]
|
|
1173
|
+
} : void 0;
|
|
1148
1174
|
if (action === "probe") {
|
|
1149
1175
|
const model = wrapped["model"];
|
|
1150
1176
|
if (typeof model !== "string" || model.trim() === "") return void 0;
|
|
@@ -1196,6 +1222,14 @@ function workBuddyProbeHandler(deps, key) {
|
|
|
1196
1222
|
json(res, 200, await deps.refresh());
|
|
1197
1223
|
return;
|
|
1198
1224
|
}
|
|
1225
|
+
if (action.action === "set-maximum-context-window") {
|
|
1226
|
+
if (deps.setMaximumContextWindow === void 0) {
|
|
1227
|
+
json(res, 404, { error: "context-window-setting-not-supported" });
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
json(res, 200, await deps.setMaximumContextWindow(action.enabled === true));
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1199
1233
|
json(res, 200, await deps.probe(action.model));
|
|
1200
1234
|
} catch (error) {
|
|
1201
1235
|
json(res, 500, { error: error instanceof Error ? error.message : String(error) });
|
|
@@ -1292,10 +1326,12 @@ const AUTH_FILE_FIELD = z.string().description("WorkBuddy desktop auth file (def
|
|
|
1292
1326
|
const AUTH_FILE_AI_FIELD = z.string().description("WorkBuddy AI desktop auth file (defaults to the app's own location)");
|
|
1293
1327
|
/** Probe authorization (shared by the plugin schema and the CN section). */
|
|
1294
1328
|
const PROBE_CONSENT_FIELD = z.boolean().default(false).description("Authorize reasoning-effort probes (each probe sends real requests that may consume credit)");
|
|
1329
|
+
const MAXIMUM_CONTEXT_WINDOW_FIELD = z.boolean().default(true).description("Use the largest context window declared by WorkBuddy AI when alternatives are available (on by default)");
|
|
1295
1330
|
const Config = z.object({
|
|
1296
1331
|
authFile: AUTH_FILE_FIELD,
|
|
1297
1332
|
authFileAI: AUTH_FILE_AI_FIELD,
|
|
1298
|
-
probeConsent: PROBE_CONSENT_FIELD
|
|
1333
|
+
probeConsent: PROBE_CONSENT_FIELD,
|
|
1334
|
+
useMaximumContextWindow: MAXIMUM_CONTEXT_WINDOW_FIELD
|
|
1299
1335
|
});
|
|
1300
1336
|
/**
|
|
1301
1337
|
* The CN card's settings section: only the fields that card edits.
|
|
@@ -1310,8 +1346,11 @@ const CN_SECTION = z.object({
|
|
|
1310
1346
|
authFile: AUTH_FILE_FIELD,
|
|
1311
1347
|
probeConsent: PROBE_CONSENT_FIELD
|
|
1312
1348
|
});
|
|
1313
|
-
/** The international card's settings section
|
|
1314
|
-
const AI_SECTION = z.object({
|
|
1349
|
+
/** The international card's settings section and its context-window preference. */
|
|
1350
|
+
const AI_SECTION = z.object({
|
|
1351
|
+
authFileAI: AUTH_FILE_AI_FIELD,
|
|
1352
|
+
useMaximumContextWindow: MAXIMUM_CONTEXT_WINDOW_FIELD
|
|
1353
|
+
});
|
|
1315
1354
|
/** Stable identity key used by credentials, probe records, and catalog entries. */
|
|
1316
1355
|
function credentialIdentity(credential) {
|
|
1317
1356
|
return `${credential.uid}:${credential.enterpriseId ?? ""}`;
|
|
@@ -1345,6 +1384,7 @@ function createVariantRuntime(config, variant, current, identityOf) {
|
|
|
1345
1384
|
});
|
|
1346
1385
|
const fallback = fallbackFor(variant);
|
|
1347
1386
|
const catalog = new WorkBuddyCatalog(fallback);
|
|
1387
|
+
if (variant.id !== CN_VARIANT.id) catalog.setUseMaximumContextWindow(config.useMaximumContextWindow === true);
|
|
1348
1388
|
catalog.setVisible(false);
|
|
1349
1389
|
const probeStore = new WorkBuddyProbeStore({
|
|
1350
1390
|
pluginVersion: WORKBUDDY_CONNECT_VERSION,
|
|
@@ -1520,6 +1560,7 @@ function apply(ctx, config) {
|
|
|
1520
1560
|
const lastIdentities = /* @__PURE__ */ new Map();
|
|
1521
1561
|
const runtimes = WORKBUDDY_VARIANTS.map((variant) => createVariantRuntime(config, variant, () => current(), (id) => lastIdentities.get(id)));
|
|
1522
1562
|
const probeKey = createProbeKey();
|
|
1563
|
+
let setMaximumContextWindow;
|
|
1523
1564
|
/**
|
|
1524
1565
|
* Point a variant at an account identity, invalidating whatever the previous
|
|
1525
1566
|
* one left behind.
|
|
@@ -1530,13 +1571,12 @@ function apply(ctx, config) {
|
|
|
1530
1571
|
* transition bumps {@link VariantRuntime.catalogGeneration}, which is what
|
|
1531
1572
|
* makes an in-flight request from before the change refuse to write back.
|
|
1532
1573
|
*
|
|
1533
|
-
* Probe observations are
|
|
1534
|
-
*
|
|
1535
|
-
*
|
|
1536
|
-
*
|
|
1537
|
-
* a first
|
|
1538
|
-
*
|
|
1539
|
-
* seeded while all of this is running).
|
|
1574
|
+
* Probe observations are kept across an account change: the store nests them
|
|
1575
|
+
* per account, so the departing account's records simply stop being served
|
|
1576
|
+
* (every read is account-scoped) and are found intact if that account
|
|
1577
|
+
* returns. The "signed out, then in as someone else" sequence that used to
|
|
1578
|
+
* look like a first sighting is still safe — a record only ever answers for
|
|
1579
|
+
* the account stamped on it, so the new account inherits nothing.
|
|
1540
1580
|
*
|
|
1541
1581
|
* @param identity - the account now in effect, or `undefined` when signed out.
|
|
1542
1582
|
*/
|
|
@@ -1550,10 +1590,7 @@ function apply(ctx, config) {
|
|
|
1550
1590
|
runtime.catalogGeneration += 1;
|
|
1551
1591
|
runtime.inflightFetch?.controller.abort();
|
|
1552
1592
|
runtime.inflightFetch = void 0;
|
|
1553
|
-
if (hadCredential && known !== identity)
|
|
1554
|
-
runtime.probeStore.clear();
|
|
1555
|
-
runtime.invalidate();
|
|
1556
|
-
}
|
|
1593
|
+
if (hadCredential && known !== identity) runtime.invalidate();
|
|
1557
1594
|
if (identity === void 0) {
|
|
1558
1595
|
if (known !== void 0) runtime.savedCatalogs.delete(known);
|
|
1559
1596
|
runtime.catalog.set(runtime.fallback);
|
|
@@ -1586,7 +1623,8 @@ function apply(ctx, config) {
|
|
|
1586
1623
|
models: () => runtime.catalog.current(),
|
|
1587
1624
|
catalog: () => catalogSection(runtime),
|
|
1588
1625
|
probe: () => probeSection(runtime, current().probeConsent === true),
|
|
1589
|
-
probeKey
|
|
1626
|
+
probeKey,
|
|
1627
|
+
...runtime.variant.id === CN_VARIANT.id ? {} : { useMaximumContextWindow: () => current().useMaximumContextWindow === true }
|
|
1590
1628
|
});
|
|
1591
1629
|
registerWorkBuddyProbeRoute(webCtx, {
|
|
1592
1630
|
path: runtime.variant.probePath,
|
|
@@ -1627,7 +1665,14 @@ function apply(ctx, config) {
|
|
|
1627
1665
|
state: "failed",
|
|
1628
1666
|
reason: runtime.catalogError
|
|
1629
1667
|
};
|
|
1630
|
-
}
|
|
1668
|
+
},
|
|
1669
|
+
...runtime.variant.id === CN_VARIANT.id ? {} : { setMaximumContextWindow: async (enabled) => {
|
|
1670
|
+
if (setMaximumContextWindow === void 0) return {
|
|
1671
|
+
state: "failed",
|
|
1672
|
+
reason: "settings are unavailable"
|
|
1673
|
+
};
|
|
1674
|
+
return setMaximumContextWindow(enabled);
|
|
1675
|
+
} }
|
|
1631
1676
|
}, probeKey);
|
|
1632
1677
|
}
|
|
1633
1678
|
});
|
|
@@ -1641,10 +1686,16 @@ function apply(ctx, config) {
|
|
|
1641
1686
|
const merged = () => ({
|
|
1642
1687
|
...sources.cn().authFile === void 0 ? {} : { authFile: sources.cn().authFile },
|
|
1643
1688
|
...sources.cn().probeConsent === void 0 ? {} : { probeConsent: sources.cn().probeConsent },
|
|
1644
|
-
...sources.ai().authFileAI === void 0 ? {} : { authFileAI: sources.ai().authFileAI }
|
|
1689
|
+
...sources.ai().authFileAI === void 0 ? {} : { authFileAI: sources.ai().authFileAI },
|
|
1690
|
+
...sources.ai().useMaximumContextWindow === void 0 ? {} : { useMaximumContextWindow: sources.ai().useMaximumContextWindow }
|
|
1645
1691
|
});
|
|
1692
|
+
const applyMaximumContextWindow = (next) => {
|
|
1693
|
+
const runtime = runtimes.find((candidate) => candidate.variant.id !== CN_VARIANT.id);
|
|
1694
|
+
if (runtime?.catalog.setUseMaximumContextWindow(next.useMaximumContextWindow === true)) runtime.invalidate();
|
|
1695
|
+
};
|
|
1646
1696
|
const repointStores = () => {
|
|
1647
1697
|
const next = merged();
|
|
1698
|
+
applyMaximumContextWindow(next);
|
|
1648
1699
|
for (const runtime of runtimes) runtime.store.setDesktopPath(configuredAuthFile(next, runtime.variant));
|
|
1649
1700
|
};
|
|
1650
1701
|
settingsCtx.settings.installSection(ctx, WORKBUDDY_SETTINGS_NS, CN_SECTION, config, {
|
|
@@ -1661,6 +1712,10 @@ function apply(ctx, config) {
|
|
|
1661
1712
|
},
|
|
1662
1713
|
onChange: repointStores
|
|
1663
1714
|
});
|
|
1715
|
+
setMaximumContextWindow = async (enabled) => {
|
|
1716
|
+
await settingsCtx.settings.update(WORKBUDDY_AI_SETTINGS_NS, { useMaximumContextWindow: enabled });
|
|
1717
|
+
return { state: "updated" };
|
|
1718
|
+
};
|
|
1664
1719
|
});
|
|
1665
1720
|
ctx.effect(() => () => {
|
|
1666
1721
|
stopped = true;
|
|
@@ -485,6 +485,33 @@ async function probeModel(options) {
|
|
|
485
485
|
const CN_CHAT_BASE = "https://copilot.tencent.com";
|
|
486
486
|
const CN_BILLING_BASE = "https://www.codebuddy.cn";
|
|
487
487
|
const GLOBAL_BASE = "https://www.workbuddy.ai";
|
|
488
|
+
/**
|
|
489
|
+
* Display name for the single synthetic row the enterprise endpoint produces.
|
|
490
|
+
*
|
|
491
|
+
* The endpoint reports one cycle quota, not the personal endpoint's list of
|
|
492
|
+
* named packages, so the card's "by package" table has exactly one row.
|
|
493
|
+
*/
|
|
494
|
+
const enterprisePackageName = "enterprise";
|
|
495
|
+
/**
|
|
496
|
+
* Field names and value types of a response document, for diagnostics.
|
|
497
|
+
*
|
|
498
|
+
* Names and `typeof` only. This string ends up in the status route and then in
|
|
499
|
+
* the browser, and the response describes the account's own usage; the values
|
|
500
|
+
* themselves must never travel. Only the document and its `data` member are
|
|
501
|
+
* described, so the output stays small.
|
|
502
|
+
*/
|
|
503
|
+
function describeShape(document) {
|
|
504
|
+
if (typeof document !== "object" || document === null || Array.isArray(document)) return typeof document;
|
|
505
|
+
const record = document;
|
|
506
|
+
const at = (source) => {
|
|
507
|
+
const keys = Object.keys(source).slice(0, 24);
|
|
508
|
+
return keys.length === 0 ? "(empty)" : keys.map((key) => `${key}:${typeof source[key]}`).join(", ");
|
|
509
|
+
};
|
|
510
|
+
const top = `top-level { ${at(record)} }`;
|
|
511
|
+
const data = record["data"];
|
|
512
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) return top;
|
|
513
|
+
return `${top}; data { ${at(data)} }`;
|
|
514
|
+
}
|
|
488
515
|
/** Shared CLI-form User-Agent for refresh and the CN catalog; chat and probe present the desktop identity (client-identity.ts). */
|
|
489
516
|
const CLIENT_UA = "CLI/2.63.2 CodeBuddy/2.63.2";
|
|
490
517
|
const JSON_TIMEOUT_MS = 3e4;
|
|
@@ -882,8 +909,25 @@ var WorkBuddyUpstreamClient = class {
|
|
|
882
909
|
};
|
|
883
910
|
return models;
|
|
884
911
|
}
|
|
885
|
-
/**
|
|
912
|
+
/**
|
|
913
|
+
* POST the billing endpoint for the aggregated remaining credit.
|
|
914
|
+
*
|
|
915
|
+
* Two upstream shapes, chosen by account type:
|
|
916
|
+
*
|
|
917
|
+
* - **CN enterprise** (`regionOf === 'cn'` and `enterpriseId` non-empty) asks
|
|
918
|
+
* `/v2/billing/meter/get-enterprise-user-usage`, which answers with a single
|
|
919
|
+
* cycle quota. The personal endpoint serves these accounts an empty
|
|
920
|
+
* `Accounts` list, which the card then renders as "0 credit" — a wrong
|
|
921
|
+
* number rather than a visible failure (issue #31).
|
|
922
|
+
* - **Everyone else** keeps the personal endpoint unchanged.
|
|
923
|
+
*
|
|
924
|
+
* The region gate is load-bearing: the enterprise endpoint is unverified for
|
|
925
|
+
* the global region, so an international credential that happens to carry an
|
|
926
|
+
* `enterpriseId` must stay on the measured personal path instead of being
|
|
927
|
+
* moved onto an unmeasured one.
|
|
928
|
+
*/
|
|
886
929
|
async fetchCredits(credential) {
|
|
930
|
+
if (regionOf(credential.domain) === "cn" && credential.enterpriseId !== void 0 && credential.enterpriseId !== "") return await this.fetchEnterpriseCredits(credential);
|
|
887
931
|
const now = /* @__PURE__ */ new Date();
|
|
888
932
|
const format = (date) => [
|
|
889
933
|
date.getFullYear().toString().padStart(4, "0"),
|
|
@@ -941,6 +985,75 @@ var WorkBuddyUpstreamClient = class {
|
|
|
941
985
|
};
|
|
942
986
|
}
|
|
943
987
|
/**
|
|
988
|
+
* CN enterprise credit read: a single cycle quota instead of a package list.
|
|
989
|
+
*
|
|
990
|
+
* Verified against the WorkBuddy desktop app (`app.asar`,
|
|
991
|
+
* `BackendProvider.getEnterpriseUsage` and `CloudAccountRepo.billing`): the
|
|
992
|
+
* body is an empty object and the account identity travels only in the
|
|
993
|
+
* headers. The two official call sites disagree on the field spelling
|
|
994
|
+
* (`limitNum`/`credit` vs `limit_num`/`used_num`), so both are accepted.
|
|
995
|
+
*
|
|
996
|
+
* A body carrying no recognisable quota field is a hard error rather than a
|
|
997
|
+
* zero. Rendering `0` for "we did not understand the answer" is exactly how
|
|
998
|
+
* issue #31 stayed invisible while users saw a plausible wrong number.
|
|
999
|
+
*
|
|
1000
|
+
* The error names fields and types only: it reaches the browser, and the
|
|
1001
|
+
* response body may describe the account's usage.
|
|
1002
|
+
*/
|
|
1003
|
+
async fetchEnterpriseCredits(credential) {
|
|
1004
|
+
const response = await fetch(`${CN_BILLING_BASE}/v2/billing/meter/get-enterprise-user-usage`, {
|
|
1005
|
+
method: "POST",
|
|
1006
|
+
headers: billingHeaders(credential),
|
|
1007
|
+
body: JSON.stringify({}),
|
|
1008
|
+
signal: AbortSignal.timeout(JSON_TIMEOUT_MS)
|
|
1009
|
+
});
|
|
1010
|
+
const envelope = await readEnvelope(response);
|
|
1011
|
+
if (!response.ok || envelope.code !== 0) throw envelopeError(response.status, envelope);
|
|
1012
|
+
const sources = [];
|
|
1013
|
+
for (const candidate of [envelope.data, envelope.document]) {
|
|
1014
|
+
if (typeof candidate !== "object" || candidate === null || Array.isArray(candidate)) continue;
|
|
1015
|
+
const record = candidate;
|
|
1016
|
+
if (typeof record["data"] === "object" && record["data"] !== null && !Array.isArray(record["data"])) sources.push(record["data"]);
|
|
1017
|
+
sources.push(record);
|
|
1018
|
+
}
|
|
1019
|
+
const numberAt = (source, key) => typeof source[key] === "number" ? source[key] : void 0;
|
|
1020
|
+
let limit;
|
|
1021
|
+
let used;
|
|
1022
|
+
let resetTime;
|
|
1023
|
+
for (const source of sources) {
|
|
1024
|
+
const candidate = numberAt(source, "limitNum") ?? numberAt(source, "limit_num");
|
|
1025
|
+
if (candidate === void 0) continue;
|
|
1026
|
+
limit = candidate;
|
|
1027
|
+
used = numberAt(source, "credit") ?? numberAt(source, "used_num");
|
|
1028
|
+
if (typeof source["cycleResetTime"] === "string" && source["cycleResetTime"] !== "") resetTime = source["cycleResetTime"];
|
|
1029
|
+
break;
|
|
1030
|
+
}
|
|
1031
|
+
if (limit === void 0) throw new Error(`workbuddy enterprise billing response carried no recognised quota field (expected limitNum/limit_num + credit/used_num; received ${describeShape(envelope.document)})`);
|
|
1032
|
+
if (limit === -1) return {
|
|
1033
|
+
total: 0,
|
|
1034
|
+
accounts: [{
|
|
1035
|
+
packageName: enterprisePackageName,
|
|
1036
|
+
remain: 0,
|
|
1037
|
+
size: 0,
|
|
1038
|
+
unlimited: true
|
|
1039
|
+
}],
|
|
1040
|
+
unlimited: true,
|
|
1041
|
+
...resetTime === void 0 ? {} : { cycleResetTime: resetTime }
|
|
1042
|
+
};
|
|
1043
|
+
if (used === void 0) throw new Error(`workbuddy enterprise billing response carried a quota limit but no recognised usage field (expected credit/used_num alongside limitNum/limit_num; received ${describeShape(envelope.document)})`);
|
|
1044
|
+
let remain = limit - used;
|
|
1045
|
+
if (remain < 0) remain = 0;
|
|
1046
|
+
return {
|
|
1047
|
+
total: remain,
|
|
1048
|
+
accounts: [{
|
|
1049
|
+
packageName: enterprisePackageName,
|
|
1050
|
+
remain,
|
|
1051
|
+
size: limit
|
|
1052
|
+
}],
|
|
1053
|
+
...resetTime === void 0 ? {} : { cycleResetTime: resetTime }
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
/**
|
|
944
1057
|
* One probe request: a real streaming chat call carrying the effort under
|
|
945
1058
|
* test.
|
|
946
1059
|
*
|
|
@@ -1092,6 +1205,7 @@ function parseModelCatalog(data, international = false) {
|
|
|
1092
1205
|
name: typeof wrapped["name"] === "string" && wrapped["name"] !== "" ? wrapped["name"] : id,
|
|
1093
1206
|
contextWindow: international && isObject(wrapped["contextWindow"]) && positive(wrapped["contextWindow"]["defaultLength"]) ? wrapped["contextWindow"]["defaultLength"] : input,
|
|
1094
1207
|
...international ? {
|
|
1208
|
+
...isObject(wrapped["contextWindow"]) && positive(wrapped["contextWindow"]["defaultLength"]) ? { defaultContextWindow: wrapped["contextWindow"]["defaultLength"] } : {},
|
|
1095
1209
|
maxInputTokens: input,
|
|
1096
1210
|
supportedContextWindows: isObject(wrapped["contextWindow"]) && Array.isArray(wrapped["contextWindow"]["supportedLengths"]) ? wrapped["contextWindow"]["supportedLengths"].filter(positive) : [],
|
|
1097
1211
|
promotions: parsePromotions(data["modelPromotions"], id)
|
|
@@ -1971,6 +2085,8 @@ const FALLBACK_WORKBUDDY_AI_MODELS = [
|
|
|
1971
2085
|
id: "hy4-preview-f",
|
|
1972
2086
|
name: "Hy4 preview",
|
|
1973
2087
|
contextWindow: 3e5,
|
|
2088
|
+
defaultContextWindow: 3e5,
|
|
2089
|
+
supportedContextWindows: [3e5, 1e6],
|
|
1974
2090
|
maxTokens: 64e3,
|
|
1975
2091
|
supportsImages: true,
|
|
1976
2092
|
reasoning: {
|
|
@@ -2007,6 +2123,8 @@ const FALLBACK_WORKBUDDY_AI_MODELS = [
|
|
|
2007
2123
|
id: "deepseek-v4.1-flash",
|
|
2008
2124
|
name: "Deepseek-V4.1-Flash",
|
|
2009
2125
|
contextWindow: 3e5,
|
|
2126
|
+
defaultContextWindow: 3e5,
|
|
2127
|
+
supportedContextWindows: [3e5, 1e6],
|
|
2010
2128
|
maxTokens: 128e3,
|
|
2011
2129
|
supportsImages: true,
|
|
2012
2130
|
reasoning: {
|
|
@@ -2024,6 +2142,8 @@ const FALLBACK_WORKBUDDY_AI_MODELS = [
|
|
|
2024
2142
|
id: "gpt-6-astra",
|
|
2025
2143
|
name: "GPT-6-Astra",
|
|
2026
2144
|
contextWindow: 4e5,
|
|
2145
|
+
defaultContextWindow: 4e5,
|
|
2146
|
+
supportedContextWindows: [4e5, 1e6],
|
|
2027
2147
|
maxTokens: 128e3,
|
|
2028
2148
|
supportsImages: true,
|
|
2029
2149
|
reasoning: {
|
|
@@ -2287,13 +2407,22 @@ const FALLBACK_WORKBUDDY_AI_MODELS = [
|
|
|
2287
2407
|
var WorkBuddyCatalog = class {
|
|
2288
2408
|
models;
|
|
2289
2409
|
visible = true;
|
|
2410
|
+
useMaximumContextWindow = false;
|
|
2290
2411
|
constructor(initial = FALLBACK_WORKBUDDY_MODELS) {
|
|
2291
2412
|
this.models = initial;
|
|
2292
2413
|
}
|
|
2293
2414
|
/** Current entries; empty while the variant has no usable credential. */
|
|
2294
2415
|
current() {
|
|
2295
2416
|
if (!this.visible) return [];
|
|
2296
|
-
return this.models.map((model) =>
|
|
2417
|
+
return this.models.map((model) => {
|
|
2418
|
+
const current = modelWithCurrentPromotion(model);
|
|
2419
|
+
const maximum = current.supportedContextWindows === void 0 ? void 0 : Math.max(...current.supportedContextWindows);
|
|
2420
|
+
return this.useMaximumContextWindow && maximum !== void 0 && maximum > current.contextWindow ? {
|
|
2421
|
+
...current,
|
|
2422
|
+
defaultContextWindow: current.defaultContextWindow ?? current.contextWindow,
|
|
2423
|
+
contextWindow: maximum
|
|
2424
|
+
} : current;
|
|
2425
|
+
});
|
|
2297
2426
|
}
|
|
2298
2427
|
/** Replace the list; callers invalidate their adapter snapshot after this. */
|
|
2299
2428
|
set(models) {
|
|
@@ -2312,6 +2441,12 @@ var WorkBuddyCatalog = class {
|
|
|
2312
2441
|
this.visible = visible;
|
|
2313
2442
|
return true;
|
|
2314
2443
|
}
|
|
2444
|
+
/** Select the largest declared international window where the upstream offers one. */
|
|
2445
|
+
setUseMaximumContextWindow(useMaximum) {
|
|
2446
|
+
if (this.useMaximumContextWindow === useMaximum) return false;
|
|
2447
|
+
this.useMaximumContextWindow = useMaximum;
|
|
2448
|
+
return true;
|
|
2449
|
+
}
|
|
2315
2450
|
/** Models to fall back to when the upstream fetch fails; ignores visibility. */
|
|
2316
2451
|
fallback() {
|
|
2317
2452
|
return this.models;
|
|
@@ -2344,7 +2479,7 @@ const WORKBUDDY_AI_STATUS_PATH = "/plugins/dsh-workbuddy-connect/ai/status";
|
|
|
2344
2479
|
const WORKBUDDY_AI_PROBE_PATH = "/plugins/dsh-workbuddy-connect/ai/probe";
|
|
2345
2480
|
//#endregion
|
|
2346
2481
|
//#region src/version.ts
|
|
2347
|
-
const WORKBUDDY_CONNECT_VERSION = "0.5.
|
|
2482
|
+
const WORKBUDDY_CONNECT_VERSION = "0.5.4";
|
|
2348
2483
|
//#endregion
|
|
2349
2484
|
//#region src/host-heartbeat.ts
|
|
2350
2485
|
/**
|
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.5.
|
|
5
|
+
"version": "0.5.4",
|
|
6
6
|
"author": "corrinehu",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"dsh-plugin",
|