dsh-grok-subscription 2.0.7 → 2.0.8
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 +7 -0
- package/README.en.md +2 -2
- package/README.md +2 -2
- package/docs/guide.en.md +5 -3
- package/docs/guide.zh-CN.md +5 -3
- package/lib/client.js +12 -7
- package/lib/index.js +77 -11
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.0.8 — 2026-10-12
|
|
4
|
+
|
|
5
|
+
- Fix **sign-in reporting only `Grok subscription RPC "login/cli" timed out after 20000ms`** when DSH cannot reach `auth.x.ai`. Starting sign-in makes two requests in a row, OIDC discovery and then the device-code request, and each had its own 20s timeout under a 20s RPC ceiling. On a network that silently drops the connection, undici gave up on each TCP connect after about 10s; discovery then fell back to the same host, and the RPC ceiling won the race at 20s, so the real cause never reached the panel. Fixes #2.
|
|
6
|
+
- Both requests now share one 15s deadline, which ends before the RPC ceiling even with the status read that follows. A request that gets no HTTP answer is reported as `Cannot reach auth.x.ai (…)`, naming the kind of failure (connection timed out, connection refused, DNS lookup failed, TLS certificate rejected, timed out) without echoing an address or proxy URL. Discovery no longer falls back to the same unreachable host; a bad or mismatched discovery document still falls back to the documented endpoints.
|
|
7
|
+
- When sign-in cannot reach `auth.x.ai`, Settings explains the usual cause under the error: DSH does not read the macOS or Windows system proxy setting, so a browser that opens x.ai does not mean DSH can. Set `https_proxy=http://127.0.0.1:<port>` (an HTTP or mixed port; `socks5://` is not supported) in `~/.dsh/.env` — `%USERPROFILE%\.dsh\.env` on Windows — and restart DSH completely, or use the proxy app's TUN mode. Both guides have a matching troubleshooting row. The unused "no sign-in link" warning path left over from the CLI-based sign-in is removed.
|
|
8
|
+
- No model-routing, image-input, quota, or dependency changes. Update the plugin and fully restart DSH.
|
|
9
|
+
|
|
3
10
|
## 2.0.7 — 2026-10-09
|
|
4
11
|
|
|
5
12
|
- Give the installed plugin card a localized display name: **Grok Subscription** in English and **Grok 订阅** in Simplified Chinese. Export and ship package-level `locale/en.json` and `locale/zh-CN.json` with `meta.title` and `meta.description`, the same metadata contract used by DSH. The npm package name remains `dsh-grok-subscription`.
|
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.8`.
|
|
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.8
|
|
71
71
|
```
|
|
72
72
|
|
|
73
73
|
`dsh plugin --profile desktop …` is refused: the Electron application owns that profile exclusively. 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.8`。
|
|
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.8
|
|
71
71
|
```
|
|
72
72
|
|
|
73
73
|
`dsh plugin --profile desktop …` 会被拒绝:桌面版 profile 由 Electron 应用独占。登录方式与故障排查见 [完整使用指南](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 and an account with Grok Build access. The grok command is not required. 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.8`.
|
|
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.8
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
`dsh plugin --profile desktop …` is refused: the Electron application owns that profile exclusively.
|
|
@@ -69,6 +69,8 @@ For prefix caching, the plugin pins one stable `prompt_cache_key` per DSH sessio
|
|
|
69
69
|
|
|
70
70
|
**No sign-in window.** Look for a clickable authorization link and code in the panel. A browser failing to open does not by itself prove `auth.x.ai` is unreachable; use “Open the sign-in page”. When a proxy is needed, set `https_proxy` in DSH's launch environment or `~/.dsh/.env`, then restart completely. `DSH_*` is a DSH bootstrap prefix, so declaring it in `~/.dsh/.env` makes that environment layer fail.
|
|
71
71
|
|
|
72
|
+
**Sign-in fails with `Cannot reach auth.x.ai` (2.0.4–2.0.7 showed `Grok subscription RPC "login/cli" timed out after 20000ms`).** The DSH process cannot connect to `auth.x.ai`. This is typical when the browser gets online through a proxy app's “system proxy” switch: DSH does not read the macOS or Windows system proxy setting, so a browser that opens x.ai does not mean DSH can. Add `https_proxy=http://127.0.0.1:<port>` to `~/.dsh/.env` (on Windows, `%USERPROFILE%\.dsh\.env`), using the proxy app's HTTP (or mixed) port; a `socks5://` URL is not supported and is skipped in favor of a direct connection. Save, then quit and restart DSH completely, or switch the proxy app to TUN mode. Conversations and usage (`cli-chat-proxy.grok.com`) depend on the same setting after sign-in.
|
|
73
|
+
|
|
72
74
|
**`fetch failed` or `Billing request timed out`.** Check access to the account service and `cli-chat-proxy.grok.com` separately. When a proxy is needed, set standard `https_proxy` / `http_proxy` variables in DSH's launch environment or `~/.dsh/.env`, not the project directory's `.env`, then manually restart DSH. The plugin uses the host network setup rather than providing its own proxy service. Network conditions are one possibility; also check authentication and backend errors.
|
|
73
75
|
|
|
74
76
|
**Empty model list.** Finish subscription sign-in, or select **Read saved session**. Check for an API-key-only entry and inspect catalog status without sharing raw responses.
|
|
@@ -95,7 +97,7 @@ In the desktop app the `desktop` profile is owned exclusively by the Electron ap
|
|
|
95
97
|
|
|
96
98
|
```sh
|
|
97
99
|
cd "$HOME/.dsh/profiles/desktop" # Windows: cd "$env:USERPROFILE\.dsh\profiles\desktop"
|
|
98
|
-
pnpm add --save-exact --config.minimumReleaseAge=0 dsh-grok-subscription@2.0.
|
|
100
|
+
pnpm add --save-exact --config.minimumReleaseAge=0 dsh-grok-subscription@2.0.8
|
|
99
101
|
```
|
|
100
102
|
|
|
101
103
|
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 使用资格的订阅账号。不需要安装 grok 命令。插件的 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.8`。
|
|
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.8
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
`dsh plugin --profile desktop …` 会被直接拒绝:桌面版 profile 由 Electron 应用独占。
|
|
@@ -69,6 +69,8 @@ grok-4.6
|
|
|
69
69
|
|
|
70
70
|
**没有弹出登录窗口。** 看面板是否已经显示可点击的授权链接和验证码。浏览器没有弹出,不一定等于 `auth.x.ai` 不可达;点「打开登录页面」。需要代理时,在 DSH 启动环境或 `~/.dsh/.env` 设置 `https_proxy` 后完全重启。注意 `DSH_*` 是 DSH 的 bootstrap 前缀,写进 `~/.dsh/.env` 会让该环境层直接报错。
|
|
71
71
|
|
|
72
|
+
**点击登录后报 `Cannot reach auth.x.ai`(2.0.4–2.0.7 显示为 `Grok subscription RPC "login/cli" timed out after 20000ms`)。** DSH 进程连不上 `auth.x.ai`。常见于浏览器靠代理软件的“系统代理”开关上网的环境:DSH 不读取 macOS / Windows 的系统代理设置,所以浏览器能打开 x.ai 不代表 DSH 也能。在 `~/.dsh/.env`(Windows 为 `%USERPROFILE%\.dsh\.env`)写入 `https_proxy=http://127.0.0.1:<端口>`,端口填代理软件的 HTTP(或 mixed)端口;`socks5://` 不受支持,会被跳过并直连。保存后完全退出并重启 DSH,或改用代理软件的 TUN 模式。登录之后的对话与用量请求(`cli-chat-proxy.grok.com`)也依赖同一设置。
|
|
73
|
+
|
|
72
74
|
**`fetch failed` / `Billing request timed out`。** 分别检查账号服务和 `cli-chat-proxy.grok.com` 的网络连接。需要代理时,在 DSH 启动环境或 `~/.dsh/.env` 中设置标准 `https_proxy` / `http_proxy`,而不是放进项目目录的 `.env`;再手动重启 DSH。插件沿用宿主网络配置,不提供单独代理服务。网络只是可能原因,还应检查认证和服务端错误。
|
|
73
75
|
|
|
74
76
|
**模型列表为空。** 先完成订阅登录,或点击「读取已保存的会话」。检查是否只有 API-key 条目;查看目录状态,不要公开原始响应。
|
|
@@ -95,7 +97,7 @@ grok-4.6
|
|
|
95
97
|
|
|
96
98
|
```sh
|
|
97
99
|
cd "$HOME/.dsh/profiles/desktop" # Windows: cd "$env:USERPROFILE\.dsh\profiles\desktop"
|
|
98
|
-
pnpm add --save-exact --config.minimumReleaseAge=0 dsh-grok-subscription@2.0.
|
|
100
|
+
pnpm add --save-exact --config.minimumReleaseAge=0 dsh-grok-subscription@2.0.8
|
|
99
101
|
```
|
|
100
102
|
|
|
101
103
|
然后完全退出并重启桌面版。更新成功后该 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.8",
|
|
43
43
|
signedIn: "\u5DF2\u767B\u5F55",
|
|
44
44
|
signedOut: "\u672A\u767B\u5F55",
|
|
45
45
|
account: "\u8D26\u6237",
|
|
@@ -57,7 +57,7 @@ var zh = {
|
|
|
57
57
|
deviceHint: "\u9762\u677F\u4F1A\u663E\u793A\u4E00\u6B21\u6027\u9A8C\u8BC1\u7801\u3002\u82E5\u6D4F\u89C8\u5668\u6CA1\u6709\u81EA\u52A8\u6253\u5F00\uFF0C\u70B9\u63D0\u793A\u91CC\u7684\u201C\u6253\u5F00\u767B\u5F55\u9875\u9762\u201D\uFF0C\u5E76\u5728\u9875\u9762\u4E0A\u786E\u8BA4\u540C\u4E00\u4E2A\u9A8C\u8BC1\u7801\u3002",
|
|
58
58
|
loginWaiting: "\u7B49\u5F85\u6D4F\u89C8\u5668\u6388\u6743\u2026",
|
|
59
59
|
loginOpenBrowser: "\u6253\u5F00\u767B\u5F55\u9875\u9762",
|
|
60
|
-
|
|
60
|
+
loginUnreachable: "DSH \u8FDE\u4E0D\u4E0A auth.x.ai\u3002\u6D4F\u89C8\u5668\u80FD\u6253\u5F00 x.ai \u4E0D\u4EE3\u8868 DSH \u4E5F\u80FD\uFF1ADSH \u4E0D\u8BFB\u53D6\u7CFB\u7EDF\u4EE3\u7406\uFF08\u4F8B\u5982 Clash\u3001v2rayN \u7684\u201C\u7CFB\u7EDF\u4EE3\u7406\u201D\u5F00\u5173\uFF09\u3002\u8BF7\u5728 ~/.dsh/.env\uFF08Windows \u4E3A %USERPROFILE%\\.dsh\\.env\uFF09\u5199\u5165 https_proxy=http://127.0.0.1:\u7AEF\u53E3\uFF08\u586B\u4EE3\u7406\u8F6F\u4EF6\u7684 HTTP \u7AEF\u53E3\uFF0C\u4E0D\u652F\u6301 socks5://\uFF09\uFF0C\u7136\u540E\u5B8C\u5168\u9000\u51FA\u5E76\u91CD\u542F DSH\uFF1B\u4E5F\u53EF\u4EE5\u6539\u7528\u4EE3\u7406\u8F6F\u4EF6\u7684 TUN \u6A21\u5F0F\u3002",
|
|
61
61
|
loginTimeout: "\u7B49\u5F85\u6388\u6743\u8D85\u65F6\uFF0C\u8BF7\u91CD\u65B0\u70B9\u51FB\u767B\u5F55\u3002",
|
|
62
62
|
pullHint: "\u5B89\u5168\u8BFB\u53D6 ~/.grok/auth.json\uFF08\u62D2\u7EDD\u7B26\u53F7\u94FE\u63A5\u4E0E\u7EC4/\u5176\u4ED6\u4EBA\u53EF\u8BFB\uFF09\u3002\u63D2\u4EF6\u767B\u5F55\u548C\u7EED\u671F\u4E5F\u4F1A\u5199\u56DE\u8FD9\u4E2A\u6587\u4EF6\uFF1B\u53EA\u628A\u77ED\u671F access token \u5199\u5165 DSH\u3002",
|
|
63
63
|
loginHelp: "\u767B\u5F55\u65B9\u5F0F\u8BF4\u660E",
|
|
@@ -122,7 +122,7 @@ var zh = {
|
|
|
122
122
|
var en = {
|
|
123
123
|
nav: "Grok Subscription",
|
|
124
124
|
title: "Grok Subscription",
|
|
125
|
-
subtitle: "Use a SuperGrok / X Premium (Grok Build) session, not XAI_API_KEY. \xB7 2.0.
|
|
125
|
+
subtitle: "Use a SuperGrok / X Premium (Grok Build) session, not XAI_API_KEY. \xB7 2.0.8",
|
|
126
126
|
signedIn: "Signed in",
|
|
127
127
|
signedOut: "Signed out",
|
|
128
128
|
account: "Account",
|
|
@@ -140,7 +140,7 @@ var en = {
|
|
|
140
140
|
deviceHint: "The panel shows a one-time code. If no browser opened, use \u201COpen the sign-in page\u201D in the status line and confirm that same code.",
|
|
141
141
|
loginWaiting: "Waiting for browser authorization\u2026",
|
|
142
142
|
loginOpenBrowser: "Open the sign-in page",
|
|
143
|
-
|
|
143
|
+
loginUnreachable: "DSH cannot reach auth.x.ai. A browser that opens x.ai does not mean DSH can: DSH ignores the system proxy setting (such as the \u201Csystem proxy\u201D switch in Clash or v2rayN). Add https_proxy=http://127.0.0.1:PORT to ~/.dsh/.env (on Windows, %USERPROFILE%\\.dsh\\.env), using your proxy app\u2019s HTTP port (socks5:// is not supported), then quit and restart DSH completely. A proxy app\u2019s TUN mode also works.",
|
|
144
144
|
loginTimeout: "Timed out waiting for authorization; click sign-in again.",
|
|
145
145
|
pullHint: "Reads ~/.grok/auth.json securely (refuses symlinks and group/other-readable files). Sign-in and renewal write this file too, and only the short-lived access token is stored in DSH.",
|
|
146
146
|
loginHelp: "How signing in works",
|
|
@@ -905,6 +905,7 @@ function GrokSubscriptionSection({ rpc, t }) {
|
|
|
905
905
|
const [busy, setBusy] = (0, import_react2.useState)("");
|
|
906
906
|
const [usageBusy, setUsageBusy] = (0, import_react2.useState)(false);
|
|
907
907
|
const [error, setError] = (0, import_react2.useState)("");
|
|
908
|
+
const [unreachable, setUnreachable] = (0, import_react2.useState)(false);
|
|
908
909
|
const [notice, setNotice] = (0, import_react2.useState)("");
|
|
909
910
|
const [status, setStatus] = (0, import_react2.useState)(void 0);
|
|
910
911
|
const [loginPending, setLoginPending] = (0, import_react2.useState)(void 0);
|
|
@@ -975,6 +976,7 @@ function GrokSubscriptionSection({ rpc, t }) {
|
|
|
975
976
|
setBusy(endpoint);
|
|
976
977
|
}
|
|
977
978
|
setError("");
|
|
979
|
+
setUnreachable(false);
|
|
978
980
|
setNotice("");
|
|
979
981
|
let value;
|
|
980
982
|
try {
|
|
@@ -987,8 +989,9 @@ function GrokSubscriptionSection({ rpc, t }) {
|
|
|
987
989
|
if (value?.ok === false && value.error) {
|
|
988
990
|
setNotice("");
|
|
989
991
|
setError(value.error);
|
|
992
|
+
if (isLogin && value.errorCode === "network") setUnreachable(true);
|
|
990
993
|
} else if (value?.pending === true) {
|
|
991
|
-
setLoginPending({ url: value.loginUrl, code: value.userCode
|
|
994
|
+
setLoginPending({ url: value.loginUrl, code: value.userCode });
|
|
992
995
|
setNotice("");
|
|
993
996
|
} else if (value?.ok !== false) {
|
|
994
997
|
const successKey = {
|
|
@@ -1005,8 +1008,10 @@ function GrokSubscriptionSection({ rpc, t }) {
|
|
|
1005
1008
|
}
|
|
1006
1009
|
}
|
|
1007
1010
|
} catch (item) {
|
|
1011
|
+
const message = item instanceof Error ? item.message : String(item);
|
|
1008
1012
|
setNotice("");
|
|
1009
|
-
setError(
|
|
1013
|
+
setError(message);
|
|
1014
|
+
if (isLogin && /timed out/iu.test(message)) setUnreachable(true);
|
|
1010
1015
|
} finally {
|
|
1011
1016
|
if (isUsageRefresh) setUsageBusy(false);
|
|
1012
1017
|
else setBusy("");
|
|
@@ -1047,13 +1052,13 @@ function GrokSubscriptionSection({ rpc, t }) {
|
|
|
1047
1052
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("a", { className: "gsLink", href: loginPending.url, target: "_blank", rel: "noreferrer", children: t("loginOpenBrowser") })
|
|
1048
1053
|
] }) : null
|
|
1049
1054
|
] }) : null,
|
|
1050
|
-
loginPending?.warning ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "gsStatus gsStatus--warn", children: t("loginNoUrl") }) : null,
|
|
1051
1055
|
notice ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "gsStatus gsStatus--ok", children: notice }) : null,
|
|
1052
1056
|
error ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "gsStatus gsStatus--error", children: [
|
|
1053
1057
|
t("error"),
|
|
1054
1058
|
": ",
|
|
1055
1059
|
error
|
|
1056
1060
|
] }) : null,
|
|
1061
|
+
error && unreachable ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "gsStatus gsStatus--warn", children: t("loginUnreachable") }) : null,
|
|
1057
1062
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(DiagnosticsRows, { diagnostics: status?.diagnostics, t }),
|
|
1058
1063
|
status?.diagnostics && !status.diagnostics.imageInput ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "gsHint", children: t("adapterHint") }) : null,
|
|
1059
1064
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("details", { className: "gsDisclosure", children: [
|
package/lib/index.js
CHANGED
|
@@ -39,6 +39,7 @@ var IMAGE_INPUT_MODEL_IDS = Object.freeze([
|
|
|
39
39
|
var IMPORT_TIMEOUT_MS = 2500;
|
|
40
40
|
var CREDENTIALS_IO_TIMEOUT_MS = 1500;
|
|
41
41
|
var RPC_HANDLER_TIMEOUT_MS = 8e3;
|
|
42
|
+
var LOGIN_START_TIMEOUT_MS = 15e3;
|
|
42
43
|
var LOGIN_HANDLER_TIMEOUT_MS = 2e4;
|
|
43
44
|
|
|
44
45
|
// src/session.js
|
|
@@ -1302,15 +1303,67 @@ function timeoutSignal(ms) {
|
|
|
1302
1303
|
}
|
|
1303
1304
|
return void 0;
|
|
1304
1305
|
}
|
|
1305
|
-
|
|
1306
|
-
|
|
1306
|
+
var OauthNetworkError = class extends Error {
|
|
1307
|
+
constructor(host, reason, options) {
|
|
1308
|
+
super(`Cannot reach ${host} (${reason})`, options);
|
|
1309
|
+
this.name = "OauthNetworkError";
|
|
1310
|
+
this.code = "network";
|
|
1311
|
+
this.host = host;
|
|
1312
|
+
}
|
|
1313
|
+
};
|
|
1314
|
+
var NETWORK_REASONS = Object.freeze({
|
|
1315
|
+
UND_ERR_CONNECT_TIMEOUT: "connection timed out",
|
|
1316
|
+
ETIMEDOUT: "connection timed out",
|
|
1317
|
+
UND_ERR_HEADERS_TIMEOUT: "no response from the server",
|
|
1318
|
+
UND_ERR_BODY_TIMEOUT: "the response stalled",
|
|
1319
|
+
ECONNREFUSED: "connection refused",
|
|
1320
|
+
ECONNRESET: "connection reset",
|
|
1321
|
+
UND_ERR_SOCKET: "connection closed",
|
|
1322
|
+
EPIPE: "connection closed",
|
|
1323
|
+
ENOTFOUND: "DNS lookup failed",
|
|
1324
|
+
EAI_AGAIN: "DNS lookup failed",
|
|
1325
|
+
ENETUNREACH: "network unreachable",
|
|
1326
|
+
EHOSTUNREACH: "host unreachable"
|
|
1327
|
+
});
|
|
1328
|
+
function networkCodeOf(error) {
|
|
1329
|
+
let current = error;
|
|
1330
|
+
for (let depth = 0; current && depth < 5; depth += 1) {
|
|
1331
|
+
if (typeof current.code === "string" && current.code) return current.code;
|
|
1332
|
+
current = current.cause ?? (Array.isArray(current.errors) ? current.errors[0] : void 0);
|
|
1333
|
+
}
|
|
1334
|
+
return void 0;
|
|
1335
|
+
}
|
|
1336
|
+
function networkReason(error) {
|
|
1337
|
+
const name2 = error?.name;
|
|
1338
|
+
if (name2 === "TimeoutError" || name2 === "AbortError") return "timed out";
|
|
1339
|
+
const code = networkCodeOf(error);
|
|
1340
|
+
if (code && NETWORK_REASONS[code]) return NETWORK_REASONS[code];
|
|
1341
|
+
if (code && /CERT|TLS|SSL/u.test(code)) return `TLS certificate rejected: ${code}`;
|
|
1342
|
+
return code ? `network error: ${code}` : "network error";
|
|
1343
|
+
}
|
|
1344
|
+
function hostOf(url) {
|
|
1345
|
+
try {
|
|
1346
|
+
return new URL(url).hostname;
|
|
1347
|
+
} catch {
|
|
1348
|
+
return "the sign-in server";
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
async function fetchOrNetworkError(fetchImpl, url, init) {
|
|
1352
|
+
try {
|
|
1353
|
+
return await fetchImpl(url, init);
|
|
1354
|
+
} catch (error) {
|
|
1355
|
+
throw new OauthNetworkError(hostOf(url), networkReason(error), { cause: error });
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
async function postForm(url, params, fetchImpl, signal) {
|
|
1359
|
+
const response = await fetchOrNetworkError(fetchImpl, url, {
|
|
1307
1360
|
method: "POST",
|
|
1308
1361
|
headers: {
|
|
1309
1362
|
accept: "application/json",
|
|
1310
1363
|
"content-type": "application/x-www-form-urlencoded"
|
|
1311
1364
|
},
|
|
1312
1365
|
body: new URLSearchParams(params).toString(),
|
|
1313
|
-
signal: timeoutSignal(CLI_REFRESH_TIMEOUT_MS)
|
|
1366
|
+
signal: signal ?? timeoutSignal(CLI_REFRESH_TIMEOUT_MS)
|
|
1314
1367
|
});
|
|
1315
1368
|
const body = await readOauthBody(response);
|
|
1316
1369
|
return { status: typeof response.status === "number" ? response.status : 0, body };
|
|
@@ -1350,11 +1403,12 @@ function fallbackOauthEndpoints(issuer = XAI_OAUTH_ISSUER) {
|
|
|
1350
1403
|
async function discoverOauthEndpoints(issuer = XAI_OAUTH_ISSUER, options = {}) {
|
|
1351
1404
|
const fetchImpl = options.fetch ?? fetch;
|
|
1352
1405
|
const base = String(issuer).replace(/\/+$/u, "");
|
|
1406
|
+
const url = `${base}/.well-known/openid-configuration`;
|
|
1407
|
+
const response = await fetchOrNetworkError(fetchImpl, url, {
|
|
1408
|
+
headers: { accept: "application/json" },
|
|
1409
|
+
signal: options.signal ?? timeoutSignal(CLI_REFRESH_TIMEOUT_MS)
|
|
1410
|
+
});
|
|
1353
1411
|
try {
|
|
1354
|
-
const response = await fetchImpl(`${base}/.well-known/openid-configuration`, {
|
|
1355
|
-
headers: { accept: "application/json" },
|
|
1356
|
-
signal: timeoutSignal(CLI_REFRESH_TIMEOUT_MS)
|
|
1357
|
-
});
|
|
1358
1412
|
const body = await readOauthBody(response);
|
|
1359
1413
|
const status = typeof response.status === "number" ? response.status : 0;
|
|
1360
1414
|
if (status < 200 || status >= 300) throw new Error(`OIDC discovery failed (HTTP ${status})`);
|
|
@@ -1400,7 +1454,7 @@ async function requestDeviceAuthorization(options = {}) {
|
|
|
1400
1454
|
const { status, body } = await postForm(endpoint, {
|
|
1401
1455
|
client_id: clientId,
|
|
1402
1456
|
scope: scopes
|
|
1403
|
-
}, fetchImpl);
|
|
1457
|
+
}, fetchImpl, options.signal);
|
|
1404
1458
|
if (body?.error || status < 200 || status >= 300) {
|
|
1405
1459
|
throw new Error(`Could not start device sign-in: ${oauthError(body, status)}`);
|
|
1406
1460
|
}
|
|
@@ -1582,6 +1636,7 @@ function createSessionService({
|
|
|
1582
1636
|
authPath,
|
|
1583
1637
|
env,
|
|
1584
1638
|
oauthEndpoints,
|
|
1639
|
+
loginStartTimeoutMs = LOGIN_START_TIMEOUT_MS,
|
|
1585
1640
|
now = () => Date.now()
|
|
1586
1641
|
} = {}) {
|
|
1587
1642
|
const renew = renewSession ?? (() => defaultRenewSession({
|
|
@@ -1834,17 +1889,28 @@ function createSessionService({
|
|
|
1834
1889
|
const sleep = loginOptions?.sleep ?? defaultSleep;
|
|
1835
1890
|
const openUrl = loginOptions?.openUrl ?? ((url) => openExternal(url, { spawn: loginOptions?.openSpawn }));
|
|
1836
1891
|
const clock = () => typeof loginOptions?.now === "function" ? loginOptions.now() : now();
|
|
1892
|
+
const requestedStartMs = loginOptions?.startTimeoutMs ?? loginStartTimeoutMs;
|
|
1893
|
+
const startTimeoutMs = typeof requestedStartMs === "number" && requestedStartMs > 0 ? requestedStartMs : LOGIN_START_TIMEOUT_MS;
|
|
1894
|
+
const startSignal = loginOptions?.signal ?? timeoutSignal(startTimeoutMs);
|
|
1837
1895
|
let endpoints;
|
|
1838
1896
|
let device;
|
|
1839
1897
|
try {
|
|
1840
|
-
endpoints = loginOptions?.endpoints ?? oauthEndpoints ?? await discoverOauthEndpoints(XAI_OAUTH_ISSUER, { fetch: fetchLogin });
|
|
1898
|
+
endpoints = loginOptions?.endpoints ?? oauthEndpoints ?? await discoverOauthEndpoints(XAI_OAUTH_ISSUER, { fetch: fetchLogin, signal: startSignal });
|
|
1841
1899
|
device = await requestDeviceAuthorization({
|
|
1842
1900
|
endpoint: endpoints.deviceAuthorizationEndpoint,
|
|
1843
|
-
fetch: fetchLogin
|
|
1901
|
+
fetch: fetchLogin,
|
|
1902
|
+
signal: startSignal
|
|
1844
1903
|
});
|
|
1845
1904
|
} catch (error) {
|
|
1846
1905
|
const message = error instanceof Error ? error.message : "Could not start sign-in";
|
|
1847
|
-
|
|
1906
|
+
const network = error?.code === "network";
|
|
1907
|
+
if (network) logger?.warn?.("Grok subscription sign-in could not start: %s", message);
|
|
1908
|
+
return {
|
|
1909
|
+
ok: false,
|
|
1910
|
+
error: message,
|
|
1911
|
+
...network ? { errorCode: "network" } : {},
|
|
1912
|
+
...await status()
|
|
1913
|
+
};
|
|
1848
1914
|
}
|
|
1849
1915
|
const loginUrl = device.verificationUriComplete || device.verificationUri;
|
|
1850
1916
|
if (!isXaiHttpsUrl(loginUrl) || !device.userCode || !device.deviceCode) {
|