dsh-grok-subscription 2.0.1 → 2.0.3

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 CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.0.3 — 2026-09-29
4
+
5
+ - Use the **documented context window**, not the 256k value `models-v2` currently returns. The public model table lists `grok-4.7`, `grok-4.6` and `grok-4.5` at 500k, and `grok-4.7` Fast as the same model; only `grok-build-0.1` is 256k. The subscription catalog reports `context_window: 256000` for all four Build models anyway. A model id in that table now keeps the documented window (`grok-4.7-build-fast` included) even when the catalog says otherwise. An id that is not in the table still uses the catalog value, and the static fallback when a window is missing is 500k again.
6
+
7
+ ## 2.0.2 — 2026-09-29
8
+
9
+ - Read an omitted weekly percentage as **0% used / 100% remaining**, instead of telling a fresh billing period that the account has no quota number. Right after the 2026-09-28 rollover, `GET /v1/billing?format=credits` returned the unified-billing `config` (`isUnifiedBillingUser`, zero prepaid balance, zero on-demand cap, and a weekly `currentPeriod`) with no `creditUsagePercent`. That omission is proto3 dropping a zero scalar, not a new meter: the same account's `GetGrokCreditsConfig` response on grok.com (HTTP 200, `grpc-status: 0`) carried the same window and also omitted the usage-ratio field. Once usage was non-zero the field came back on the wire, and an explicit number still wins. The zero is adopted only when the period type is weekly or monthly, both bounds parse, the window contains the current time, and no percentage exists on the body or on a product row. Prepaid balance and on-demand cap are still not a percentage. Outside that window the card stays unavailable. An explicit `0` is shown the same way but is not marked as omitted.
10
+ - The reading carries `percentSource: 'omitted-zero'`. Settings prints that caveat under the gauge (both locales), and the composer badge shows the remaining 100% because the status is now `ok`. `sanitizeUsage` forwards only that exact source tag.
11
+ - The **256k context window is the live catalog, not a plugin cap**. `models-v2` on 2026-09-29 returns `context_window: 256000` for `grok-4.7`, `grok-4.7-build-fast`, `grok-4.6` and `grok-4.5`. The plugin already prefers that field; 500000 was only the fallback used when the field was absent, which is why the picker used to say 500. The static fallback now matches the current catalog (256000) so an empty listing does not advertise a window the proxy no longer publishes. A catalog value, including a future 500000, still wins.
12
+
3
13
  ## 2.0.1 — 2026-09-29
4
14
 
5
15
  - 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.
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.1`.
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.3`.
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.1
70
+ pnpm add --save-exact --config.minimumReleaseAge=0 dsh-grok-subscription@2.0.3
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.1`。
64
+ **设置 → 插件** 会列出已安装插件与版本,插件自己的 **设置 → Grok 订阅** 页也有版本卡片。要固定版本,在安装框里填入完整的 `包名@版本`,例如 `dsh-grok-subscription@2.0.3`。
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.1
70
+ pnpm add --save-exact --config.minimumReleaseAge=0 dsh-grok-subscription@2.0.3
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.1`.
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.3`.
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.1
19
+ pnpm add --save-exact --config.minimumReleaseAge=0 dsh-grok-subscription@2.0.3
20
20
  ```
21
21
 
22
22
  `dsh plugin --profile desktop …` is refused: the Electron application owns that profile exclusively.
@@ -69,7 +69,7 @@ The `grok-build` route is served by the host's official pi-ai adapter, which als
69
69
 
70
70
  Usage is read through the undocumented `/v1/billing?format=credits` endpoint and is experimental. Settings displays the used and remaining percentages actually returned. Interface changes, network failures, or invalid authentication can make the data unavailable.
71
71
 
72
- The composer badge appears only for `grok-build` with a successful usage read; hover or click for weekly quota and its reset time. Missing data does not mean zero quota. A failed read does not invent percentages or independently block chat.
72
+ The composer badge appears only for `grok-build` with a successful usage read; hover or click for weekly quota and its reset time. A failed read does not invent a percentage or independently block chat. The one exception is protobuf omitting a zero: a complete weekly or monthly `currentPeriod` that contains the current time, with no percentage field anywhere, displays as 0% used and 100% remaining, and the card says that zero was omitted rather than reported. A window that does not contain now, a prepaid balance or on-demand cap alone, or a product row that already has a percentage still produces no number.
73
73
 
