dsh-grok-subscription 2.0.0 → 2.0.2
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 +16 -0
- package/README.en.md +2 -2
- package/README.md +2 -2
- package/docs/guide.en.md +4 -4
- package/docs/guide.zh-CN.md +4 -4
- package/lib/client.js +23 -6
- package/lib/index.js +88 -12
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.0.2 — 2026-09-29
|
|
4
|
+
|
|
5
|
+
- 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.
|
|
6
|
+
- 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.
|
|
7
|
+
- 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.
|
|
8
|
+
|
|
9
|
+
## 2.0.1 — 2026-09-29
|
|
10
|
+
|
|
11
|
+
- 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.
|
|
12
|
+
- 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.
|
|
13
|
+
- **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.
|
|
14
|
+
- 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.
|
|
15
|
+
- No adapter, routing, session or RPC-surface changes. `sanitizeUsage` carries the new diagnostic fields through the RPC without exposing anything else.
|
|
16
|
+
|
|
17
|
+
## 2.0.0 — 2026-09-28
|
|
18
|
+
|
|
3
19
|
## 2.0.0 — 2026-09-28
|
|
4
20
|
|
|
5
21
|
- **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.2`.
|
|
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.2
|
|
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.2`。
|
|
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.2
|
|
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.2`.
|
|
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.2
|
|
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.
|
|
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.
|
|
104
|
+
pnpm add --save-exact --config.minimumReleaseAge=0 dsh-grok-subscription@2.0.2
|
|
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.2`。
|
|
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.2
|
|
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.
|
|
104
|
+
pnpm add --save-exact --config.minimumReleaseAge=0 dsh-grok-subscription@2.0.2
|
|
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.2",
|
|
43
43
|
signedIn: "\u5DF2\u767B\u5F55",
|
|
44
44
|
signedOut: "\u672A\u767B\u5F55",
|
|
45
45
|
account: "\u8D26\u6237",
|
|
@@ -86,6 +86,10 @@ 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",
|
|
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",
|
|
89
93
|
usageUnavailable: "\u4E0D\u53EF\u7528",
|
|
90
94
|
usageRefresh: "\u5237\u65B0\u7528\u91CF",
|
|
91
95
|
usageOpenGrok: "\u5728 grok.com \u67E5\u770B\u7528\u91CF",
|
|
@@ -120,7 +124,7 @@ var zh = {
|
|
|
120
124
|
var en = {
|
|
121
125
|
nav: "Grok Subscription",
|
|
122
126
|
title: "Grok Subscription",
|
|
123
|
-
subtitle: "Use a SuperGrok / X Premium (Grok Build) session, not XAI_API_KEY. \xB7 2.0.
|
|
127
|
+
subtitle: "Use a SuperGrok / X Premium (Grok Build) session, not XAI_API_KEY. \xB7 2.0.2",
|
|
124
128
|
signedIn: "Signed in",
|
|
125
129
|
signedOut: "Signed out",
|
|
126
130
|
account: "Account",
|
|
@@ -167,6 +171,10 @@ var en = {
|
|
|
167
171
|
usageUsed: "Used",
|
|
168
172
|
usageRemaining: "Remaining \u2248",
|
|
169
173
|
usageReset: "Period end / reset",
|
|
174
|
+
usageWindow: "Current window",
|
|
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.",
|
|
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.",
|
|
170
178
|
usageUnavailable: "Unavailable",
|
|
171
179
|
usageRefresh: "Refresh usage",
|
|
172
180
|
usageOpenGrok: "View usage on grok.com",
|
|
@@ -830,6 +838,7 @@ function VersionCard({ call, t }) {
|
|
|
830
838
|
] }) : null
|
|
831
839
|
] });
|
|
832
840
|
}
|
|
841
|
+
var EXPLAINED_USAGE_CODES = /* @__PURE__ */ new Set(["unified-billing", "missing-percent"]);
|
|
833
842
|
function UsagePanel({ usage, t, usageBusy, onRefresh, signedIn }) {
|
|
834
843
|
const ok = usage?.status === "ok";
|
|
835
844
|
const usedNumber = ok && Number.isFinite(usage.usedPercent) ? usage.usedPercent : void 0;
|
|
@@ -866,10 +875,18 @@ function UsagePanel({ usage, t, usageBusy, onRefresh, signedIn }) {
|
|
|
866
875
|
resetLabel
|
|
867
876
|
] }) : null,
|
|
868
877
|
usage.periodEnd && usage.periodEndLocal ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("code", { children: usage.periodEnd }) : null
|
|
869
|
-
] })
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
usage?.reason ? `: ${usage.reason}` : ""
|
|
878
|
+
] }),
|
|
879
|
+
usage.percentSource === "omitted-zero" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "gsHint", children: t("usageOmittedZero") }) : null
|
|
880
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "gsEmpty", children: [
|
|
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}` : ""}` }),
|
|
882
|
+
usage?.periodStartLocal || usage?.periodEndLocal ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "gsHint", children: [
|
|
883
|
+
t("usageWindow"),
|
|
884
|
+
": ",
|
|
885
|
+
usage?.periodStartLocal ?? usage?.periodStart ?? "\u2014",
|
|
886
|
+
" \u2192 ",
|
|
887
|
+
usage?.periodEndLocal ?? usage?.periodEnd ?? "\u2014"
|
|
888
|
+
] }) : null,
|
|
889
|
+
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
890
|
] }),
|
|
874
891
|
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
892
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: row.name ?? "\u2014" }),
|
package/lib/index.js
CHANGED
|
@@ -323,6 +323,7 @@ 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 FALLBACK_CONTEXT_WINDOW = 256e3;
|
|
326
327
|
function asId(value) {
|
|
327
328
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
328
329
|
}
|
|
@@ -360,8 +361,8 @@ function extractLiveModels(body) {
|
|
|
360
361
|
models.push(Object.freeze({
|
|
361
362
|
id,
|
|
362
363
|
name: displayNameOf(id, object),
|
|
363
|
-
contextWindow: contextWindowOf(object) ??
|
|
364
|
-
maxTokens: contextWindowOf(object) ??
|
|
364
|
+
contextWindow: contextWindowOf(object) ?? FALLBACK_CONTEXT_WINDOW,
|
|
365
|
+
maxTokens: contextWindowOf(object) ?? FALLBACK_CONTEXT_WINDOW,
|
|
365
366
|
reasoning: object.reasoning !== false,
|
|
366
367
|
reasoningEfforts: reasoningEffortsOf(object)
|
|
367
368
|
}));
|
|
@@ -372,8 +373,8 @@ function fallbackModels() {
|
|
|
372
373
|
return FALLBACK_MODEL_IDS.map((id) => Object.freeze({
|
|
373
374
|
id,
|
|
374
375
|
name: id === "grok-4.7" ? "Grok 4.7" : id === "grok-4.6" ? "Grok 4.6" : "Grok 4.5",
|
|
375
|
-
contextWindow:
|
|
376
|
-
maxTokens:
|
|
376
|
+
contextWindow: FALLBACK_CONTEXT_WINDOW,
|
|
377
|
+
maxTokens: FALLBACK_CONTEXT_WINDOW,
|
|
377
378
|
reasoning: true,
|
|
378
379
|
reasoningEfforts: id === "grok-4.5" ? ["low", "medium", "high"] : ["low", "medium", "high", "xhigh"],
|
|
379
380
|
defaultEffort: "high",
|
|
@@ -411,8 +412,8 @@ function toPiModels(models) {
|
|
|
411
412
|
thinkingLevelMap,
|
|
412
413
|
input: supportsImageInput(model.id) ? ["text", "image"] : ["text"],
|
|
413
414
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
414
|
-
contextWindow: model.contextWindow ??
|
|
415
|
-
maxTokens: model.maxTokens ??
|
|
415
|
+
contextWindow: model.contextWindow ?? FALLBACK_CONTEXT_WINDOW,
|
|
416
|
+
maxTokens: model.maxTokens ?? FALLBACK_CONTEXT_WINDOW,
|
|
416
417
|
compat: { supportsLongCacheRetention: false, supportsDeveloperRole: false }
|
|
417
418
|
};
|
|
418
419
|
});
|
|
@@ -457,10 +458,11 @@ async function loadCatalog(accessToken, options = {}) {
|
|
|
457
458
|
// src/usage.js
|
|
458
459
|
var USAGE_SOURCE = "billing-credits-undocumented";
|
|
459
460
|
var SHANGHAI_TZ = "Asia/Shanghai";
|
|
460
|
-
function unavailable(reason) {
|
|
461
|
+
function unavailable(reason, extra = {}) {
|
|
461
462
|
return Object.freeze({
|
|
462
463
|
status: "unavailable",
|
|
463
464
|
reason: typeof reason === "string" && reason.trim() ? reason.trim() : "Usage unavailable",
|
|
465
|
+
...extra,
|
|
464
466
|
experimental: true,
|
|
465
467
|
source: USAGE_SOURCE
|
|
466
468
|
});
|
|
@@ -516,6 +518,23 @@ function missingPercentReason(body) {
|
|
|
516
518
|
}
|
|
517
519
|
return `Missing creditUsagePercent (keys: ${keys.join(",")})`;
|
|
518
520
|
}
|
|
521
|
+
function isUnifiedBillingBody(body) {
|
|
522
|
+
const config = isPlainObject(body.config) ? body.config : void 0;
|
|
523
|
+
return config?.isUnifiedBillingUser === true || isPlainObject(config?.onDemandCap) || isPlainObject(config?.prepaidBalance);
|
|
524
|
+
}
|
|
525
|
+
function unifiedBillingReason(body) {
|
|
526
|
+
const config = isPlainObject(body.config) ? body.config : {};
|
|
527
|
+
const balance = asFiniteNumber(config.prepaidBalance);
|
|
528
|
+
const cap = asFiniteNumber(config.onDemandCap);
|
|
529
|
+
return `No credit percentage in this billing response (unified billing: prepaidBalance ${balance ?? "n/a"}, onDemandCap ${cap ?? "n/a"})`;
|
|
530
|
+
}
|
|
531
|
+
function periodFields(body) {
|
|
532
|
+
const { start, end } = resolvePeriodBounds(body);
|
|
533
|
+
return {
|
|
534
|
+
...start ? { periodStart: start, periodStartLocal: formatShanghai(start) } : {},
|
|
535
|
+
...end ? { periodEnd: end, periodEndLocal: formatShanghai(end) } : {}
|
|
536
|
+
};
|
|
537
|
+
}
|
|
519
538
|
function firstDefined(...values) {
|
|
520
539
|
for (const value of values) {
|
|
521
540
|
if (value !== void 0 && value !== null) return value;
|
|
@@ -567,6 +586,48 @@ function resolveUsedPercent(body) {
|
|
|
567
586
|
}
|
|
568
587
|
return void 0;
|
|
569
588
|
}
|
|
589
|
+
function nowMillis(options) {
|
|
590
|
+
if (options?.now instanceof Date) {
|
|
591
|
+
const ms = options.now.getTime();
|
|
592
|
+
return Number.isFinite(ms) ? ms : Date.now();
|
|
593
|
+
}
|
|
594
|
+
if (typeof options?.now === "number" && Number.isFinite(options.now)) return options.now;
|
|
595
|
+
return Date.now();
|
|
596
|
+
}
|
|
597
|
+
function currentPeriodOf(body) {
|
|
598
|
+
const config = isPlainObject(body.config) ? body.config : void 0;
|
|
599
|
+
if (isPlainObject(config?.currentPeriod)) return config.currentPeriod;
|
|
600
|
+
if (isPlainObject(body.currentPeriod)) return body.currentPeriod;
|
|
601
|
+
return void 0;
|
|
602
|
+
}
|
|
603
|
+
function productHasPercent(body) {
|
|
604
|
+
const raw = resolveProductUsage(body);
|
|
605
|
+
if (!Array.isArray(raw)) return false;
|
|
606
|
+
return raw.some((item) => {
|
|
607
|
+
if (!item || typeof item !== "object") return false;
|
|
608
|
+
return asFinitePercent(firstDefined(
|
|
609
|
+
item.creditUsagePercent,
|
|
610
|
+
item.usedPercent,
|
|
611
|
+
item.usagePercent,
|
|
612
|
+
item.percentUsed,
|
|
613
|
+
item.used_percent
|
|
614
|
+
)) !== void 0;
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
function omittedZeroReading(body, nowMs) {
|
|
618
|
+
const period = currentPeriodOf(body);
|
|
619
|
+
if (!period) return void 0;
|
|
620
|
+
const type = typeof period.type === "string" ? period.type.trim() : "";
|
|
621
|
+
if (!/weekly$/i.test(type) && !/monthly$/i.test(type)) return void 0;
|
|
622
|
+
const start = asIsoString(period.start);
|
|
623
|
+
const end = asIsoString(period.end);
|
|
624
|
+
if (!start || !end) return void 0;
|
|
625
|
+
const startMs = Date.parse(start);
|
|
626
|
+
const endMs = Date.parse(end);
|
|
627
|
+
if (!(endMs > startMs) || nowMs < startMs || nowMs >= endMs) return void 0;
|
|
628
|
+
if (productHasPercent(body)) return void 0;
|
|
629
|
+
return { start, end };
|
|
630
|
+
}
|
|
570
631
|
function resolvePeriodBounds(body) {
|
|
571
632
|
const config = isPlainObject(body.config) ? body.config : void 0;
|
|
572
633
|
const period = firstDefined(
|
|
@@ -620,16 +681,25 @@ function sanitizeProductUsage(raw) {
|
|
|
620
681
|
}
|
|
621
682
|
return rows.length ? Object.freeze(rows) : void 0;
|
|
622
683
|
}
|
|
623
|
-
function parseBillingCredits(body) {
|
|
684
|
+
function parseBillingCredits(body, options = {}) {
|
|
624
685
|
if (body === null || body === void 0) {
|
|
625
686
|
return unavailable("Empty billing response");
|
|
626
687
|
}
|
|
627
688
|
if (typeof body !== "object" || Array.isArray(body)) {
|
|
628
689
|
return unavailable("Unexpected billing response shape");
|
|
629
690
|
}
|
|
630
|
-
const
|
|
691
|
+
const explicitPercent = resolveUsedPercent(body);
|
|
692
|
+
const omittedZero = explicitPercent === void 0 ? omittedZeroReading(body, nowMillis(options)) : void 0;
|
|
693
|
+
const usedPercent = explicitPercent ?? (omittedZero ? 0 : void 0);
|
|
631
694
|
if (usedPercent === void 0) {
|
|
632
|
-
|
|
695
|
+
const unified = isUnifiedBillingBody(body);
|
|
696
|
+
return unavailable(
|
|
697
|
+
unified ? unifiedBillingReason(body) : missingPercentReason(body),
|
|
698
|
+
{
|
|
699
|
+
code: unified ? "unified-billing" : "missing-percent",
|
|
700
|
+
...periodFields(body)
|
|
701
|
+
}
|
|
702
|
+
);
|
|
633
703
|
}
|
|
634
704
|
const { start: periodStart, end: periodEnd } = resolvePeriodBounds(body);
|
|
635
705
|
const productUsage = sanitizeProductUsage(resolveProductUsage(body));
|
|
@@ -640,12 +710,13 @@ function parseBillingCredits(body) {
|
|
|
640
710
|
source: USAGE_SOURCE,
|
|
641
711
|
usedPercent,
|
|
642
712
|
remainingPercent,
|
|
713
|
+
...omittedZero ? { percentSource: "omitted-zero" } : {},
|
|
643
714
|
...periodStart ? { periodStart, periodStartLocal: formatShanghai(periodStart) } : {},
|
|
644
715
|
...periodEnd ? { periodEnd, periodEndLocal: formatShanghai(periodEnd) } : {},
|
|
645
716
|
...productUsage ? { productUsage } : {}
|
|
646
717
|
});
|
|
647
718
|
}
|
|
648
|
-
function parseBillingCreditsJson(text) {
|
|
719
|
+
function parseBillingCreditsJson(text, options = {}) {
|
|
649
720
|
if (typeof text !== "string") return unavailable("Non-text billing response");
|
|
650
721
|
if (!text.trim()) return unavailable("Empty billing response");
|
|
651
722
|
let body;
|
|
@@ -654,7 +725,7 @@ function parseBillingCreditsJson(text) {
|
|
|
654
725
|
} catch {
|
|
655
726
|
return unavailable("Invalid JSON from billing API");
|
|
656
727
|
}
|
|
657
|
-
return parseBillingCredits(body);
|
|
728
|
+
return parseBillingCredits(body, options);
|
|
658
729
|
}
|
|
659
730
|
async function fetchBillingUsageOnce(accessToken, options = {}) {
|
|
660
731
|
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
@@ -1936,6 +2007,10 @@ function sanitizeUsage(usage) {
|
|
|
1936
2007
|
};
|
|
1937
2008
|
if (out.status !== "ok") {
|
|
1938
2009
|
out.reason = typeof usage.reason === "string" ? usage.reason : "Usage unavailable";
|
|
2010
|
+
if (typeof usage.code === "string") out.code = usage.code;
|
|
2011
|
+
for (const field of ["periodStart", "periodStartLocal", "periodEnd", "periodEndLocal"]) {
|
|
2012
|
+
if (typeof usage[field] === "string") out[field] = usage[field];
|
|
2013
|
+
}
|
|
1939
2014
|
return out;
|
|
1940
2015
|
}
|
|
1941
2016
|
if (typeof usage.usedPercent === "number" && Number.isFinite(usage.usedPercent)) out.usedPercent = usage.usedPercent;
|
|
@@ -1945,6 +2020,7 @@ function sanitizeUsage(usage) {
|
|
|
1945
2020
|
if (typeof usage.periodEnd === "string") out.periodEnd = usage.periodEnd;
|
|
1946
2021
|
if (typeof usage.periodEndLocal === "string") out.periodEndLocal = usage.periodEndLocal;
|
|
1947
2022
|
if (typeof usage.fetchedAt === "string") out.fetchedAt = usage.fetchedAt;
|
|
2023
|
+
if (usage.percentSource === "omitted-zero") out.percentSource = "omitted-zero";
|
|
1948
2024
|
if (Array.isArray(usage.productUsage)) {
|
|
1949
2025
|
out.productUsage = usage.productUsage.filter((row) => row && typeof row === "object").map((row) => ({
|
|
1950
2026
|
...typeof row.name === "string" ? { name: row.name } : {},
|