dsh-grok-subscription 1.0.0 → 1.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.
@@ -0,0 +1,42 @@
1
+ # Contributing
2
+
3
+ Bug reports, focused fixes, and compatibility updates are welcome.
4
+
5
+ ## Before changing code
6
+
7
+ - Keep one pull request focused on one problem. Do not bundle unrelated
8
+ refactors, generated artifacts, or release preparation.
9
+ - Never commit credentials. This plugin reads a real Grok Build session, so no
10
+ `~/.grok/auth.json` contents, access or refresh tokens, account identifiers,
11
+ or raw subscription responses belong in the repository, tests, or issues.
12
+ - Reuse DSH's native model, settings, tool, and plugin-lifecycle contracts
13
+ instead of introducing a parallel subsystem.
14
+
15
+ ## Local verification
16
+
17
+ ```sh
18
+ npm install
19
+ npm test
20
+ npm run build
21
+ ```
22
+
23
+ `lib/` is committed and generated by the build — never edit it by hand. Run
24
+ `npm run build` and commit the result whenever `src/` changes; CI fails if the
25
+ committed `lib/` differs from a fresh build.
26
+
27
+ Changes that affect the DSH UI or the plugin lifecycle also need a check in a
28
+ real DSH installation; source tests alone are not installed-product acceptance.
29
+ The host-side adapter is loaded into memory, so restart `dsh web` before judging
30
+ a host change, and hard-refresh the browser for client changes.
31
+
32
+ ## Pull requests
33
+
34
+ Describe the user-visible problem, the smallest implemented change, the checks
35
+ you ran, and any remaining compatibility risk. When user instructions change,
36
+ keep `README.md` and `README.en.md` behaviourally aligned.
37
+
38
+ The READMEs are user documentation. They explain the product and its supported
39
+ use, not internal debugging history or private design discussion.
40
+
41
+ A merged pull request does not imply an immediate npm release; maintainers batch
42
+ fixes into planned releases.
package/README.en.md ADDED
@@ -0,0 +1,218 @@
1
+ <div align="center">
2
+
3
+ # DSH Grok Subscription — Use SuperGrok / X Premium subscriptions in DeepSeek Harness
4
+
5
+ [简体中文](https://github.com/BaronCyrus/dsh-grok-subscription/blob/main/README.md) · **English**
6
+
7
+ **Use your SuperGrok / X Premium (Grok Build) subscription directly in DeepSeek Harness**
8
+
9
+ Reuse the official Grok Build CLI session — no `XAI_API_KEY` needed.
10
+ Models, reasoning effort, and weekly quota all stay inside DSH.
11
+
12
+ [![CI](https://github.com/BaronCyrus/dsh-grok-subscription/actions/workflows/ci.yml/badge.svg)](https://github.com/BaronCyrus/dsh-grok-subscription/actions/workflows/ci.yml)
13
+ [![npm](https://img.shields.io/npm/v/dsh-grok-subscription?logo=npm&label=npm)](https://www.npmjs.com/package/dsh-grok-subscription)
14
+ [![total npm downloads](https://img.shields.io/npm/dt/dsh-grok-subscription?logo=npm&label=total%20downloads)](https://www.npmjs.com/package/dsh-grok-subscription)
15
+ [![MIT](https://img.shields.io/badge/license-MIT-111111.svg)](LICENSE)
16
+ [![Star](https://img.shields.io/github/stars/BaronCyrus/dsh-grok-subscription?style=flat&logo=github&label=Star)](https://github.com/BaronCyrus/dsh-grok-subscription/stargazers)
17
+
18
+ [Three-step start](#three-step-start) · [Install](#install) · [Contribute](CONTRIBUTING.md) · [Update and uninstall](#update-and-uninstall)
19
+
20
+ </div>
21
+
22
+ <p align="center">
23
+ <img src="docs/assets/grok-subscription-overview.webp" width="900" alt="Select Grok 4.7 in DeepSeek Harness and chat through a SuperGrok subscription">
24
+ </p>
25
+
26
+ ## Three-step start
27
+
28
+ 1. **Install the plugin.** Run the command below; to select a candidate version, enter the full `package@version`, for example `dsh-grok-subscription@1.0.2`.
29
+
30
+ ```sh
31
+ dsh plugin --profile web add BaronCyrus/dsh-grok-subscription
32
+ ```
33
+
34
+ 2. **Sign in to the subscription.** Open **Settings → Grok Subscription** and click **CLI login** or **Device-code login**, then finish the official prompt in the terminal that started DSH. If you already ran `grok login` yourself, just click **Pull from Grok CLI** — nothing has to be pasted.
35
+ 3. **Start using it.** Pick a model such as Grok 4.7 in the model picker. The round badge next to the picker shows weekly remaining quota, and the model menu offers reasoning effort levels.
36
+
37
+ Restart `dsh web` after installing or upgrading, otherwise the old `lib/` keeps running.
38
+
39
+ ## Why this plugin
40
+
41
+ | Capability | What you get |
42
+ | --- | --- |
43
+ | **Direct subscription routing** | Reuse the official Grok Build CLI session — no `XAI_API_KEY` needed |
44
+ | **Real model catalog** | Read the models the account can actually use (such as `grok-4.7`, `grok-4.6`, `grok-4.5`) from `/v1/models-v2`; nothing is exposed while signed out |
45
+ | **Reasoning effort** | The model picker offers `low` / `medium` / `high` / `xhigh`, defaulting to `high`, matching the Codex interaction |
46
+ | **Composer quota** | A badge beside the model picker shows weekly remaining percentage; hover or click for "Weekly quota · N% left · resets M/D HH:mm" |
47
+ | **Usage panel (experimental)** | Settings shows the used and remaining percentages the backend reports, and never invents numbers when the read fails |
48
+ | **Credentials stay local** | Only the short-lived access token from `~/.grok/auth.json` is read on the host; no token is handed to browser RPC |
49
+ | **Sign-in state refreshes the UI** | Login, pull, and logout emit `llm/adapters-updated`, so the chat model list updates with them |
50
+ | **Failures stay visible** | When subscription routing is unavailable it reports an error instead of silently using another paid route |
51
+
52
+ These capabilities reuse the same local Grok Build sign-in.
53
+
54
+ ## Product screen
55
+
56
+ <p align="center">
57
+ <img src="docs/assets/settings-account.webp" width="820" alt="The Grok Subscription page in DSH Settings: sign-in status, account, login buttons and credential notes">
58
+ </p>
59
+
60
+ The **Settings → Grok Subscription** screen shows sign-in status and a masked account, offers CLI login, device-code login, pull from the Grok CLI, and logout, and explains how credentials are read. Account and time values in the screenshot are demo data.
61
+
62
+ <p align="center">
63
+ <img src="docs/assets/settings-usage.webp" width="820" alt="The experimental usage panel in Grok Subscription settings: 6% used, about 94% remaining">
64
+ </p>
65
+
66
+ The experimental usage panel comes from an undocumented subscription billing endpoint (`/v1/billing?format=credits`). It is informational: the endpoint may change or disappear, a failed read never shows an invented percentage, and chat is unaffected.
67
+
68
+ ## Prepare DSH
69
+
70
+ This plugin supports the latest DeepSeek Harness release recorded in its package metadata and requires a **SuperGrok or X Premium** account that currently has Grok Build access.
71
+
72
+ - **Already able to run `dsh`**: use the standard command below;
73
+ - **Prefer the official path**: see the [DeepSeek Harness documentation](https://github.com/deepseek-ai/deepseek-harness#run).
74
+
75
+ The plugin is strict about `~/.grok/auth.json`: it refuses symbolic links, files readable by group or others, and files not owned by the current user. If permissions are wrong:
76
+
77
+ ```sh
78
+ chmod 600 "${GROK_HOME:-$HOME/.grok}/auth.json"
79
+ ```
80
+
81
+ ## Install
82
+
83
+ ### Standard DSH command
84
+
85
+ ```sh
86
+ dsh plugin --profile web add BaronCyrus/dsh-grok-subscription
87
+ ```
88
+
89
+ You can also install the version published on npm:
90
+
91
+ ```sh
92
+ dsh plugin --profile web add dsh-grok-subscription@1.0.2
93
+ ```
94
+
95
+ DSH handles target selection, the profile lock, dependency resolution, and bundle activation.
96
+
97
+ ### Headless
98
+
99
+ Sign in and select a Grok model once in the Web UI, then install the same plugin into the standard DSH Headless profile:
100
+
101
+ ```sh
102
+ dsh plugin --profile headless add BaronCyrus/dsh-grok-subscription
103
+ dsh --profile headless "reply with exactly: ok"
104
+ ```
105
+
106
+ <details>
107
+ <summary>Official npm route (Node.js installed)</summary>
108
+
109
+ The official `npx @deepseek-ai/dsh web` does not create a global `dsh` command, so keep the full `npx` prefix when installing the plugin:
110
+
111
+ ```sh
112
+ npx -y @deepseek-ai/dsh@0.1.5-rc.2 plugin --profile web add BaronCyrus/dsh-grok-subscription
113
+ npx -y @deepseek-ai/dsh@0.1.5-rc.2 plugin --profile web list dsh-grok-subscription --depth 0
114
+ npx -y @deepseek-ai/dsh@0.1.5-rc.2 --profile web --dump-config
115
+ ```
116
+
117
+ </details>
118
+
119
+ <details>
120
+ <summary>Verify the install when <code>dsh</code> already works</summary>
121
+
122
+ ```sh
123
+ dsh plugin --profile web list dsh-grok-subscription --depth 0
124
+ dsh --profile web --dump-config
125
+ ```
126
+
127
+ The package list should contain exactly one `dsh-grok-subscription`, and the config exactly one `grok-build` route.
128
+
129
+ </details>
130
+
131
+ Restart DSH manually afterwards, then:
132
+
133
+ 1. Open **Settings → Grok Subscription**;
134
+ 2. Sign in with an account that has Grok Build access (browser login or device-code login);
135
+ 3. Select a Grok model in the model picker.
136
+
137
+ ## Features
138
+
139
+ - Reuses the official Grok Build CLI session; credentials stay local and the account is identified by a partially masked email;
140
+ - Models appear directly in DSH sessions with no `XAI_API_KEY`, and no token is exposed to the browser;
141
+ - The model catalog is pulled after sign-in; a failed or timed-out read falls back to a built-in list and records the error, and it stays empty while signed out;
142
+ - The model menu offers `low` / `medium` / `high` / `xhigh` reasoning effort, defaulting to `high`;
143
+ - A weekly remaining quota badge sits beside the model picker in the composer, with remaining percentage and reset time on hover or click;
144
+ - Settings can show the used and remaining percentages the backend reports, and says so plainly instead of guessing when the read fails;
145
+ - Supports CLI login, device-code login, pull from the Grok CLI, and logout; the chat model list refreshes whenever sign-in state changes;
146
+ - When subscription routing is unavailable it reports an error instead of silently switching to another paid route.
147
+
148
+ ### Composer quota
149
+
150
+ <p align="center">
151
+ <img src="docs/assets/composer-quota.webp" width="820" alt="DSH composer: weekly remaining quota badge beside the Grok 4.7 model picker">
152
+ </p>
153
+
154
+ The badge appears only when the current session's provider is `grok-build` and the usage read succeeds; hover or click for "Weekly quota · N% left · resets M/D HH:mm". It reflects only the weekly quota the backend returns, and when the read fails the badge is simply hidden — chat is unaffected.
155
+
156
+ ### Reasoning effort
157
+
158
+ After selecting a Grok model, the model menu exposes a reasoning effort submenu: `low` / `medium` / `high` / `xhigh`, defaulting to `high`. The levels come from `model.reasoning` metadata (`efforts` + `defaultEffort`), so the interaction matches Codex in DSH; the levels actually available depend on the account's model catalog.
159
+
160
+ ### Model catalog and sign-in state
161
+
162
+ After sign-in the plugin reads the model catalog the account can actually use from the subscription proxy; while signed out it registers no models at all. If the read fails or times out it falls back to a built-in list (`grok-4.7` / `grok-4.6` / `grok-4.5`) and records the error in status, so the catalog is never left empty and plugin startup is never blocked.
163
+
164
+ ## Update and uninstall
165
+
166
+ ### Update and verify
167
+
168
+ ```sh
169
+ dsh plugin --profile web update dsh-grok-subscription
170
+ dsh plugin --profile web list dsh-grok-subscription --depth 0
171
+ dsh --profile web --dump-config
172
+ ```
173
+
174
+ ### Uninstall
175
+
176
+ Confirm you want the plugin removed, then run:
177
+
178
+ ```sh
179
+ dsh plugin --profile web remove dsh-grok-subscription
180
+ ```
181
+
182
+ These operations keep the DSH profile, other plugins, and the sign-in stored in `~/.grok/auth.json`.
183
+
184
+ <details>
185
+ <summary>Official npm fallback</summary>
186
+
187
+ ```sh
188
+ npx -y @deepseek-ai/dsh@0.1.5-rc.2 plugin --profile web update dsh-grok-subscription
189
+ npx -y @deepseek-ai/dsh@0.1.5-rc.2 plugin --profile web remove dsh-grok-subscription
190
+ ```
191
+
192
+ </details>
193
+
194
+ ## Troubleshooting
195
+
196
+ - **`dsh` is not recognized**: the official npm route never creates a global `dsh` command — use the full `npx -y @deepseek-ai/dsh@0.1.5-rc.2 ...` command above;
197
+ - **The model list is empty**: nothing is exposed while signed out. Finish signing in, then click **Pull from Grok CLI**;
198
+ - **Nothing changed after upgrading**: the plugin keeps running the old `lib/` — reinstall the plugin, restart `dsh web`, and hard-refresh (Ctrl+Shift+R);
199
+ - **It reports `auth.json` permissions**: apply the `chmod 600` above; the plugin refuses symlinks and group- or other-readable files;
200
+ - **"No reply" after sending a message**: upgrade to `1.0.1` or later. `1.0.0` had a defect where tool-calling turns aborted with a non-serializable stream chunk, leaving no reply in the UI at all.
201
+
202
+ ## Scope and support
203
+
204
+ The Grok subscription backend and DSH can change independently; this is a community project with no affiliation or endorsement from DeepSeek or xAI.
205
+
206
+ For sensitive issues read [SECURITY.md](SECURITY.md) first. For bug reports use [Issues](https://github.com/BaronCyrus/dsh-grok-subscription/issues).
207
+
208
+ ### Development checks
209
+
210
+ ```sh
211
+ npm install
212
+ npm test
213
+ npm run build
214
+ ```
215
+
216
+ `lib/` is a committed build artifact, so run `npm run build` after changing `src/`.
217
+
218
+ [MIT](LICENSE)
package/README.md CHANGED
@@ -1,186 +1,218 @@
1
- # dsh-grok-subscription
1
+ # DSH Grok Subscription — 在 DeepSeek Harness 使用 SuperGrok / X Premium 订阅
2
2
 
3
- 一个第一方形态的 **DeepSeek Harness 社区插件**:复用官方 Grok Build CLI 的 SuperGrok / X Premium 登录,在 DSH 中提供独立的 `grok-build` 模型路由,不覆盖 DSH 内置的 `xai` API-key 路由。
3
+ <div align="center">
4
4
 
5
- ## 它做什么
5
+ **简体中文** · [English](https://github.com/BaronCyrus/dsh-grok-subscription/blob/main/README.en.md)
6
6
 
7
- - 读取 `${GROK_HOME:-~/.grok}/auth.json` 中的 OAuth 会话(不接受纯 API-key 项作为订阅登录)。
8
- - 通过 DSH credentials 仅保存短期 access token;refresh token 不离开 Grok CLI 文件。
9
- - 推理使用 OpenAI Responses:`https://cli-chat-proxy.grok.com/v1/responses`,不是 `api.x.ai`。
10
- - 登录后从 `GET https://cli-chat-proxy.grok.com/v1/models-v2` 获取账号可用模型;实时目录不可用或为空时使用小型静态目录(包含 `grok-4.7`)。`grok-4.7` 优先来自实时 `models-v2` 返回。
11
- - 未登录时仍注册 adapter,但模型列表为 **0**(fail closed)。
12
- - 设置页显示脱敏账户、登录状态、模型与最近一次目录错误,并提供 CLI 登录、设备码登录、从 Grok CLI 拉取、退出登录。
13
- - **实验性用量显示**:登录后从未文档化的 `GET /v1/billing?format=credits` 读取 `config.creditUsagePercent`(兼容顶层)与周期结束时间;失败时显示「不可用」且**绝不编造百分比**。不影响 Chat。
14
- - Pull / 登录 / 退出 / 目录刷新后通过 `llm/adapters-updated` 通知 Web 客户端刷新 Chat / New Session 模型选择器(emit 延后,避免与 Pull RPC 重入)。
15
- - 设置页 Pull **零出站网络、内存优先**:只读本地 auth.json,同步写入 `memoryAccessToken` 后立即返回;credentials.set / credentialRef 延后(带超时),不堵 Pull RPC;目录与用量由后台及客户端 fire-and-forget 的 `catalog/refresh` / `usage/refresh` 更新(不占用全局 busy);`status` / `currentToken` 优先内存。
7
+ **把 SuperGrok / X Premium(Grok Build)订阅直接接入 DeepSeek Harness**
16
8
 
17
- ## 安装
9
+ 复用官方 Grok Build CLI 的登录会话,不需要 `XAI_API_KEY`。
10
+ 模型、推理档位和每周额度都留在 DSH 里。
18
11
 
19
- 要求 Node.js `^22.19.0 || >=24`,并建议先安装官方 `grok` CLI。
12
+ [![CI](https://github.com/BaronCyrus/dsh-grok-subscription/actions/workflows/ci.yml/badge.svg)](https://github.com/BaronCyrus/dsh-grok-subscription/actions/workflows/ci.yml)
13
+ [![npm](https://img.shields.io/npm/v/dsh-grok-subscription?logo=npm&label=npm)](https://www.npmjs.com/package/dsh-grok-subscription)
14
+ [![npm 总下载量](https://img.shields.io/npm/dt/dsh-grok-subscription?logo=npm&label=%E6%80%BB%E4%B8%8B%E8%BD%BD%E9%87%8F)](https://www.npmjs.com/package/dsh-grok-subscription)
15
+ [![MIT](https://img.shields.io/badge/license-MIT-111111.svg)](LICENSE)
16
+ [![Star](https://img.shields.io/github/stars/BaronCyrus/dsh-grok-subscription?style=flat&logo=github&label=Star)](https://github.com/BaronCyrus/dsh-grok-subscription/stargazers)
20
17
 
21
- ```bash
22
- grok login
23
- # 从 GitHub 安装(推荐)
24
- dsh plugin --profile web add BaronCyrus/dsh-grok-subscription
25
- # 或本地路径
26
- # dsh plugin --profile web add /absolute/path/to/dsh-grok-subscription
27
- # 或 npm(发布后)
28
- # dsh plugin --profile web add dsh-grok-subscription@1.0.0
29
- ```
18
+ [三步开始](#三步开始) · [安装](#安装) · [参与贡献](CONTRIBUTING.md) · [更新与卸载](#更新与卸载)
30
19
 
31
- 然后重启 `dsh web`,打开 **Settings → Grok 订阅**。也可以在设置页点击“CLI 登录”或“设备码登录”;设备码流程会在启动 DSH 的终端中显示提示。登录完成后点击“从 Grok CLI 拉取”可立即同步;Chat 模型列表会随之刷新。
20
+ </div>
32
21
 
33
- 升级到新版本后请重新安装插件(`add` 同一路径)并重启 `dsh web`,否则会继续跑旧的 `lib/`。
22
+ <p align="center">
23
+ <img src="docs/assets/grok-subscription-overview.webp" width="900" alt="在 DeepSeek Harness 中选择 Grok 4.7 并使用 SuperGrok 订阅进行多轮对话">
24
+ </p>
34
25
 
35
- 若权限不正确:
26
+ ## 三步开始
36
27
 
37
- ```bash
38
- chmod 600 "${GROK_HOME:-$HOME/.grok}/auth.json"
39
- ```
28
+ 1. **安装插件**:在终端运行下面的命令;指定候选版本时填写完整的 `包名@版本`,例如 `dsh-grok-subscription@1.0.2`。
40
29
 
41
- 开发检查:
30
+ ```sh
31
+ dsh plugin --profile web add BaronCyrus/dsh-grok-subscription
32
+ ```
42
33
 
43
- ```bash
44
- npm install
45
- npm test
46
- npm run build
47
- ```
34
+ 2. **登录订阅**:打开 **设置 → Grok 订阅**,点击 **CLI 登录** 或 **设备码登录**,在启动 DSH 的终端里完成官方提示。已经自己跑过 `grok login` 的话,直接点 **从 Grok CLI 拉取** 即可,不需要粘贴任何 token。
35
+ 3. **开始使用**:在模型选择器中选择 Grok 4.7 等模型。输入框旁的圆形徽章显示每周剩余额度,模型菜单里有推理档位。
48
36
 
49
- ## v1.0.0
37
+ 安装或升级后需要重启 `dsh web`,否则会继续运行旧的 `lib/`。
50
38
 
51
- 首个稳定版,已发布 npm:`dsh-grok-subscription@1.0.0`。内置模型选择器现可通过 `model.reasoning`(efforts + defaultEffort)展示 Grok Build 的 reasoning effort 子菜单(low/medium/high/xhigh),与 Codex 一致。修复 duck `listModels`/`resolveModel` 此前省略该元数据的问题。
39
+ ## 核心优势
52
40
 
53
- ## v0.1.13
41
+ | 能力 | 用户得到什么 |
42
+ | --- | --- |
43
+ | **订阅直连** | 复用官方 Grok Build CLI 会话,不需要 `XAI_API_KEY` |
44
+ | **真实模型目录** | 登录后从 `/v1/models-v2` 读取账号实际可用的模型(如 `grok-4.7`、`grok-4.6`、`grok-4.5`);未登录时不暴露任何模型 |
45
+ | **推理档位** | 模型选择器内置 `low` / `medium` / `high` / `xhigh` 子菜单,默认 `high`,与 Codex 的交互一致 |
46
+ | **输入框额度** | 模型选择器旁的徽章直接显示每周剩余比例,悬停或点击查看「每周额度 剩余 N% · 重置于 M/D HH:mm」 |
47
+ | **用量面板(实验性)** | 设置页显示服务端返回的已用与剩余百分比,读取失败时不猜数字、不虚构额度 |
48
+ | **凭据留在本机** | 只在主机侧读取 `~/.grok/auth.json` 的短期 access token;不会把 token 交给浏览器 RPC |
49
+ | **登录状态会刷新界面** | 登录、拉取或登出后自动派发 `llm/adapters-updated`,Chat 模型列表随之更新 |
50
+ | **失败可见** | 订阅路由不可用时明确报错,不会静默改用其他付费路由 |
54
51
 
55
- Chat 输入区模型选择旁增加 Codex 风格的每周剩余额度徽章(如 `16%`);悬停/点击显示「每周额度 剩余 N% · 重置于 M/D HH:mm」。仅在当前会话 provider 为 `grok-build` 且用量 `ok` 时显示。Settings 中 Pull/登录成功后会派发刷新事件更新徽章。本地 0.1.13 供复测,**未发 npm**。升级后请用新 token URL 硬刷新。
52
+ 这些能力共用同一份本机 Grok Build 登录。
56
53
 
57
- ## v0.1.12
54
+ ## 实际界面
58
55
 
59
- 升级后请用 `dsh web` 新打印的带 token URL 打开,并硬刷新(Ctrl+Shift+R),避免旧 `/plugins` client 缓存。
56
+ <p align="center">
57
+ <img src="docs/assets/settings-account.webp" width="820" alt="DSH 设置中的 Grok 订阅页面:登录状态、账号、登录按钮与凭据说明">
58
+ </p>
60
59
 
61
- 修复 0.1.11 实机:Settings Plugins 仍卡在「Reading plugins…」,Pull 等到客户端 45s 超时。根因是 `status` / `currentToken` 仍可能无超时地 `await credentials.resolve` 与裸 `import('@deepseek-ai/dsh-credentials')`,楔住连接桥后 Plugins 清单也跟着挂。现:`credentialRefOf` 用 timed `optionalImport`;resolve 硬超时(默认 1.5s);`status` 先内存 / auth.json,再可选 credentials;宿主 RPC 每端点 8s 硬超时;`llm/adapters-updated` 一律延后;`inject` 保持 `['llm','web']`;客户端缺 connection 软跳过,RPC 客户端超时降至 12s。本地 0.1.12 供复测,未发 npm。
60
+ 上图为 **设置Grok 订阅** 主界面:显示登录状态与脱敏账号,提供 CLI 登录、设备码登录、从 Grok CLI 拉取和登出,并说明凭据的读取方式。截图中的账号与时间均为演示数据。
62
61
 
63
- ## v0.1.10
62
+ <p align="center">
63
+ <img src="docs/assets/settings-usage.webp" width="820" alt="Grok 订阅设置页中的实验性用量面板:已用 6%、剩余约 94%">
64
+ </p>
64
65
 
65
- 修复 0.1.9 实机:Pull 仍可能 45s 超时,且 **Settings → Plugins** 卡在 `Reading plugins…`(宿主 Settings/RPC 通道被楔住)。根因是 `apply`/`boot` 路径会 `await` 无超时的动态 `import`(`pi-ai` / `dsh-llm-pi-ai` / schemastery),且 `inject` 含 sticky 的 `credentials`。现 `inject` 收窄为 `['llm','web']`;`apply` 同步注册 duck adapter 后立即返回;schemastery / `session.pull` / pi-ai 升级全部 `setImmediate` 延后;所有动态 import 硬超时(默认 2.5s),超时则保持 duck。保留 0.1.9 内存优先 Pull 与 0.1.8 客户端 fire-and-forget。
66
+ 实验性用量面板来自未公开的订阅计费接口(`/v1/billing?format=credits`)。它仅供参考:接口可能随时变化或消失,读取失败时不会显示编造的百分比,也不影响聊天。
66
67
 
67
- ## v0.1.9
68
+ ## 准备 DSH
68
69
 
69
- 修复 0.1.8 实机:Pull RPC 本身卡到客户端 45s 超时。根因是零网络 Pull `await storeToken` → `credentialRefOf()`(动态 import `@deepseek-ai/dsh-credentials`)无超时,import/set 与 DSH credentials 死锁时会拖死整个 Pull。现会话服务持有 `memoryAccessToken`;Pull 成功路径同步写入内存后立即返回,credentials 持久化与 clear 全部 `scheduleDeferred`;`currentToken` / `status` 优先内存。客户端仍保持 0.1.8 的 fire-and-forget 刷新行为。
70
+ 本插件支持软件包元数据中记录的最新版 DeepSeek Harness,并需要一个具有 Grok Build 使用资格的 **SuperGrok X Premium** 账号。
70
71
 
71
- ## v0.1.8
72
+ - **已经能运行 `dsh`**:直接使用下面的标准命令;
73
+ - **想按官方方式运行**:查看 [DeepSeek Harness 官方说明](https://github.com/deepseek-ai/deepseek-harness#run)。
72
74
 
73
- 修复 0.1.7 实机:Pull 成功后客户端仍 await `usage/refresh` 并占用全局 busy,导致 Working… 卡住、按钮一直 disabled。现 Pull/登录成功后立即清 busy 并显示成功提示;`catalog/refresh` 与 `usage/refresh` 为 fire-and-forget(不置全局 busy)。仅「刷新用量」按钮使用本地 `usageBusy`。`fetchBillingUsage` 增加独立于 AbortSignal 的硬 `Promise.race` 超时(默认 8s);空 JSON `{}` 与非 JSON Content-Type 会写入更明确的不可用原因。
75
+ 插件读取 `~/.grok/auth.json` 时较为严格:拒绝符号链接、拒绝组或其他用户可读的文件、拒绝非当前用户拥有的文件。如果权限不正确:
74
76
 
75
- ## v0.1.7
77
+ ```sh
78
+ chmod 600 "${GROK_HOME:-$HOME/.grok}/auth.json"
79
+ ```
76
80
 
77
- 修复设置页 Pull 仍卡住 Working…(0.1.6 去掉 billing 后仍可能卡在 `credentials.set` 或 `loadCatalog`):Pull 关键路径**零出站网络**,`storeToken` 硬超时(默认 5s),立即返回缓存目录/用量;后台 kick `refreshCatalog` + `refreshUsage`;客户端在 Pull 成功后另行 `catalog/refresh` 与 `usage/refresh`(独立 busy 文案)。`notifyCatalogChange` 仍仅延后触发。
81
+ ## 安装
82
+
83
+ ### DSH 标准命令
78
84
 
79
- ## v0.1.6
85
+ ```sh
86
+ dsh plugin --profile web add BaronCyrus/dsh-grok-subscription
87
+ ```
80
88
 
81
- 修复设置页 Pull 卡住 Working…、用量长期「不可用」:Pull 关键路径不再等待 billing;`status()` 只返回缓存用量;Pull 成功后由客户端另行 `usage/refresh`;`llm/adapters-updated` 延后发出;客户端 RPC 增加超时保护。解析逻辑仍沿用 0.1.5 的 config.* 兼容。
89
+ 也可以按 npm 上的已发布版本安装:
82
90
 
83
- ## v0.1.5
91
+ ```sh
92
+ dsh plugin --profile web add dsh-grok-subscription@1.0.2
93
+ ```
84
94
 
85
- 修复实验性用量解析:官方 `/v1/billing?format=credits` 实际把 `creditUsagePercent`、`currentPeriod`、`productUsage` 放在 `config` 下(非顶层)。0.1.4 因严格读顶层字段在 QA 中显示「不可用」。现同时接受 `config.*` 与顶层;不可用原因会附带顶层 key 名以便排查。**绝不编造百分比。**
95
+ 目标选择、profile 锁、依赖解析和 bundle 激活均由 DSH 负责。
86
96
 
87
- ## v0.1.4
97
+ ### Headless
88
98
 
89
- 实验性 SuperGrok 周用量显示:设置页新增「用量(实验性)」区块,只读请求 `GET https://cli-chat-proxy.grok.com/v1/billing?format=credits`(与现有 OAuth + CLI fingerprint 请求头相同)。解析 `config.creditUsagePercent` / 顶层 `creditUsagePercent` 与 `config.currentPeriod.end`;RPC 提供 `usage` / `usage/refresh`,永不回传 token。失败 fail-closed 为「不可用」。**账单 API 未公开文档,可能随时变更。**
99
+ 先在 Web 中完成登录并选择一次 Grok 模型,再把同一个插件安装到 DSH 的标准 Headless profile:
90
100
 
91
- ## v0.1.3
101
+ ```sh
102
+ dsh plugin --profile headless add BaronCyrus/dsh-grok-subscription
103
+ dsh --profile headless "只回复:ok"
104
+ ```
92
105
 
93
- 修复多轮 Chat 中助手正文不显示的问题:自定义 Responses 流在 `block-end` 时把已累计的 text/reasoning 写成空字符串,Harness `BlockAssembler` 会用空块覆盖流式增量,导致第 2 轮起界面只见思考(如 “Deep diving…”)不见正文。同时为 `grok-build` 强制 `include: reasoning.encrypted_content`(与 pi-ai 对 `xai` 的处理对齐),保证推理密文可回放。
106
+ <details>
107
+ <summary>官方 npm 方式(已安装 Node.js)</summary>
94
108
 
95
- ## v0.1.2
109
+ 官方的 `npx @deepseek-ai/dsh web` 不会创建全局 `dsh` 命令,因此安装插件时也要保留完整的 `npx` 前缀:
96
110
 
97
- 设置页 Pull / 登录 / 退出成功后显示明确的内联成功反馈。
111
+ ```sh
112
+ npx -y @deepseek-ai/dsh@0.1.5-rc.2 plugin --profile web add BaronCyrus/dsh-grok-subscription
113
+ npx -y @deepseek-ai/dsh@0.1.5-rc.2 plugin --profile web list dsh-grok-subscription --depth 0
114
+ npx -y @deepseek-ai/dsh@0.1.5-rc.2 --profile web --dump-config
115
+ ```
98
116
 
99
- ## v0.1.1
117
+ </details>
100
118
 
101
- 修复 Chat / New Session 模型选择器在 Settings 已 Pull 到 `models-v2` 后仍只显示 DeepSeek 模型的问题:
119
+ <details>
120
+ <summary>已经能运行 <code>dsh</code> 时检查安装结果</summary>
102
121
 
103
- - PiAiAdapter 的 `profiles` 每次重建 provider,且 `getModels()` 读取当前 `session.models()`(不再依赖一次性的 `models: []` + `fetchModels`)。
104
- - Pull / 登录 / 退出 / 目录刷新后发出 `llm/adapters-updated`,让选择器刷新。
122
+ ```sh
123
+ dsh plugin --profile web list dsh-grok-subscription --depth 0
124
+ dsh --profile web --dump-config
125
+ ```
105
126
 
106
- v0.1.0 范围仍适用:仅文本/工具调用所需的 Responses 流、账号同步、动态模型目录和设置页。没有图片生成、画板、额度预测或 Codex 功能。官方 CLI 负责登录与 refresh-token 生命周期;插件不会实现或写回供应商 OAuth refresh。
127
+ 安装列表中应只有一个 `dsh-grok-subscription`,配置中应只有一个 `grok-build` 路由。
107
128
 
108
- ## 注意事项
129
+ </details>
109
130
 
110
- - 这是社区插件,不是 xAI、Grok 或 DeepSeek 官方产品。
111
- - 订阅账号用于非官方客户端可能处于供应商条款灰色地带;**只使用你自己的账号**,风险自担。
112
- - 官方文档没有发布完整 HTTP wire protocol;代理协议、请求头、模型 ID 或权限规则可能随时改变。
113
- - 用量面板依赖的 `/v1/billing?format=credits` **未公开文档**,属实验性功能;字段形状或可用性可能变化,失败时仅显示不可用。
114
- - 设置页发起 CLI 登录时,浏览器/设备码提示由官方 CLI 处理;远程部署请优先用 `grok login --device-auth`。
131
+ 安装完成后手动重启 DSH,然后:
115
132
 
116
- ## English
133
+ 1. 打开 **设置 → Grok 订阅**;
134
+ 2. 登录具有 Grok Build 资格的账号(浏览器登录或设备码登录);
135
+ 3. 在模型选择器中选择 Grok 模型。
117
136
 
118
- `dsh-grok-subscription` is a small community DSH plugin that reuses an official Grok Build CLI SuperGrok/X Premium session. It adds a separate `grok-build` route, calls the subscription Responses proxy, discovers models from `/v1/models-v2` (with a signed-in-only fallback including `grok-4.7`), and never exposes tokens to browser RPC. Settings also shows an **experimental** weekly usage panel from the undocumented `/v1/billing?format=credits` endpoint (fail-closed; never invents percentages; chat is unaffected). After Pull/login/logout the plugin emits `llm/adapters-updated` so the Chat model picker refreshes. Install locally with:
137
+ ## 功能
119
138
 
120
- ```bash
121
- dsh plugin --profile web add BaronCyrus/dsh-grok-subscription
122
- ```
139
+ - 复用官方 Grok Build CLI 会话登录,凭据保留在本机;账号以部分隐藏的邮箱区分;
140
+ - 模型直接出现在 DSH 会话中,无需 `XAI_API_KEY`,也不向浏览器暴露 token;
141
+ - 模型目录登录后自动拉取;读取失败或超时时回退到内置列表并记录错误,未登录时保持为空;
142
+ - 模型菜单提供 `low` / `medium` / `high` / `xhigh` 推理档位,默认 `high`;
143
+ - 输入框模型选择器旁显示每周剩余额度徽章,悬停或点击查看剩余比例与重置时间;
144
+ - 设置页可查看服务端返回的用量与剩余百分比,失败时明确提示而不是显示猜测值;
145
+ - 支持 CLI 登录、设备码登录、从 Grok CLI 拉取和登出;登录状态变化后 Chat 模型列表自动刷新;
146
+ - 订阅路由不可用时明确报错,不会静默切换到其他付费路由。
123
147
 
124
- Reinstall after upgrades, restart `dsh web`, then open **Settings → Grok Subscription**.
148
+ ### 输入框额度
125
149
 
126
- ### v1.0.0
150
+ <p align="center">
151
+ <img src="docs/assets/composer-quota.webp" width="820" alt="DSH 输入框:Grok 4.7 模型选择器旁的每周剩余额度徽章">
152
+ </p>
127
153
 
128
- First stable release, published to npm as `dsh-grok-subscription@1.0.0`. Stock model picker now shows Grok Build reasoning effort (low/medium/high/xhigh) via `model.reasoning` metadata (`efforts` + `defaultEffort`), matching Codex. Fixes duck `listModels`/`resolveModel` omitting that shape.
154
+ 仅当当前会话的 provider `grok-build` 且用量读取成功时显示徽章;悬停或点击可查看「每周额度 剩余 N% · 重置于 M/D HH:mm」。徽章只反映服务端返回的每周额度;读取失败时徽章不显示,聊天不受影响。
129
155
 
130
- ### v0.1.13
156
+ ### 推理档位
131
157
 
132
- Chat 输入区模型选择旁增加 Codex 风格的每周剩余额度徽章(如 `16%`);悬停/点击显示「每周额度 剩余 N% · 重置于 M/D HH:mm」。仅在当前会话 provider 为 `grok-build` 且用量 `ok` 时显示。Settings Pull/登录成功后会派发刷新事件更新徽章。本地 0.1.13 供复测,**未发 npm**。升级后请用新 token URL 硬刷新。
158
+ 选择 Grok 模型后,模型菜单里会出现推理档位子菜单:`low` / `medium` / `high` / `xhigh`,默认 `high`。档位通过 `model.reasoning`(`efforts` + `defaultEffort`)元数据提供,因此与 Codex DSH 中的交互一致;具体可用档位以账号模型目录为准。
133
159
 
134
- ## v0.1.12
160
+ ### 模型目录与登录状态
135
161
 
136
- After upgrading, open the fresh token URL from `dsh web` and hard-refresh (Ctrl+Shift+R) so the old immutable `/plugins` client is not reused.
162
+ 登录后插件从订阅代理读取账号实际可用的模型目录;未登录时不注册任何模型。读取失败或超时时回退到内置列表(`grok-4.7` / `grok-4.6` / `grok-4.5`)并在状态里记录错误,因此目录不会留空,也不会让插件启动卡住。
137
163
 
138
- 修复 0.1.11 实机:Settings → Plugins 仍卡在「Reading plugins…」,Pull 等到客户端 45s 超时。根因是 `status` / `currentToken` 仍可能无超时地 `await credentials.resolve` 与裸 `import('@deepseek-ai/dsh-credentials')`,楔住连接桥后 Plugins 清单也跟着挂。现:`credentialRefOf` 用 timed `optionalImport`;resolve 硬超时(默认 1.5s);`status` 先内存 / auth.json,再可选 credentials;宿主 RPC 每端点 8s 硬超时;`llm/adapters-updated` 一律延后;`inject` 保持 `['llm','web']`;客户端缺 connection 软跳过,RPC 客户端超时降至 12s。本地 0.1.12 供复测,未发 npm。
164
+ ## 更新与卸载
139
165
 
140
- ## v0.1.10
166
+ ### 更新并检查
141
167
 
142
- Fixes 0.1.9 live hang: Pull could still hit the 45s client timeout, and **Settings → Plugins** stuck on `Reading plugins…` (host Settings/RPC channel wedged). Root cause: `apply`/`boot` awaited untimed dynamic `import`s (`pi-ai` / `dsh-llm-pi-ai` / schemastery), and `inject` listed sticky `credentials`. Now `inject` is `['llm','web']`; `apply` synchronously registers a duck adapter and returns; schemastery / `session.pull` / pi-ai upgrade are deferred via `setImmediate`; every dynamic import has a hard timeout (default 2.5s) and falls back to duck. Keeps 0.1.9 memory-first Pull and 0.1.8 client fire-and-forget.
168
+ ```sh
169
+ dsh plugin --profile web update dsh-grok-subscription
170
+ dsh plugin --profile web list dsh-grok-subscription --depth 0
171
+ dsh --profile web --dump-config
172
+ ```
143
173
 
144
- ### v0.1.9
174
+ ### 卸载
145
175
 
146
- Fixes 0.1.8 live hang where the Pull RPC itself hit the client 45s timeout: zero-network Pull still awaited `storeToken` → `credentialRefOf()` (dynamic import of `@deepseek-ai/dsh-credentials`) with no timeout, so a credentials import/set deadlock stalled Pull. Session now keeps `memoryAccessToken`; success Pull writes memory and returns immediately, deferring credential persist/clear; `currentToken` / `status` prefer memory. Keeps 0.1.8 client fire-and-forget refresh behavior.
176
+ 确认需要移除插件后再运行:
147
177
 
148
- ### v0.1.8
178
+ ```sh
179
+ dsh plugin --profile web remove dsh-grok-subscription
180
+ ```
149
181
 
150
- Fixes 0.1.7 live hang: after Pull the client no longer awaits `usage/refresh` while holding global busy (Working… / all buttons disabled). Pull/login clear busy and show success immediately; catalog + usage refresh are fire-and-forget. Only the Usage Refresh button uses local `usageBusy`. `fetchBillingUsage` has a hard Promise.race timeout (default 8s) independent of AbortSignal; empty `{}` and non-JSON Content-Type get clearer unavailable reasons.
182
+ 这些操作会保留 DSH profile、其他插件和 `~/.grok/auth.json` 中的登录信息。
151
183
 
152
- ### v0.1.7
153
- Fixes Settings Pull hang after 0.1.6: zero-network Pull path, `storeToken` hard timeout (default 5s), returns cached catalog/usage immediately; background kick of `refreshCatalog` + `refreshUsage`; client follows up with `catalog/refresh` and `usage/refresh`. Catalog notify stays deferred.
184
+ <details>
185
+ <summary>官方 npm 备用方式</summary>
154
186
 
155
- ## v0.1.6
156
- Fixes Settings Pull hang (Working…) and stale Unavailable usage: Pull no longer awaits billing; `status()` returns cached usage only; client calls `usage/refresh` after successful Pull; catalog notify is deferred; client RPC has a safety timeout. Keeps 0.1.5 config.* parsing.
187
+ ```sh
188
+ npx -y @deepseek-ai/dsh@0.1.5-rc.2 plugin --profile web update dsh-grok-subscription
189
+ npx -y @deepseek-ai/dsh@0.1.5-rc.2 plugin --profile web remove dsh-grok-subscription
190
+ ```
157
191
 
158
- ### v0.1.5
159
- Fixes experimental usage parsing: live `/v1/billing?format=credits` nests `creditUsagePercent`, `currentPeriod`, and `productUsage` under `config` (not top-level). 0.1.4 failed QA with Unavailable. Parser now accepts `config.*` and top-level; unavailable reasons include top-level key names. Never invents percentages.
192
+ </details>
160
193
 
161
- ### v0.1.4
162
- Experimental SuperGrok weekly usage in Settings: read-only `GET /v1/billing?format=credits` with the same OAuth + CLI fingerprint headers; shows used % and period end; RPC `usage` / `usage/refresh` never return tokens; fail closed to Unavailable. The billing API is undocumented and may change.
194
+ ## 常见问题
163
195
 
164
- ### v0.1.3
165
- Fixes multi-turn Chat where assistant body text vanished from turn 2 onward: the custom Responses SSE mapper ended text/reasoning blocks with empty strings, so Harness `BlockAssembler` replaced streamed deltas with blanks (UI could show "Deep diving…" then no body). Also forces `include: reasoning.encrypted_content` for `grok-build` so encrypted reasoning can replay on later turns. This is unofficial, may fall into a vendor-ToS gray area, and the undocumented wire protocol can change. Use your own account only.
196
+ - **`dsh` 无法识别**:官方 npm 方式本来就不会创建全局 `dsh` 命令,请使用上面的完整 `npx -y @deepseek-ai/dsh@0.1.5-rc.2 ...` 命令;
197
+ - **模型列表是空的**:未登录时插件不暴露任何模型。先完成登录,再点 **从 Grok CLI 拉取**;
198
+ - **升级后界面没变化**:插件会继续运行旧的 `lib/`,请重新安装插件并重启 `dsh web`,然后硬刷新(Ctrl+Shift+R);
199
+ - **提示 `auth.json` 权限不正确**:按上面的 `chmod 600` 处理;插件拒绝读取符号链接或组/其他用户可读的文件;
200
+ - **出现「没有回复」**:请升级到 `1.0.1` 或更高版本。`1.0.0` 存在一个缺陷:工具调用轮次会因流片段无法无损序列化而整轮中止,界面上完全没有回复。
166
201
 
167
- ## 还需要实机验证的部分
202
+ ## 边界与支持
168
203
 
169
- 单元测试覆盖 auth.json 解析、目录解析、请求头、RPC 脱敏、signed-out 空目录与 catalog-change 回调,不访问网络。以下需要本机已安装的 DSH + 已登录 SuperGrok 才能确认:
204
+ Grok 订阅后端和 DSH 可能独立变化;本项目为社区项目,与 DeepSeek、xAI 无隶属或背书关系。
170
205
 
171
- - Settings 页槽位、locale、connection.rpc 与 DSH web client 的实际装配
172
- - Pull 后 Chat / New Session 是否出现 `grok-4.7` 等 `grok-build` 模型并可发起推理
173
- - `PiAiAdapter` 对 `grok-build` 路由的流式映射(若宿主没有 `@deepseek-ai/dsh-llm-pi-ai` / `@earendil-works/pi-ai`,插件会降级到自带的 Responses SSE MVP adapter)
174
- - 官方 CLI `grok login` / `grok login --device-auth` 从 Settings 按钮拉起后的终端交互
175
- - 实时 `GET /v1/models-v2` 是否返回 `grok-4.7`(目录为空时使用静态回退)
176
- - Settings「用量(实验性)」:登录后刷新是否显示已用 % 与周期结束时间;401/网络失败是否显示「不可用」且无假百分比;Chat 在用量失败时仍可正常对话
206
+ 敏感问题请先阅读 [SECURITY.md](SECURITY.md)。问题反馈请使用 [Issues](https://github.com/BaronCyrus/dsh-grok-subscription/issues)。
177
207
 
178
- ## 致谢 / Acknowledgments
208
+ ### 开发检查
179
209
 
180
- - [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)
181
- - [dsh-kimi-subscription](https://github.com/BaronCyrus/dsh-kimi-subscription)(插件形态与发布方式参考)
182
- - [dsh-codex-subscription](https://github.com/WSL043/dsh-codex-subscription)
210
+ ```sh
211
+ npm install
212
+ npm test
213
+ npm run build
214
+ ```
183
215
 
184
- ## License
216
+ `lib/` 是提交进仓库的构建产物,改动 `src/` 后请运行 `npm run build`。
185
217
 
186
218
  [MIT](LICENSE)
Binary file
Binary file
package/lib/client.js CHANGED
@@ -34,7 +34,7 @@ var import_react2 = require("react");
34
34
  var zh = {
35
35
  nav: "Grok \u8BA2\u9605",
36
36
  title: "Grok \u8BA2\u9605",
37
- subtitle: "\u7528 SuperGrok / X Premium\uFF08Grok Build\uFF09\u4F1A\u8BDD\uFF0C\u800C\u4E0D\u662F XAI_API_KEY\u3002\xB7 1.0.0",
37
+ subtitle: "\u7528 SuperGrok / X Premium\uFF08Grok Build\uFF09\u4F1A\u8BDD\uFF0C\u800C\u4E0D\u662F XAI_API_KEY\u3002\xB7 1.0.2",
38
38
  signedIn: "\u5DF2\u767B\u5F55",
39
39
  signedOut: "\u672A\u767B\u5F55",
40
40
  account: "\u8D26\u6237",
@@ -81,7 +81,7 @@ var zh = {
81
81
  var en = {
82
82
  nav: "Grok Subscription",
83
83
  title: "Grok Subscription",
84
- subtitle: "Use a SuperGrok / X Premium (Grok Build) session, not XAI_API_KEY. \xB7 1.0.0",
84
+ subtitle: "Use a SuperGrok / X Premium (Grok Build) session, not XAI_API_KEY. \xB7 1.0.2",
85
85
  signedIn: "Signed in",
86
86
  signedOut: "Signed out",
87
87
  account: "Account",
package/lib/index.js CHANGED
@@ -788,7 +788,31 @@ function mapFinish(reason) {
788
788
  if (reason === "length" || reason === "max_tokens") return { kind: "max-tokens" };
789
789
  return { kind: "stop" };
790
790
  }
791
- async function* streamResponses(options, token) {
791
+ function finiteOr(value, fallback) {
792
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
793
+ }
794
+ function jsonSafeChunk(value) {
795
+ if (value === null) return null;
796
+ const type = typeof value;
797
+ if (type === "string" || type === "boolean") return value;
798
+ if (type === "number") return Number.isFinite(value) && !Object.is(value, -0) ? value : void 0;
799
+ if (type !== "object") return void 0;
800
+ if (Array.isArray(value)) {
801
+ const list = [];
802
+ for (const item of value) {
803
+ const safe = jsonSafeChunk(item);
804
+ if (safe !== void 0) list.push(safe);
805
+ }
806
+ return list;
807
+ }
808
+ const out = {};
809
+ for (const [key, item] of Object.entries(value)) {
810
+ const safe = jsonSafeChunk(item);
811
+ if (safe !== void 0) out[key] = safe;
812
+ }
813
+ return out;
814
+ }
815
+ async function* streamResponsesUnsafe(options, token) {
792
816
  const headers = {
793
817
  Accept: "text/event-stream",
794
818
  "Content-Type": "application/json",
@@ -839,8 +863,24 @@ async function* streamResponses(options, token) {
839
863
  let reasoningContent = "";
840
864
  let nextIndex = 0;
841
865
  const toolBlocks = /* @__PURE__ */ new Map();
866
+ const toolNames = /* @__PURE__ */ new Map();
842
867
  let usage;
843
868
  let finish = { kind: "stop" };
869
+ const toolIdOf = (payload) => {
870
+ const id = payload?.item_id ?? payload?.call_id ?? payload?.id;
871
+ return typeof id === "string" && id ? id : void 0;
872
+ };
873
+ const learnToolName = (id, name2) => {
874
+ if (typeof id !== "string" || !id || typeof name2 !== "string" || !name2) return;
875
+ toolNames.set(id, name2);
876
+ const tool = toolBlocks.get(id);
877
+ if (tool) tool.name = name2;
878
+ };
879
+ const adoptToolArguments = (id, value) => {
880
+ if (typeof id !== "string" || typeof value !== "string" || !value) return;
881
+ const tool = toolBlocks.get(id);
882
+ if (tool && value.length > tool.arguments.length) tool.arguments = value;
883
+ };
844
884
  const flushSse = function* (raw) {
845
885
  const lines = raw.split("\n");
846
886
  let event = "message";
@@ -880,12 +920,33 @@ async function* streamResponses(options, token) {
880
920
  yield { type: "reasoning-delta", index: reasoningIndex, text: String(delta) };
881
921
  return;
882
922
  }
923
+ if (type === "response.output_item.added" || type === "response.output_item.done") {
924
+ const item = payload.item;
925
+ if (item?.type === "function_call") {
926
+ learnToolName(item.id, item.name);
927
+ learnToolName(item.call_id, item.name);
928
+ adoptToolArguments(item.id, item.arguments);
929
+ }
930
+ return;
931
+ }
932
+ if (type === "response.function_call_arguments.done") {
933
+ const id = toolIdOf(payload);
934
+ learnToolName(id, payload.name);
935
+ adoptToolArguments(id, payload.arguments);
936
+ return;
937
+ }
883
938
  if (type === "response.function_call_arguments.delta") {
884
- const id = payload.item_id ?? payload.call_id ?? payload.id;
939
+ const id = toolIdOf(payload);
885
940
  if (!id) return;
886
941
  let tool = toolBlocks.get(id);
887
942
  if (!tool) {
888
- tool = { index: nextIndex++, id, name: payload.name ?? payload.item?.name, arguments: "" };
943
+ tool = {
944
+ index: nextIndex++,
945
+ id,
946
+ // Never leave this undefined: an unserializable chunk aborts the turn.
947
+ name: toolNames.get(id) ?? "tool",
948
+ arguments: ""
949
+ };
889
950
  toolBlocks.set(id, tool);
890
951
  yield { type: "block-start", index: tool.index, blockType: "tool-call" };
891
952
  }
@@ -895,20 +956,38 @@ async function* streamResponses(options, token) {
895
956
  return;
896
957
  }
897
958
  if (type === "response.completed") {
959
+ for (const item of payload.response?.output ?? []) {
960
+ if (item?.type !== "function_call") continue;
961
+ learnToolName(item.id, item.name);
962
+ learnToolName(item.call_id, item.name);
963
+ const id = typeof item.id === "string" && item.id ? item.id : void 0;
964
+ if (id && !toolBlocks.has(id)) {
965
+ const tool = { index: nextIndex++, id, name: toolNames.get(id) ?? "tool", arguments: "" };
966
+ toolBlocks.set(id, tool);
967
+ yield { type: "block-start", index: tool.index, blockType: "tool-call" };
968
+ }
969
+ adoptToolArguments(id, item.arguments);
970
+ }
898
971
  const responseUsage = payload.response?.usage ?? payload.usage;
899
972
  if (responseUsage) {
900
973
  usage = {
901
- inputTokens: responseUsage.input_tokens ?? responseUsage.prompt_tokens ?? 0,
902
- outputTokens: responseUsage.output_tokens ?? responseUsage.completion_tokens ?? 0,
903
- totalTokens: responseUsage.total_tokens
974
+ inputTokens: finiteOr(responseUsage.input_tokens ?? responseUsage.prompt_tokens, 0),
975
+ outputTokens: finiteOr(responseUsage.output_tokens ?? responseUsage.completion_tokens, 0)
904
976
  };
977
+ usage.totalTokens = finiteOr(responseUsage.total_tokens, usage.inputTokens + usage.outputTokens);
905
978
  }
906
979
  finish = mapFinish(payload.response?.status === "incomplete" ? "length" : "stop");
907
980
  if (toolBlocks.size) finish = { kind: "tool-calls" };
908
981
  }
909
982
  if (type === "response.failed" || type === "error") {
910
- const message = payload.error?.message ?? payload.message ?? "Grok Build stream failed";
911
- finish = { kind: "error", failure: { message, code: "PROVIDER", status: payload.error?.status } };
983
+ const raw2 = payload.error?.message ?? payload.message;
984
+ const failure = {
985
+ message: typeof raw2 === "string" && raw2 ? raw2 : "Grok Build stream failed",
986
+ code: "PROVIDER"
987
+ };
988
+ const status = finiteOr(payload.error?.status, void 0);
989
+ if (status !== void 0) failure.status = status;
990
+ finish = { kind: "error", failure };
912
991
  }
913
992
  };
914
993
  for await (const chunk of response.body) {
@@ -937,6 +1016,12 @@ async function* streamResponses(options, token) {
937
1016
  if (usage) yield { type: "usage", usage };
938
1017
  yield { type: "finish", reason: finish };
939
1018
  }
1019
+ async function* streamResponses(options, token) {
1020
+ for await (const chunk of streamResponsesUnsafe(options, token)) {
1021
+ const safe = jsonSafeChunk(chunk);
1022
+ if (safe) yield safe;
1023
+ }
1024
+ }
940
1025
  function visiblePiModels(session) {
941
1026
  if (session.publicAccount()?.signedIn !== true) return [];
942
1027
  return toPiModels(session.models()).map((model) => model.provider === PROVIDER_ID ? model : { ...model, provider: PROVIDER_ID });
@@ -992,17 +1077,13 @@ function createDuckAdapter(session) {
992
1077
  yield* streamResponses(options, token);
993
1078
  } catch (error) {
994
1079
  const aborted = options.signal?.aborted === true;
995
- yield {
996
- type: "finish",
997
- reason: {
998
- kind: aborted ? "aborted" : "error",
999
- failure: {
1000
- message: error instanceof Error ? error.message : "Grok Build request failed",
1001
- code: aborted ? "ABORTED" : "PROVIDER",
1002
- status: error?.status
1003
- }
1004
- }
1080
+ const failure = {
1081
+ message: error instanceof Error ? error.message : "Grok Build request failed",
1082
+ code: aborted ? "ABORTED" : "PROVIDER"
1005
1083
  };
1084
+ const status = finiteOr(error?.status, void 0);
1085
+ if (status !== void 0) failure.status = status;
1086
+ yield { type: "finish", reason: { kind: aborted ? "aborted" : "error", failure } };
1006
1087
  }
1007
1088
  }
1008
1089
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-grok-subscription",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
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",
@@ -12,8 +12,11 @@
12
12
  },
13
13
  "files": [
14
14
  "lib",
15
+ "docs/assets",
15
16
  "cordis.patch.yml",
16
17
  "README.md",
18
+ "README.en.md",
19
+ "CONTRIBUTING.md",
17
20
  "SECURITY.md",
18
21
  "LICENSE"
19
22
  ],