74
74
  For prefix caching, the plugin pins one stable `prompt_cache_key` per DSH session (`grok:<sessionId>`) and writes it into every request; tool ordering stays stable. Auxiliary requests (compaction, session titles) share the session's key rather than taking a separate one; measured against the proxy, isolating them did not improve the chat prefix's cache reads. These are request-construction measures, not a guarantee of backend cache hits or quota savings. No invented hit rate is reported. See [adapter.js](../src/adapter.js).
75
75
 
@@ -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.1
104
+ pnpm add --save-exact --config.minimumReleaseAge=0 dsh-grok-subscription@2.0.3
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.
@@ -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.1`。
11
+ **安装:** 在桌面版打开 **设置 → 插件**,在安装输入框里填入包名 `dsh-grok-subscription` 并安装,然后**完全退出并重启桌面应用**。仅刷新页面不会重新加载 Host 中的适配器。需要可重复安装时,填入完整的 `包名@版本`,例如 `dsh-grok-subscription@2.0.3`。
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.1
19
+ pnpm add --save-exact --config.minimumReleaseAge=0 dsh-grok-subscription@2.0.3
20
20
  ```
21
21
 
22
22
  `dsh plugin --profile desktop …` 会被直接拒绝:桌面版 profile 由 Electron 应用独占。
@@ -69,7 +69,7 @@ grok-4.6
69
69
 
70
70
  用量通过未公开的 `/v1/billing?format=credits` 接口读取,属于实验性能力。设置页展示实际返回的已用和剩余比例;接口变化、网络异常或认证失效都可能使数据不可用。
71
71
 
72
- 输入框徽章仅在当前 provider 为 `grok-build` 且用量读取成功时显示,悬停或点击查看每周余量与重置时间。缺失数据不是额度为零;读取失败不会编造百分比,也不会因为单独的额度读取故障阻止聊天。
72
+ 输入框徽章仅在当前 provider 为 `grok-build` 且用量读取成功时显示,悬停或点击查看每周余量与重置时间。读取失败不会编造百分比,也不会因为单独的额度读取故障阻止聊天。唯一的例外是 protobuf 把 0 省略掉:响应里有完整的周或月 `currentPeriod`、窗口包住当前时间、并且没有任何百分比字段时,显示已用 0%、剩余 100%,并标明这是零值省略。窗口对不上、只有预付余额或按量上限、或者产品明细里已经有百分比时,仍然不显示数字。
73
73
 
74
74
  前缀缓存方面,插件为同一 DSH 会话固定同一个 `prompt_cache_key`(`grok:<sessionId>`),并在请求里显式写入,工具顺序保持稳定。压缩、会话标题这类辅助请求与会话共用同一个 key,而不是另开一个——对代理实测下来,隔离它们并不会提高聊天前缀的缓存命中。这些是请求构造措施,不保证服务端缓存命中或节省额度;项目不提供虚构的命中率。实现见 [adapter.js](../src/adapter.js)。
75
75
 
@@ -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.1
104
+ pnpm add --save-exact --config.minimumReleaseAge=0 dsh-grok-subscription@2.0.3
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.1",
42
+ subtitle: "\u7528 SuperGrok / X Premium\uFF08Grok Build\uFF09\u4F1A\u8BDD\uFF0C\u800C\u4E0D\u662F XAI_API_KEY\u3002\xB7 2.0.3",
43
43
  signedIn: "\u5DF2\u767B\u5F55",
44
44
  signedOut: "\u672A\u767B\u5F55",
45
45
  account: "\u8D26\u6237",
