dsh-grok-subscription 2.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.en.md +2 -2
- package/README.md +2 -2
- package/docs/guide.en.md +3 -3
- package/docs/guide.zh-CN.md +3 -3
- package/lib/client.js +19 -5
- package/lib/index.js +31 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.0.1 — 2026-09-29
|
|
4
|
+
|
|
5
|
+
- Fix **the quota card reporting a raw parser error for an account the upstream API stopped describing with a percentage**. Since the weekly period rolled over on 2026-09-28T16:46Z, `GET /v1/billing?format=credits` answers with a `config` object that carries `isUnifiedBillingUser`, `prepaidBalance`, `onDemandCap`, `onDemandUsed`, `topUpMethod` and the period bounds — and no `creditUsagePercent`, no `productUsage`, and no used/total pair. Every reader in the plugin is unchanged since 1.0.20, so the card simply failed closed with `不可用: Missing creditUsagePercent (keys: config)`, which reads like a broken plugin rather than an upstream shape the plugin does not recognise. Reproduced on a second machine with the same account: same build, same headers, only the clock moved.
|
|
6
|
+
- Name the shape instead of a missing field. The unavailable reading now carries a `code` (`unified-billing` or `missing-percent`) plus the period bounds, the panel renders localized copy for a named code and keeps the raw diagnostic as a hint underneath, and an unnamed failure still shows its own reason.
|
|
7
|
+
- **Show the window even when there is no percentage.** The period start and end come from the same response, so the card now prints `本期窗口: 2026/09/29 00:46:29 → 2026/10/06 00:46:29` whether or not a reading came back. A reset time is never lost to a shape change.
|
|
8
|
+
- Still no invented numbers. A zero prepaid balance and a zero on-demand cap are not a percentage, so the card shows no gauge and the composer badge stays hidden; `parseBillingCredits` is asserted against the captured live body to keep it that way.
|
|
9
|
+
- No adapter, routing, session or RPC-surface changes. `sanitizeUsage` carries the new diagnostic fields through the RPC without exposing anything else.
|
|
10
|
+
|
|
11
|
+
## 2.0.0 — 2026-09-28
|
|
12
|
+
|
|
3
13
|
## 2.0.0 — 2026-09-28
|
|
4
14
|
|
|
5
15
|
- **Remove the bundled Responses adapter. The `grok-build` route is now always the host's own pi-ai adapter.** The bundled implementation and everything that existed only to support it are gone: its request building, encrypted-reasoning envelope, prompt-cache and usage mapping and image collection (`src/adapter.js` drops from 1081 to 467 lines), the synchronous duck registration, `createGrokBuildAdapterSync` / `createDuckHostAdapter`, the release-then-re-register upgrade 1.2.0 added, and the `DSH_GROK_ADAPTER=fallback` switch. `apply` still returns immediately and builds the adapter from a deferred task, but registers exactly once.
|
package/README.en.md
CHANGED
|
@@ -61,13 +61,13 @@ Pick a Grok model your account can use, adjust reasoning effort as needed, and s
|
|
|
61
61
|
<details>
|
|
62
62
|
<summary>Check the version / pin one / fall back when the UI install fails</summary>
|
|
63
63
|
|
|
64
|
-
**Settings → Plugins** lists the installed plugins and their versions, and the plugin's own **Settings → Grok Subscription** page shows a version card. To pin a version, put the full `package@version` in the install field, for example `dsh-grok-subscription@2.0.
|
|
64
|
+
**Settings → Plugins** lists the installed plugins and their versions, and the plugin's own **Settings → Grok Subscription** page shows a version card. To pin a version, put the full `package@version` in the install field, for example `dsh-grok-subscription@2.0.1`.
|
|
65
65
|
|
|
66
66
|
If the UI install fails (an unusual profile directory, say), the equivalent runs against the desktop profile itself (pnpm ≥ 11):
|
|
67
67
|
|
|
68
68
|
```sh
|
|
69
69
|
cd "$HOME/.dsh/profiles/desktop" # Windows: cd "$env:USERPROFILE\.dsh\profiles\desktop"
|
|
70
|
-
pnpm add --save-exact --config.minimumReleaseAge=0 dsh-grok-subscription@2.0.
|
|
70
|
+
pnpm add --save-exact --config.minimumReleaseAge=0 dsh-grok-subscription@2.0.1
|
|
71
71
|
```
|
|
72
72
|
|
|
73
73
|
`dsh plugin --profile desktop …` is refused: the Electron application owns that profile exclusively. CLI paths, sign-in, and troubleshooting are in the [full user guide](docs/guide.en.md).
|
package/README.md
CHANGED
|
@@ -61,13 +61,13 @@
|
|
|
61
61
|
<details>
|
|
62
62
|
<summary>确认版本 / 固定版本 / 界面安装失败时的兜底</summary>
|
|
63
63
|
|
|
64
|
-
**设置 → 插件** 会列出已安装插件与版本,插件自己的 **设置 → Grok 订阅** 页也有版本卡片。要固定版本,在安装框里填入完整的 `包名@版本`,例如 `dsh-grok-subscription@2.0.
|
|
64
|
+
**设置 → 插件** 会列出已安装插件与版本,插件自己的 **设置 → Grok 订阅** 页也有版本卡片。要固定版本,在安装框里填入完整的 `包名@版本`,例如 `dsh-grok-subscription@2.0.1`。
|
|
65
65
|
|
|
66
66
|
界面安装失败时(例如 profile 目录权限异常),可以对桌面版 profile 手动执行等价操作(pnpm ≥ 11):
|
|
67
67
|
|
|
68
68
|
```sh
|
|
69
69
|
cd "$HOME/.dsh/profiles/desktop" # Windows: cd "$env:USERPROFILE\.dsh\profiles\desktop"
|
|
70
|
-
pnpm add --save-exact --config.minimumReleaseAge=0 dsh-grok-subscription@2.0.
|
|
70
|
+
pnpm add --save-exact --config.minimumReleaseAge=0 dsh-grok-subscription@2.0.1
|
|
71
71
|
```
|
|
72
72
|
|
|
73
73
|
`dsh plugin --profile desktop …` 会被拒绝:桌面版 profile 由 Electron 应用独占。CLI 路径、登录方式与故障排查见 [完整使用指南](docs/guide.zh-CN.md)。
|
package/docs/guide.en.md
CHANGED
|
@@ -8,7 +8,7 @@ For quick setup, see the [README](../README.en.md#install). This guide covers de
|
|
|
8
8
|
|
|
9
9
|
You need the DeepSeek Harness desktop app, the official Grok Build CLI, and an account with Grok Build access. The plugin declares Node.js `^22.19.0 || >=24.0.0`; DSH peers use `>=0.1.5-rc.2 <0.3.0-0`, which spans the whole 0.1 and 0.2 lines. See [package.json](../package.json) for the full requirements. Declared compatibility is not a claim that every version has passed live verification.
|
|
10
10
|
|
|
11
|
-
**Install:** in the desktop app open **Settings → Plugins**, type the package name `dsh-grok-subscription` into the install field, and install it. Then **quit and restart the desktop app completely**; refreshing the page alone does not reload the Host adapter. To pin a version, put the full `package@version` in that field, for example `dsh-grok-subscription@2.0.
|
|
11
|
+
**Install:** in the desktop app open **Settings → Plugins**, type the package name `dsh-grok-subscription` into the install field, and install it. Then **quit and restart the desktop app completely**; refreshing the page alone does not reload the Host adapter. To pin a version, put the full `package@version` in that field, for example `dsh-grok-subscription@2.0.1`.
|
|
12
12
|
|
|
13
13
|
**The plugin depends on the host's own pi-ai adapter.** From 2.0.0 the `grok-build` route is served entirely by the host's `@deepseek-ai/dsh-llm-pi-ai` / `@earendil-works/pi-ai`, which every DSH installation carries (`@deepseek-ai/dsh` → `dsh-base` → `dsh-llm-pi-ai`, with the `llm-pi-ai` row mounted unconditionally). If those packages cannot be resolved from the DSH installation, the plugin registers no route at all and **Active path** in Settings reads **unavailable**.
|
|
14
14
|
|
|
@@ -16,7 +16,7 @@ If the UI install fails (an unusual profile directory, say), the equivalent runs
|
|
|
16
16
|
|
|
17
17
|
```sh
|
|
18
18
|
cd "$HOME/.dsh/profiles/desktop" # Windows: cd "$env:USERPROFILE\.dsh\profiles\desktop"
|
|
19
|
-
pnpm add --save-exact --config.minimumReleaseAge=0 dsh-grok-subscription@2.0.
|
|
19
|
+
pnpm add --save-exact --config.minimumReleaseAge=0 dsh-grok-subscription@2.0.1
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
`dsh plugin --profile desktop …` is refused: the Electron application owns that profile exclusively.
|
|
@@ -101,7 +101,7 @@ In the desktop app the `desktop` profile is owned exclusively by the Electron ap
|
|
|
101
101
|
|
|
102
102
|
```sh
|
|
103
103
|
cd "$HOME/.dsh/profiles/desktop" # Windows: cd "$env:USERPROFILE\.dsh\profiles\desktop"
|
|
104
|
-
pnpm add --save-exact --config.minimumReleaseAge=0 dsh-grok-subscription@2.0.
|
|
104
|
+
pnpm add --save-exact --config.minimumReleaseAge=0 dsh-grok-subscription@2.0.1
|
|
105
105
|
```
|
|
106
106
|
|
|
107
107
|
Then quit and restart the desktop app completely. After a successful update the profile's `package.json` dependency should show the target version while its `dsh.profile.bundles` entry is unchanged. Refreshing the browser is not enough: without a Host restart the old version keeps loading.
|
package/docs/guide.zh-CN.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
需要 DeepSeek Harness 桌面版、官方 Grok Build CLI,以及当前具有 Grok Build 使用资格的订阅账号。插件的 Node.js 要求为 `^22.19.0 || >=24.0.0`,DSH peer 声明以 [package.json](../package.json) 为准;其中 DSH 依赖使用 `>=0.1.5-rc.2 <0.3.0-0`,覆盖 0.1 与 0.2 全线。声明范围不等于所有版本都已经过实机验证。
|
|
10
10
|
|
|
11
|
-
**安装:** 在桌面版打开 **设置 → 插件**,在安装输入框里填入包名 `dsh-grok-subscription` 并安装,然后**完全退出并重启桌面应用**。仅刷新页面不会重新加载 Host 中的适配器。需要可重复安装时,填入完整的 `包名@版本`,例如 `dsh-grok-subscription@2.0.
|
|
11
|
+
**安装:** 在桌面版打开 **设置 → 插件**,在安装输入框里填入包名 `dsh-grok-subscription` 并安装,然后**完全退出并重启桌面应用**。仅刷新页面不会重新加载 Host 中的适配器。需要可重复安装时,填入完整的 `包名@版本`,例如 `dsh-grok-subscription@2.0.1`。
|
|
12
12
|
|
|
13
13
|
**插件依赖宿主自带的 pi-ai 适配器。** 从 2.0.0 起,`grok-build` 路由完全由宿主的 `@deepseek-ai/dsh-llm-pi-ai` / `@earendil-works/pi-ai` 服务;DSH 的任何安装方式都带有它们(`@deepseek-ai/dsh` → `dsh-base` → `dsh-llm-pi-ai`,且 `llm-pi-ai` 行无条件挂载)。这两个包无法从 DSH 安装目录解析时,插件不会注册任何路由,设置页的「运行通路」显示**不可用**。
|
|
14
14
|
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
```sh
|
|
18
18
|
cd "$HOME/.dsh/profiles/desktop" # Windows: cd "$env:USERPROFILE\.dsh\profiles\desktop"
|
|
19
|
-
pnpm add --save-exact --config.minimumReleaseAge=0 dsh-grok-subscription@2.0.
|
|
19
|
+
pnpm add --save-exact --config.minimumReleaseAge=0 dsh-grok-subscription@2.0.1
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
`dsh plugin --profile desktop …` 会被直接拒绝:桌面版 profile 由 Electron 应用独占。
|
|
@@ -101,7 +101,7 @@ grok-4.6
|
|
|
101
101
|
|
|
102
102
|
```sh
|
|
103
103
|
cd "$HOME/.dsh/profiles/desktop" # Windows: cd "$env:USERPROFILE\.dsh\profiles\desktop"
|
|
104
|
-
pnpm add --save-exact --config.minimumReleaseAge=0 dsh-grok-subscription@2.0.
|
|
104
|
+
pnpm add --save-exact --config.minimumReleaseAge=0 dsh-grok-subscription@2.0.1
|
|
105
105
|
```
|
|
106
106
|
|
|
107
107
|
然后完全退出并重启桌面版。更新成功后该 profile 的 `package.json` 里依赖应变为目标版本,`dsh.profile.bundles` 条目保持不变。仅刷新网页不够:Host 未重启时仍会加载旧版本。
|
package/lib/client.js
CHANGED
|
@@ -39,7 +39,7 @@ var import_react2 = require("react");
|
|
|
39
39
|
var zh = {
|
|
40
40
|
nav: "Grok \u8BA2\u9605",
|
|
41
41
|
title: "Grok \u8BA2\u9605",
|
|
42
|
-
subtitle: "\u7528 SuperGrok / X Premium\uFF08Grok Build\uFF09\u4F1A\u8BDD\uFF0C\u800C\u4E0D\u662F XAI_API_KEY\u3002\xB7 2.0.
|
|
42
|
+
subtitle: "\u7528 SuperGrok / X Premium\uFF08Grok Build\uFF09\u4F1A\u8BDD\uFF0C\u800C\u4E0D\u662F XAI_API_KEY\u3002\xB7 2.0.1",
|
|
43
43
|
signedIn: "\u5DF2\u767B\u5F55",
|
|
44
44
|
signedOut: "\u672A\u767B\u5F55",
|
|
45
45
|
account: "\u8D26\u6237",
|
|
@@ -86,6 +86,9 @@ var zh = {
|
|
|
86
86
|
usageUsed: "\u5DF2\u7528",
|
|
87
87
|
usageRemaining: "\u5269\u4F59\u7EA6",
|
|
88
88
|
usageReset: "\u5468\u671F\u7ED3\u675F / \u91CD\u7F6E",
|
|
89
|
+
usageWindow: "\u672C\u671F\u7A97\u53E3",
|
|
90
|
+
usageUnifiedBilling: "\u4E0A\u6E38\u672A\u8FD4\u56DE\u989D\u5EA6\u767E\u5206\u6BD4\uFF1A\u8BE5\u8D26\u53F7\u5DF2\u662F\u7EDF\u4E00\u8BA1\u8D39\u5F62\u6001\uFF08\u4EE5\u9884\u4ED8\u4F59\u989D\u4E0E\u6309\u91CF\u4E0A\u9650\u8BA1\u91CF\uFF09\uFF0C\u56E0\u6B64\u6CA1\u6709\u53EF\u5C55\u793A\u7684\u5468\u989D\u5EA6\u8BFB\u6570\u3002",
|
|
91
|
+
usageMissingPercent: "\u4E0A\u6E38\u672A\u8FD4\u56DE\u989D\u5EA6\u767E\u5206\u6BD4\uFF0C\u672C\u671F\u6682\u65E0\u53EF\u7528\u8BFB\u6570\u3002",
|
|
89
92
|
usageUnavailable: "\u4E0D\u53EF\u7528",
|
|
90
93
|
usageRefresh: "\u5237\u65B0\u7528\u91CF",
|
|
91
94
|
usageOpenGrok: "\u5728 grok.com \u67E5\u770B\u7528\u91CF",
|
|
@@ -120,7 +123,7 @@ var zh = {
|
|
|
120
123
|
var en = {
|
|
121
124
|
nav: "Grok Subscription",
|
|
122
125
|
title: "Grok Subscription",
|
|
123
|
-
subtitle: "Use a SuperGrok / X Premium (Grok Build) session, not XAI_API_KEY. \xB7 2.0.
|
|
126
|
+
subtitle: "Use a SuperGrok / X Premium (Grok Build) session, not XAI_API_KEY. \xB7 2.0.1",
|
|
124
127
|
signedIn: "Signed in",
|
|
125
128
|
signedOut: "Signed out",
|
|
126
129
|
account: "Account",
|
|
@@ -167,6 +170,9 @@ var en = {
|
|
|
167
170
|
usageUsed: "Used",
|
|
168
171
|
usageRemaining: "Remaining \u2248",
|
|
169
172
|
usageReset: "Period end / reset",
|
|
173
|
+
usageWindow: "Current window",
|
|
174
|
+
usageUnifiedBilling: "The upstream API returned no credit percentage: this account is on unified billing (measured by prepaid balance and an on-demand cap), so there is no weekly credit reading to show.",
|
|
175
|
+
usageMissingPercent: "The upstream API returned no credit percentage for this period.",
|
|
170
176
|
usageUnavailable: "Unavailable",
|
|
171
177
|
usageRefresh: "Refresh usage",
|
|
172
178
|
usageOpenGrok: "View usage on grok.com",
|
|
@@ -830,6 +836,7 @@ function VersionCard({ call, t }) {
|
|
|
830
836
|
] }) : null
|
|
831
837
|
] });
|
|
832
838
|
}
|
|
839
|
+
var EXPLAINED_USAGE_CODES = /* @__PURE__ */ new Set(["unified-billing", "missing-percent"]);
|
|
833
840
|
function UsagePanel({ usage, t, usageBusy, onRefresh, signedIn }) {
|
|
834
841
|
const ok = usage?.status === "ok";
|
|
835
842
|
const usedNumber = ok && Number.isFinite(usage.usedPercent) ? usage.usedPercent : void 0;
|
|
@@ -867,9 +874,16 @@ function UsagePanel({ usage, t, usageBusy, onRefresh, signedIn }) {
|
|
|
867
874
|
] }) : null,
|
|
868
875
|
usage.periodEnd && usage.periodEndLocal ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("code", { children: usage.periodEnd }) : null
|
|
869
876
|
] })
|
|
870
|
-
] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("
|
|
871
|
-
t("usageUnavailable"),
|
|
872
|
-
usage?.
|
|
877
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "gsEmpty", children: [
|
|
878
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { children: EXPLAINED_USAGE_CODES.has(usage?.code) ? t(usage.code === "unified-billing" ? "usageUnifiedBilling" : "usageMissingPercent") : `${t("usageUnavailable")}${usage?.reason ? `: ${usage.reason}` : ""}` }),
|
|
879
|
+
usage?.periodStartLocal || usage?.periodEndLocal ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "gsHint", children: [
|
|
880
|
+
t("usageWindow"),
|
|
881
|
+
": ",
|
|
882
|
+
usage?.periodStartLocal ?? usage?.periodStart ?? "\u2014",
|
|
883
|
+
" \u2192 ",
|
|
884
|
+
usage?.periodEndLocal ?? usage?.periodEnd ?? "\u2014"
|
|
885
|
+
] }) : null,
|
|
886
|
+
EXPLAINED_USAGE_CODES.has(usage?.code) && usage?.reason ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "gsHint", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("code", { children: usage.reason }) }) : null
|
|
873
887
|
] }),
|
|
874
888
|
products.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "gsRows", children: products.map((row, index) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "gsRow", children: [
|
|
875
889
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: row.name ?? "\u2014" }),
|
package/lib/index.js
CHANGED
|
@@ -457,10 +457,11 @@ async function loadCatalog(accessToken, options = {}) {
|
|
|
457
457
|
// src/usage.js
|
|
458
458
|
var USAGE_SOURCE = "billing-credits-undocumented";
|
|
459
459
|
var SHANGHAI_TZ = "Asia/Shanghai";
|
|
460
|
-
function unavailable(reason) {
|
|
460
|
+
function unavailable(reason, extra = {}) {
|
|
461
461
|
return Object.freeze({
|
|
462
462
|
status: "unavailable",
|
|
463
463
|
reason: typeof reason === "string" && reason.trim() ? reason.trim() : "Usage unavailable",
|
|
464
|
+
...extra,
|
|
464
465
|
experimental: true,
|
|
465
466
|
source: USAGE_SOURCE
|
|
466
467
|
});
|
|
@@ -516,6 +517,23 @@ function missingPercentReason(body) {
|
|
|
516
517
|
}
|
|
517
518
|
return `Missing creditUsagePercent (keys: ${keys.join(",")})`;
|
|
518
519
|
}
|
|
520
|
+
function isUnifiedBillingBody(body) {
|
|
521
|
+
const config = isPlainObject(body.config) ? body.config : void 0;
|
|
522
|
+
return config?.isUnifiedBillingUser === true || isPlainObject(config?.onDemandCap) || isPlainObject(config?.prepaidBalance);
|
|
523
|
+
}
|
|
524
|
+
function unifiedBillingReason(body) {
|
|
525
|
+
const config = isPlainObject(body.config) ? body.config : {};
|
|
526
|
+
const balance = asFiniteNumber(config.prepaidBalance);
|
|
527
|
+
const cap = asFiniteNumber(config.onDemandCap);
|
|
528
|
+
return `No credit percentage in this billing response (unified billing: prepaidBalance ${balance ?? "n/a"}, onDemandCap ${cap ?? "n/a"})`;
|
|
529
|
+
}
|
|
530
|
+
function periodFields(body) {
|
|
531
|
+
const { start, end } = resolvePeriodBounds(body);
|
|
532
|
+
return {
|
|
533
|
+
...start ? { periodStart: start, periodStartLocal: formatShanghai(start) } : {},
|
|
534
|
+
...end ? { periodEnd: end, periodEndLocal: formatShanghai(end) } : {}
|
|
535
|
+
};
|
|
536
|
+
}
|
|
519
537
|
function firstDefined(...values) {
|
|
520
538
|
for (const value of values) {
|
|
521
539
|
if (value !== void 0 && value !== null) return value;
|
|
@@ -629,7 +647,14 @@ function parseBillingCredits(body) {
|
|
|
629
647
|
}
|
|
630
648
|
const usedPercent = resolveUsedPercent(body);
|
|
631
649
|
if (usedPercent === void 0) {
|
|
632
|
-
|
|
650
|
+
const unified = isUnifiedBillingBody(body);
|
|
651
|
+
return unavailable(
|
|
652
|
+
unified ? unifiedBillingReason(body) : missingPercentReason(body),
|
|
653
|
+
{
|
|
654
|
+
code: unified ? "unified-billing" : "missing-percent",
|
|
655
|
+
...periodFields(body)
|
|
656
|
+
}
|
|
657
|
+
);
|
|
633
658
|
}
|
|
634
659
|
const { start: periodStart, end: periodEnd } = resolvePeriodBounds(body);
|
|
635
660
|
const productUsage = sanitizeProductUsage(resolveProductUsage(body));
|
|
@@ -1936,6 +1961,10 @@ function sanitizeUsage(usage) {
|
|
|
1936
1961
|
};
|
|
1937
1962
|
if (out.status !== "ok") {
|
|
1938
1963
|
out.reason = typeof usage.reason === "string" ? usage.reason : "Usage unavailable";
|
|
1964
|
+
if (typeof usage.code === "string") out.code = usage.code;
|
|
1965
|
+
for (const field of ["periodStart", "periodStartLocal", "periodEnd", "periodEndLocal"]) {
|
|
1966
|
+
if (typeof usage[field] === "string") out[field] = usage[field];
|
|
1967
|
+
}
|
|
1939
1968
|
return out;
|
|
1940
1969
|
}
|
|
1941
1970
|
if (typeof usage.usedPercent === "number" && Number.isFinite(usage.usedPercent)) out.usedPercent = usage.usedPercent;
|