@@ -89,6 +89,7 @@ var zh = {
89
89
  usageWindow: "\u672C\u671F\u7A97\u53E3",
90
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
91
  usageMissingPercent: "\u4E0A\u6E38\u672A\u8FD4\u56DE\u989D\u5EA6\u767E\u5206\u6BD4\uFF0C\u672C\u671F\u6682\u65E0\u53EF\u7528\u8BFB\u6570\u3002",
92
+ usageOmittedZero: "\u63A5\u53E3\u7701\u7565\u4E86 0 \u503C\u767E\u5206\u6BD4\uFF0C\u56E0\u6B64\u663E\u793A\u4E3A\u5DF2\u7528 0%\u3001\u5269\u4F59 100%\u3002\u8FD9\u4E0D\u662F\u53E6\u62A5\u7684\u6570\u5B57\uFF1B\u7A97\u53E3\u5BF9\u4E0D\u4E0A\u5F53\u524D\u65F6\u95F4\u65F6\u4E0D\u4F1A\u8FD9\u6837\u663E\u793A\u3002",
92
93
  usageUnavailable: "\u4E0D\u53EF\u7528",
93
94
  usageRefresh: "\u5237\u65B0\u7528\u91CF",
94
95
  usageOpenGrok: "\u5728 grok.com \u67E5\u770B\u7528\u91CF",
@@ -123,7 +124,7 @@ var zh = {
123
124
  var en = {
124
125
  nav: "Grok Subscription",
125
126
  title: "Grok Subscription",
126
- subtitle: "Use a SuperGrok / X Premium (Grok Build) session, not XAI_API_KEY. \xB7 2.0.1",
127
+ subtitle: "Use a SuperGrok / X Premium (Grok Build) session, not XAI_API_KEY. \xB7 2.0.3",
127
128
  signedIn: "Signed in",
128
129
  signedOut: "Signed out",
129
130
  account: "Account",
@@ -173,6 +174,7 @@ var en = {
173
174
  usageWindow: "Current window",
174
175
  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
176
  usageMissingPercent: "The upstream API returned no credit percentage for this period.",
177
+ usageOmittedZero: "The API omitted a zero percentage, so this shows 0% used and 100% remaining. It is not a separately reported number, and it is not shown unless the billing window contains the current time.",
176
178
  usageUnavailable: "Unavailable",
177
179
  usageRefresh: "Refresh usage",
178
180
  usageOpenGrok: "View usage on grok.com",
@@ -873,7 +875,8 @@ function UsagePanel({ usage, t, usageBusy, onRefresh, signedIn }) {
873
875
  resetLabel
874
876
  ] }) : null,
875
877
  usage.periodEnd && usage.periodEndLocal ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("code", { children: usage.periodEnd }) : null
876
- ] })
878
+ ] }),
879
+ usage.percentSource === "omitted-zero" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "gsHint", children: t("usageOmittedZero") }) : null
877
880
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "gsEmpty", children: [
878
881
  /* @__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
882
  usage?.periodStartLocal || usage?.periodEndLocal ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "gsHint", children: [
package/lib/index.js CHANGED
@@ -323,6 +323,21 @@ async function readResponseJson(response, options = {}) {
323
323
 
324
324
  // src/catalog.js
325
325
  var REASONING_LEVELS = Object.freeze(["low", "medium", "high", "xhigh"]);
326
+ var DOCUMENTED_CONTEXT_WINDOWS = Object.freeze({
327
+ "grok-4.7": 5e5,
328
+ "grok-4.7-build-fast": 5e5,
329
+ "grok-4.6": 5e5,
330
+ "grok-4.5": 5e5,
331
+ "grok-4.3": 1e6,
332
+ "grok-4.20-0309-reasoning": 1e6,
333
+ "grok-4.20-0309-non-reasoning": 1e6,
334
+ "grok-4.20-multi-agent-0309": 1e6,
335
+ "grok-build-0.1": 256e3
336
+ });
337
+ var FALLBACK_CONTEXT_WINDOW = 5e5;
338
+ function documentedContextWindow(id) {
339
+ return Object.prototype.hasOwnProperty.call(DOCUMENTED_CONTEXT_WINDOWS, id) ? DOCUMENTED_CONTEXT_WINDOWS[id] : void 0;
340
+ }
326
341
  function asId(value) {
327
342
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
328
343
  }
@@ -357,11 +372,12 @@ function extractLiveModels(body) {
357
372
  if (!id || seen.has(id)) continue;
358
373
  seen.add(id);
359
374
  const object = row && typeof row === "object" ? row : { id };
375
+ const contextWindow = documentedContextWindow(id) ?? contextWindowOf(object) ?? FALLBACK_CONTEXT_WINDOW;
360
376
  models.push(Object.freeze({
361
377
  id,
362
378
  name: displayNameOf(id, object),
363
- contextWindow: contextWindowOf(object) ?? 5e5,
364
- maxTokens: contextWindowOf(object) ?? 5e5,
379
+ contextWindow,
380
+ maxTokens: contextWindow,
365
381
  reasoning: object.reasoning !== false,
366
382
  reasoningEfforts: reasoningEffortsOf(object)
367
383
  }));
@@ -372,8 +388,8 @@ function fallbackModels() {
372
388
  return FALLBACK_MODEL_IDS.map((id) => Object.freeze({
373
389
  id,
374
390
  name: id === "grok-4.7" ? "Grok 4.7" : id === "grok-4.6" ? "Grok 4.6" : "Grok 4.5",
375
- contextWindow: 5e5,
376
- maxTokens: 5e5,
391
+ contextWindow: documentedContextWindow(id) ?? FALLBACK_CONTEXT_WINDOW,
392
+ maxTokens: documentedContextWindow(id) ?? FALLBACK_CONTEXT_WINDOW,
377
393
  reasoning: true,
378
394
  reasoningEfforts: id === "grok-4.5" ? ["low", "medium", "high"] : ["low", "medium", "high", "xhigh"],
379
395
  defaultEffort: "high",
@@ -411,8 +427,8 @@ function toPiModels(models) {
411
427
  thinkingLevelMap,
412
428
  input: supportsImageInput(model.id) ? ["text", "image"] : ["text"],
413
429
  cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
414
- contextWindow: model.contextWindow ?? 5e5,
415
- maxTokens: model.maxTokens ?? 5e5,
430
+ contextWindow: model.contextWindow ?? FALLBACK_CONTEXT_WINDOW,
431
+ maxTokens: model.maxTokens ?? FALLBACK_CONTEXT_WINDOW,
416
432
  compat: { supportsLongCacheRetention: false, supportsDeveloperRole: false }
417
433
  };
418
434
  });
@@ -585,6 +601,48 @@ function resolveUsedPercent(body) {
585
601
  }
586
602
  return void 0;
587
603
  }
604
+ function nowMillis(options) {
605
+ if (options?.now instanceof Date) {
606
+ const ms = options.now.getTime();
607
+ return Number.isFinite(ms) ? ms : Date.now();
608
+ }
609
+ if (typeof options?.now === "number" && Number.isFinite(options.now)) return options.now;
610
+ return Date.now();
611
+ }
612
+ function currentPeriodOf(body) {
613
+ const config = isPlainObject(body.config) ? body.config : void 0;
614
+ if (isPlainObject(config?.currentPeriod)) return config.currentPeriod;
615
+ if (isPlainObject(body.currentPeriod)) return body.currentPeriod;
616
+ return void 0;
617
+ }
618
+ function productHasPercent(body) {
619
+ const raw = resolveProductUsage(body);
620
+ if (!Array.isArray(raw)) return false;
621
+ return raw.some((item) => {
622
+ if (!item || typeof item !== "object") return false;
623
+ return asFinitePercent(firstDefined(
624
+ item.creditUsagePercent,
625
+ item.usedPercent,
626
+ item.usagePercent,
627
+ item.percentUsed,
628
+ item.used_percent
629
+ )) !== void 0;
630
+ });
631
+ }
632
+ function omittedZeroReading(body, nowMs) {
633
+ const period = currentPeriodOf(body);
634
+ if (!period) return void 0;
635
+ const type = typeof period.type === "string" ? period.type.trim() : "";
636
+ if (!/weekly$/i.test(type) && !/monthly$/i.test(type)) return void 0;
637
+ const start = asIsoString(period.start);
638
+ const end = asIsoString(period.end);
639
+ if (!start || !end) return void 0;
640
+ const startMs = Date.parse(start);
641
+ const endMs = Date.parse(end);
642
+ if (!(endMs > startMs) || nowMs < startMs || nowMs >= endMs) return void 0;
643
+ if (productHasPercent(body)) return void 0;
644
+ return { start, end };
645
+ }
588
646
  function resolvePeriodBounds(body) {
589
647
  const config = isPlainObject(body.config) ? body.config : void 0;
590
648
  const period = firstDefined(
@@ -638,14 +696,16 @@ function sanitizeProductUsage(raw) {
638
696
  }
639
697
  return rows.length ? Object.freeze(rows) : void 0;
640
698
  }
641
- function parseBillingCredits(body) {
699
+ function parseBillingCredits(body, options = {}) {
642
700
  if (body === null || body === void 0) {
643
701
  return unavailable("Empty billing response");
644
702
  }
645
703
  if (typeof body !== "object" || Array.isArray(body)) {
646
704
  return unavailable("Unexpected billing response shape");
647
705
  }
648
- const usedPercent = resolveUsedPercent(body);
706
+ const explicitPercent = resolveUsedPercent(body);
707
+ const omittedZero = explicitPercent === void 0 ? omittedZeroReading(body, nowMillis(options)) : void 0;
708
+ const usedPercent = explicitPercent ?? (omittedZero ? 0 : void 0);
649
709
  if (usedPercent === void 0) {
650
710
  const unified = isUnifiedBillingBody(body);
651
711
  return unavailable(
@@ -665,12 +725,13 @@ function parseBillingCredits(body) {
665
725
  source: USAGE_SOURCE,
666
726
  usedPercent,
667
727
  remainingPercent,
728
+ ...omittedZero ? { percentSource: "omitted-zero" } : {},
668
729
  ...periodStart ? { periodStart, periodStartLocal: formatShanghai(periodStart) } : {},
669
730
  ...periodEnd ? { periodEnd, periodEndLocal: formatShanghai(periodEnd) } : {},
670
731
  ...productUsage ? { productUsage } : {}
671
732
  });
672
733
  }
673
- function parseBillingCreditsJson(text) {
734
+ function parseBillingCreditsJson(text, options = {}) {
674
735
  if (typeof text !== "string") return unavailable("Non-text billing response");
675
736
  if (!text.trim()) return unavailable("Empty billing response");
676
737
  let body;
@@ -679,7 +740,7 @@ function parseBillingCreditsJson(text) {
679
740
  } catch {
680
741
  return unavailable("Invalid JSON from billing API");
681
742
  }
682
- return parseBillingCredits(body);
743
+ return parseBillingCredits(body, options);
683
744
  }
684
745
  async function fetchBillingUsageOnce(accessToken, options = {}) {
685
746
  const fetchImpl = options.fetch ?? globalThis.fetch;
@@ -1974,6 +2035,7 @@ function sanitizeUsage(usage) {
1974
2035
  if (typeof usage.periodEnd === "string") out.periodEnd = usage.periodEnd;
1975
2036
  if (typeof usage.periodEndLocal === "string") out.periodEndLocal = usage.periodEndLocal;
1976
2037
  if (typeof usage.fetchedAt === "string") out.fetchedAt = usage.fetchedAt;
2038
+ if (usage.percentSource === "omitted-zero") out.percentSource = "omitted-zero";
1977
2039
  if (Array.isArray(usage.productUsage)) {
1978
2040
  out.productUsage = usage.productUsage.filter((row) => row && typeof row === "object").map((row) => ({
1979
2041
  ...typeof row.name === "string" ? { name: row.name } : {},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-grok-subscription",
3
- "version": "2.0.1",
3
+ "version": "2.0.3",
4
4
  "description": "Use a SuperGrok or X Premium Grok Build subscription in DeepSeek Harness",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",