dsh-grok-subscription 1.0.11 → 1.0.18
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/README.en.md +2 -1
- package/README.md +2 -1
- package/lib/client.js +59 -12
- package/lib/index.js +334 -52
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -31,7 +31,7 @@ Models, reasoning effort, and weekly quota all stay inside DSH.
|
|
|
31
31
|
dsh plugin --profile web add BaronCyrus/dsh-grok-subscription
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
2. **Sign in to the subscription.** Open **Settings → Grok Subscription** and click **CLI login** or **Device-code login
|
|
34
|
+
2. **Sign in to the subscription.** Open **Settings → Grok Subscription** and click **CLI login** or **Device-code login**. The plugin runs the official `grok login`, hands the link the CLI prints to your browser, and shows the one-time code plus a clickable link in the panel; the session syncs itself once you authorize. If you already ran `grok login` yourself, just click **Pull from Grok CLI** — nothing has to be pasted.
|
|
35
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
36
|
|
|
37
37
|
Restart `dsh web` after installing or upgrading, otherwise the old `lib/` keeps running.
|
|
@@ -209,6 +209,7 @@ npx -y @deepseek-ai/dsh@0.1.5-rc.2 plugin --profile web remove dsh-grok-subscrip
|
|
|
209
209
|
|
|
210
210
|
- **`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;
|
|
211
211
|
- **The model list is empty**: nothing is exposed while signed out. Finish signing in, then click **Pull from Grok CLI**;
|
|
212
|
+
- **Sign-in opens no browser / chat fails with `fetch failed` / usage says "Billing request timed out"**: all three have one cause — `accounts.x.ai` and `cli-chat-proxy.grok.com` need a tunnel on some networks. Add `GROK_PROXY=http://127.0.0.1:10808` to `~/.dsh/.env`: it applies to **this plugin's Grok requests** (catalog, usage, chat) and to the **`grok` CLI** it spawns (sign-in, renewal), while DSH and every other provider stay direct — an `http_proxy` line there would route them all through the tunnel and break them whenever it is down. Restart the client afterwards; the sign-in link is handed to your browser and the panel also shows the one-time code and a clickable link. Standard proxy variables already present in the environment are inherited by the CLI too;
|
|
212
213
|
- **Nothing changed after upgrading**: the plugin keeps running the old `lib/` — reinstall the plugin, restart `dsh web`, and hard-refresh (Ctrl+Shift+R);
|
|
213
214
|
- **It reports `auth.json` permissions**: apply the `chmod 600` above; the plugin refuses symlinks and group- or other-readable files;
|
|
214
215
|
- **"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.
|
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
dsh plugin --profile web add BaronCyrus/dsh-grok-subscription
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
2. **登录订阅**:打开 **设置 → Grok 订阅**,点击 **CLI 登录** 或
|
|
34
|
+
2. **登录订阅**:打开 **设置 → Grok 订阅**,点击 **CLI 登录** 或 **设备码登录**。插件会运行官方 `grok login`,把 CLI 打印的登录链接交给浏览器,并在面板里显示一次性验证码与可点的链接;授权完成后自动同步。已经自己跑过 `grok login` 的话,直接点 **从 Grok CLI 拉取** 即可,不需要粘贴任何 token。
|
|
35
35
|
3. **开始使用**:在模型选择器中选择 Grok 4.7 等模型。输入框旁的圆形徽章显示每周剩余额度,模型菜单里有推理档位。
|
|
36
36
|
|
|
37
37
|
安装或升级后需要重启 `dsh web`,否则会继续运行旧的 `lib/`。
|
|
@@ -209,6 +209,7 @@ npx -y @deepseek-ai/dsh@0.1.5-rc.2 plugin --profile web remove dsh-grok-subscrip
|
|
|
209
209
|
|
|
210
210
|
- **`dsh` 无法识别**:官方 npm 方式本来就不会创建全局 `dsh` 命令,请使用上面的完整 `npx -y @deepseek-ai/dsh@0.1.5-rc.2 ...` 命令;
|
|
211
211
|
- **模型列表是空的**:未登录时插件不暴露任何模型。先完成登录,再点 **从 Grok CLI 拉取**;
|
|
212
|
+
- **登录不弹浏览器 / 聊天报 `fetch failed` / 用量「Billing request timed out」**:这三件事都指向同一个原因——`accounts.x.ai` 与 `cli-chat-proxy.grok.com` 在部分网络里必须走隧道。在 `~/.dsh/.env` 写一行 `GROK_PROXY=http://127.0.0.1:10808` 即可:它只作用于**本插件的 Grok 请求**(模型目录、用量、聊天)和它启动的 **`grok` CLI**(登录、续期),DSH 自身与其他 provider 仍走直连——把 `http_proxy` 写在这里会让所有 provider 一起走隧道,隧道一停就全连不上。写好后重启客户端;登录链接会交给系统浏览器打开,面板里同时显示一次性验证码与可点链接。环境里已有的标准代理变量同样会被 CLI 继承;
|
|
212
213
|
- **升级后界面没变化**:插件会继续运行旧的 `lib/`,请重新安装插件并重启 `dsh web`,然后硬刷新(Ctrl+Shift+R);
|
|
213
214
|
- **提示 `auth.json` 权限不正确**:按上面的 `chmod 600` 处理;插件拒绝读取符号链接或组/其他用户可读的文件;
|
|
214
215
|
- **出现「没有回复」**:请升级到 `1.0.1` 或更高版本。`1.0.0` 存在一个缺陷:工具调用轮次会因流片段无法无损序列化而整轮中止,界面上完全没有回复。
|
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 1.0.
|
|
42
|
+
subtitle: "\u7528 SuperGrok / X Premium\uFF08Grok Build\uFF09\u4F1A\u8BDD\uFF0C\u800C\u4E0D\u662F XAI_API_KEY\u3002\xB7 1.0.18",
|
|
43
43
|
signedIn: "\u5DF2\u767B\u5F55",
|
|
44
44
|
signedOut: "\u672A\u767B\u5F55",
|
|
45
45
|
account: "\u8D26\u6237",
|
|
@@ -54,8 +54,12 @@ var zh = {
|
|
|
54
54
|
loginDevice: "\u8BBE\u5907\u7801\u767B\u5F55",
|
|
55
55
|
pull: "\u4ECE Grok CLI \u62C9\u53D6",
|
|
56
56
|
logout: "\u9000\u51FA\u767B\u5F55",
|
|
57
|
-
loginHint: "\
|
|
58
|
-
deviceHint: "\u8BBE\u5907\u7801\
|
|
57
|
+
loginHint: "\u70B9\u51FB\u540E\u5728\u672C\u673A\u8FD0\u884C grok login\uFF0C\u5E76\u628A CLI \u6253\u5370\u7684\u767B\u5F55\u94FE\u63A5\u4EA4\u7ED9\u6D4F\u89C8\u5668\uFF1B\u6388\u6743\u5B8C\u6210\u540E\u8FD9\u91CC\u4F1A\u81EA\u52A8\u540C\u6B65\u3002",
|
|
58
|
+
deviceHint: "\u8BBE\u5907\u7801\u767B\u5F55\u4F1A\u8BA9 CLI \u6253\u5370\u4E00\u6B21\u6027\u9A8C\u8BC1\u7801\u548C\u94FE\u63A5\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\u5E76\u786E\u8BA4\u9A8C\u8BC1\u7801\u3002",
|
|
59
|
+
loginWaiting: "\u7B49\u5F85\u6D4F\u89C8\u5668\u6388\u6743\u2026",
|
|
60
|
+
loginOpenBrowser: "\u6253\u5F00\u767B\u5F55\u9875\u9762",
|
|
61
|
+
loginNoUrl: "\u6CA1\u62FF\u5230\u767B\u5F55\u94FE\u63A5\uFF1A\u8BF7\u786E\u8BA4 grok CLI \u80FD\u8BBF\u95EE x.ai\uFF08\u684C\u9762\u7248\u901A\u5E38\u9700\u8981\u4EE3\u7406\uFF09\uFF0C\u7136\u540E\u91CD\u8BD5\u3002",
|
|
62
|
+
loginTimeout: "\u7B49\u5F85\u6388\u6743\u8D85\u65F6\uFF0C\u8BF7\u91CD\u65B0\u70B9\u51FB\u767B\u5F55\u3002",
|
|
59
63
|
pullHint: "\u5B89\u5168\u8BFB\u53D6 ~/.grok/auth.json\uFF08\u62D2\u7EDD\u7B26\u53F7\u94FE\u63A5\u4E0E\u7EC4/\u5176\u4ED6\u4EBA\u53EF\u8BFB\uFF09\uFF0C\u53EA\u628A\u77ED\u671F access token \u5199\u5165 DSH\u3002",
|
|
60
64
|
loginHelp: "\u767B\u5F55\u65B9\u5F0F\u8BF4\u660E",
|
|
61
65
|
refreshCatalog: "\u5237\u65B0\u6A21\u578B\u76EE\u5F55",
|
|
@@ -115,7 +119,7 @@ var zh = {
|
|
|
115
119
|
var en = {
|
|
116
120
|
nav: "Grok Subscription",
|
|
117
121
|
title: "Grok Subscription",
|
|
118
|
-
subtitle: "Use a SuperGrok / X Premium (Grok Build) session, not XAI_API_KEY. \xB7 1.0.
|
|
122
|
+
subtitle: "Use a SuperGrok / X Premium (Grok Build) session, not XAI_API_KEY. \xB7 1.0.18",
|
|
119
123
|
signedIn: "Signed in",
|
|
120
124
|
signedOut: "Signed out",
|
|
121
125
|
account: "Account",
|
|
@@ -130,8 +134,12 @@ var en = {
|
|
|
130
134
|
loginDevice: "Device-code login",
|
|
131
135
|
pull: "Pull from Grok CLI",
|
|
132
136
|
logout: "Log out",
|
|
133
|
-
loginHint: "Runs grok login
|
|
134
|
-
deviceHint: "
|
|
137
|
+
loginHint: "Runs grok login on this machine and hands the link the CLI prints to your browser; the panel syncs itself once you authorize.",
|
|
138
|
+
deviceHint: "Device-code login makes the CLI print a one-time code and link. If no browser opened, use \u201COpen the sign-in page\u201D in the status line and confirm the code.",
|
|
139
|
+
loginWaiting: "Waiting for browser authorization\u2026",
|
|
140
|
+
loginOpenBrowser: "Open the sign-in page",
|
|
141
|
+
loginNoUrl: "No sign-in link was returned: make sure the grok CLI can reach x.ai (the desktop app usually needs a proxy), then retry.",
|
|
142
|
+
loginTimeout: "Timed out waiting for authorization; click sign-in again.",
|
|
135
143
|
pullHint: "Reads ~/.grok/auth.json securely (refuses symlinks and group/other-readable files) and stores only the short-lived access token in DSH.",
|
|
136
144
|
loginHelp: "How signing in works",
|
|
137
145
|
refreshCatalog: "Refresh model catalog",
|
|
@@ -640,12 +648,14 @@ var inject = [
|
|
|
640
648
|
"slots",
|
|
641
649
|
"locale",
|
|
642
650
|
"connection",
|
|
643
|
-
"settingsScope",
|
|
644
651
|
"modelDirectories",
|
|
645
652
|
"sessions",
|
|
646
653
|
"remote"
|
|
647
654
|
];
|
|
648
655
|
var RPC_CALL_TIMEOUT_MS = 12e3;
|
|
656
|
+
var LOGIN_CALL_TIMEOUT_MS = 3e4;
|
|
657
|
+
var LOGIN_POLL_INTERVAL_MS = 3e3;
|
|
658
|
+
var LOGIN_POLL_TIMEOUT_MS = 10 * 6e4;
|
|
649
659
|
function pickCopy(locale) {
|
|
650
660
|
const language = typeof locale === "string" ? locale : locale?.language ?? locale?.lang;
|
|
651
661
|
const resolved = language ?? (typeof navigator !== "undefined" ? navigator.language : "zh-CN");
|
|
@@ -672,7 +682,7 @@ async function callRpc(rpc, endpoint, payload = {}, timeoutMs = RPC_CALL_TIMEOUT
|
|
|
672
682
|
controller?.abort();
|
|
673
683
|
} catch {
|
|
674
684
|
}
|
|
675
|
-
reject(new Error(`Request timed out after ${
|
|
685
|
+
reject(new Error(`Request timed out after ${timeoutMs}ms`));
|
|
676
686
|
}, timeoutMs);
|
|
677
687
|
});
|
|
678
688
|
return unwrap(await Promise.race([call, timeout]));
|
|
@@ -876,6 +886,7 @@ function GrokSubscriptionSection({ rpc, t }) {
|
|
|
876
886
|
const [error, setError] = (0, import_react2.useState)("");
|
|
877
887
|
const [notice, setNotice] = (0, import_react2.useState)("");
|
|
878
888
|
const [status, setStatus] = (0, import_react2.useState)(void 0);
|
|
889
|
+
const [loginPending, setLoginPending] = (0, import_react2.useState)(void 0);
|
|
879
890
|
const initialUsageKick = (0, import_react2.useRef)(false);
|
|
880
891
|
const applyPartial = (value) => {
|
|
881
892
|
if (value?.account || value?.catalog || value?.usage) {
|
|
@@ -914,8 +925,29 @@ function GrokSubscriptionSection({ rpc, t }) {
|
|
|
914
925
|
}).catch(() => {
|
|
915
926
|
});
|
|
916
927
|
};
|
|
928
|
+
(0, import_react2.useEffect)(() => {
|
|
929
|
+
if (!loginPending) return void 0;
|
|
930
|
+
const startedAt = Date.now();
|
|
931
|
+
const timer = setInterval(() => {
|
|
932
|
+
if (Date.now() - startedAt > LOGIN_POLL_TIMEOUT_MS) {
|
|
933
|
+
setLoginPending(void 0);
|
|
934
|
+
setError(t("loginTimeout"));
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
void callRpc(rpc, "status", {}).then((value) => {
|
|
938
|
+
if (value?.account || value?.catalog || value?.usage) applyPartial(value);
|
|
939
|
+
if (value?.account?.signedIn !== true) return;
|
|
940
|
+
setLoginPending(void 0);
|
|
941
|
+
setNotice(t("loginOk"));
|
|
942
|
+
kickFollowUpRefresh();
|
|
943
|
+
}).catch(() => {
|
|
944
|
+
});
|
|
945
|
+
}, LOGIN_POLL_INTERVAL_MS);
|
|
946
|
+
return () => clearInterval(timer);
|
|
947
|
+
}, [loginPending, rpc]);
|
|
917
948
|
const run = async (endpoint) => {
|
|
918
949
|
const isUsageRefresh = endpoint === "usage/refresh";
|
|
950
|
+
const isLogin = endpoint === "login/cli" || endpoint === "login/device";
|
|
919
951
|
if (isUsageRefresh) {
|
|
920
952
|
setUsageBusy(true);
|
|
921
953
|
} else {
|
|
@@ -925,7 +957,7 @@ function GrokSubscriptionSection({ rpc, t }) {
|
|
|
925
957
|
setNotice("");
|
|
926
958
|
let value;
|
|
927
959
|
try {
|
|
928
|
-
value = await callRpc(rpc, endpoint, {});
|
|
960
|
+
value = await callRpc(rpc, endpoint, {}, isLogin ? LOGIN_CALL_TIMEOUT_MS : RPC_CALL_TIMEOUT_MS);
|
|
929
961
|
if (value?.account || value?.catalog || value?.usage) {
|
|
930
962
|
applyPartial(value);
|
|
931
963
|
} else {
|
|
@@ -934,6 +966,9 @@ function GrokSubscriptionSection({ rpc, t }) {
|
|
|
934
966
|
if (value?.ok === false && value.error) {
|
|
935
967
|
setNotice("");
|
|
936
968
|
setError(value.error);
|
|
969
|
+
} else if (value?.pending === true) {
|
|
970
|
+
setLoginPending({ url: value.loginUrl, code: value.userCode, warning: value.warning });
|
|
971
|
+
setNotice("");
|
|
937
972
|
} else if (value?.ok !== false) {
|
|
938
973
|
const successKey = {
|
|
939
974
|
pull: "pullOk",
|
|
@@ -955,7 +990,7 @@ function GrokSubscriptionSection({ rpc, t }) {
|
|
|
955
990
|
if (isUsageRefresh) setUsageBusy(false);
|
|
956
991
|
else setBusy("");
|
|
957
992
|
}
|
|
958
|
-
const shouldFollowUp = (endpoint === "pull" || endpoint === "login/cli" || endpoint === "login/device") && value?.ok !== false && !value?.error;
|
|
993
|
+
const shouldFollowUp = (endpoint === "pull" || endpoint === "login/cli" || endpoint === "login/device") && value?.ok !== false && !value?.error && value?.pending !== true;
|
|
959
994
|
if (shouldFollowUp) kickFollowUpRefresh();
|
|
960
995
|
};
|
|
961
996
|
const account = status?.account;
|
|
@@ -975,12 +1010,24 @@ function GrokSubscriptionSection({ rpc, t }) {
|
|
|
975
1010
|
signedIn && account?.maskedAccount ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gsAccount", children: account.maskedAccount }) : null
|
|
976
1011
|
] }),
|
|
977
1012
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "gsActions", children: [
|
|
978
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "gsBtn gsBtn--primary", type: "button", disabled: Boolean(busy), onClick: () => void run("login/cli"), children: t("loginCli") }),
|
|
979
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "gsBtn", type: "button", disabled: Boolean(busy), onClick: () => void run("login/device"), children: t("loginDevice") }),
|
|
1013
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "gsBtn gsBtn--primary", type: "button", disabled: Boolean(busy) || Boolean(loginPending), onClick: () => void run("login/cli"), children: t("loginCli") }),
|
|
1014
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "gsBtn", type: "button", disabled: Boolean(busy) || Boolean(loginPending), onClick: () => void run("login/device"), children: t("loginDevice") }),
|
|
980
1015
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "gsBtn", type: "button", disabled: Boolean(busy), onClick: () => void run("pull"), children: t("pull") }),
|
|
981
1016
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "gsBtn gsBtn--danger", type: "button", disabled: Boolean(busy) || !signedIn, onClick: () => void run("logout"), children: t("logout") })
|
|
982
1017
|
] }),
|
|
983
1018
|
busy ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "gsStatus gsStatus--busy", children: t("busy") }) : null,
|
|
1019
|
+
loginPending ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "gsStatus gsStatus--busy", children: [
|
|
1020
|
+
t("loginWaiting"),
|
|
1021
|
+
loginPending.code ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
1022
|
+
" \xB7 ",
|
|
1023
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("code", { children: loginPending.code })
|
|
1024
|
+
] }) : null,
|
|
1025
|
+
loginPending.url ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
1026
|
+
" \xB7 ",
|
|
1027
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("a", { className: "gsLink", href: loginPending.url, target: "_blank", rel: "noreferrer", children: t("loginOpenBrowser") })
|
|
1028
|
+
] }) : null
|
|
1029
|
+
] }) : null,
|
|
1030
|
+
loginPending?.warning ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "gsStatus gsStatus--warn", children: t("loginNoUrl") }) : null,
|
|
984
1031
|
notice ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "gsStatus gsStatus--ok", children: notice }) : null,
|
|
985
1032
|
error ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "gsStatus gsStatus--error", children: [
|
|
986
1033
|
t("error"),
|
package/lib/index.js
CHANGED
|
@@ -38,11 +38,13 @@ var IMAGE_INPUT_MODEL_IDS = Object.freeze([
|
|
|
38
38
|
var IMPORT_TIMEOUT_MS = 2500;
|
|
39
39
|
var CREDENTIALS_IO_TIMEOUT_MS = 1500;
|
|
40
40
|
var RPC_HANDLER_TIMEOUT_MS = 8e3;
|
|
41
|
+
var LOGIN_START_TIMEOUT_MS = 1e4;
|
|
42
|
+
var LOGIN_HANDLER_TIMEOUT_MS = 2e4;
|
|
41
43
|
|
|
42
44
|
// src/session.js
|
|
43
|
-
import { existsSync as
|
|
45
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
44
46
|
import { spawn } from "node:child_process";
|
|
45
|
-
import { join as
|
|
47
|
+
import { join as join4 } from "node:path";
|
|
46
48
|
|
|
47
49
|
// src/auth-file.js
|
|
48
50
|
import { lstatSync, openSync, readFileSync, closeSync, constants as fsConstants } from "node:fs";
|
|
@@ -278,6 +280,142 @@ function buildProxyHeaders(accessToken, options = {}) {
|
|
|
278
280
|
});
|
|
279
281
|
}
|
|
280
282
|
|
|
283
|
+
// src/http-json.js
|
|
284
|
+
import { brotliDecompressSync, gunzipSync, inflateSync, zstdDecompressSync } from "node:zlib";
|
|
285
|
+
var MAGIC_GZIP = [31, 139];
|
|
286
|
+
var MAGIC_ZSTD = [40, 181, 47, 253];
|
|
287
|
+
var DEFAULT_MAX_OUTPUT_BYTES = 8 * 1024 * 1024;
|
|
288
|
+
function startsWith(buffer, bytes) {
|
|
289
|
+
if (buffer.length < bytes.length) return false;
|
|
290
|
+
return bytes.every((byte, index) => buffer[index] === byte);
|
|
291
|
+
}
|
|
292
|
+
function maxOutputOf(options) {
|
|
293
|
+
return typeof options.maxOutputBytes === "number" && options.maxOutputBytes > 0 ? options.maxOutputBytes : DEFAULT_MAX_OUTPUT_BYTES;
|
|
294
|
+
}
|
|
295
|
+
function decodeCompressedBody(buffer, encoding = "", options = {}) {
|
|
296
|
+
const declared = String(encoding ?? "").toLowerCase();
|
|
297
|
+
const kind = declared.includes("gzip") || startsWith(buffer, MAGIC_GZIP) ? "gzip" : declared.includes("zstd") || startsWith(buffer, MAGIC_ZSTD) ? "zstd" : declared.includes("deflate") ? "deflate" : declared.includes("br") ? "br" : void 0;
|
|
298
|
+
if (!kind) return buffer;
|
|
299
|
+
const limits = { maxOutputLength: maxOutputOf(options) };
|
|
300
|
+
try {
|
|
301
|
+
if (kind === "gzip") return gunzipSync(buffer, limits);
|
|
302
|
+
if (kind === "zstd") return zstdDecompressSync(buffer, limits);
|
|
303
|
+
if (kind === "deflate") return inflateSync(buffer, limits);
|
|
304
|
+
return brotliDecompressSync(buffer, limits);
|
|
305
|
+
} catch {
|
|
306
|
+
return buffer;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
async function readResponseText(response, options = {}) {
|
|
310
|
+
if (typeof response.arrayBuffer !== "function") {
|
|
311
|
+
if (typeof response.text === "function") return response.text();
|
|
312
|
+
throw new TypeError("response exposes neither arrayBuffer() nor text()");
|
|
313
|
+
}
|
|
314
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
315
|
+
const encoding = typeof response.headers?.get === "function" ? response.headers.get("content-encoding") ?? "" : "";
|
|
316
|
+
return decodeCompressedBody(buffer, encoding, options).toString("utf8");
|
|
317
|
+
}
|
|
318
|
+
async function readResponseJson(response, options = {}) {
|
|
319
|
+
if (typeof response.arrayBuffer !== "function" && typeof response.json === "function") {
|
|
320
|
+
return response.json();
|
|
321
|
+
}
|
|
322
|
+
return JSON.parse(await readResponseText(response, options));
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// src/proxy.js
|
|
326
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
327
|
+
import { dirname, join as join2 } from "node:path";
|
|
328
|
+
import { pathToFileURL } from "node:url";
|
|
329
|
+
var GROK_API_HOSTS = Object.freeze(["cli-chat-proxy.grok.com"]);
|
|
330
|
+
var HTTP_PROXY_PATTERN = /^https?:\/\//iu;
|
|
331
|
+
var HOST_PROXY_PACKAGE = "@deepseek-ai/dsh-http-proxy";
|
|
332
|
+
var installed;
|
|
333
|
+
var dependencies = { undici: void 0, hostProxies: void 0 };
|
|
334
|
+
function apiProxyUrl(env = process.env) {
|
|
335
|
+
const named = [env?.GROK_API_PROXY, env?.GROK_PROXY];
|
|
336
|
+
for (const candidate of named) {
|
|
337
|
+
if (typeof candidate === "string" && candidate.trim()) return candidate.trim();
|
|
338
|
+
}
|
|
339
|
+
return void 0;
|
|
340
|
+
}
|
|
341
|
+
function hostOf(origin) {
|
|
342
|
+
try {
|
|
343
|
+
return new URL(origin).hostname;
|
|
344
|
+
} catch {
|
|
345
|
+
return "";
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
async function loadHostUndici() {
|
|
349
|
+
let dir = dirname(process.argv[1] ?? "");
|
|
350
|
+
for (let depth = 0; depth < 6 && dir && dir !== dirname(dir); depth++) {
|
|
351
|
+
const entry = join2(dir, "node_modules", "undici", "index.js");
|
|
352
|
+
if (existsSync2(entry)) {
|
|
353
|
+
try {
|
|
354
|
+
return await import(pathToFileURL(entry).href);
|
|
355
|
+
} catch {
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
dir = dirname(dir);
|
|
360
|
+
}
|
|
361
|
+
try {
|
|
362
|
+
return await import("undici");
|
|
363
|
+
} catch {
|
|
364
|
+
return void 0;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
async function hostProxiesGrok() {
|
|
368
|
+
if (typeof dependencies.hostProxies === "function") return dependencies.hostProxies();
|
|
369
|
+
try {
|
|
370
|
+
const mod = await import(HOST_PROXY_PACKAGE);
|
|
371
|
+
const route = mod?.proxyRouteFor?.(`https://${GROK_API_HOSTS[0]}/`);
|
|
372
|
+
return route?.proxied === true;
|
|
373
|
+
} catch {
|
|
374
|
+
return false;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
async function ensureApiRouting(env = process.env) {
|
|
378
|
+
const url = apiProxyUrl(env);
|
|
379
|
+
if (!url || !HTTP_PROXY_PATTERN.test(url)) return "direct";
|
|
380
|
+
if (installed?.url === url) return "plugin";
|
|
381
|
+
if (await hostProxiesGrok()) return "host";
|
|
382
|
+
const undici = dependencies.undici ?? await loadHostUndici();
|
|
383
|
+
const { Agent, Pool, ProxyAgent, getGlobalDispatcher, setGlobalDispatcher } = undici ?? {};
|
|
384
|
+
if (typeof Agent !== "function" || typeof setGlobalDispatcher !== "function") return "direct";
|
|
385
|
+
try {
|
|
386
|
+
const tunnel = new ProxyAgent(url);
|
|
387
|
+
const routing = new Agent({
|
|
388
|
+
factory(origin, options) {
|
|
389
|
+
return GROK_API_HOSTS.includes(hostOf(origin)) ? tunnel : new Pool(origin, options);
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
const previous = typeof getGlobalDispatcher === "function" ? getGlobalDispatcher() : void 0;
|
|
393
|
+
setGlobalDispatcher(routing);
|
|
394
|
+
installed = {
|
|
395
|
+
url,
|
|
396
|
+
dispatcher: routing,
|
|
397
|
+
restore: () => {
|
|
398
|
+
if (previous !== void 0) setGlobalDispatcher(previous);
|
|
399
|
+
installed = void 0;
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
return "plugin";
|
|
403
|
+
} catch {
|
|
404
|
+
return "direct";
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
function resetApiRouting() {
|
|
408
|
+
const current = installed;
|
|
409
|
+
installed = void 0;
|
|
410
|
+
if (!current) return false;
|
|
411
|
+
try {
|
|
412
|
+
current.restore();
|
|
413
|
+
return true;
|
|
414
|
+
} catch {
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
281
419
|
// src/catalog.js
|
|
282
420
|
var REASONING_LEVELS = Object.freeze(["low", "medium", "high", "xhigh"]);
|
|
283
421
|
function asId(value) {
|
|
@@ -400,6 +538,7 @@ function toLlmModels(models) {
|
|
|
400
538
|
});
|
|
401
539
|
}
|
|
402
540
|
async function fetchLiveCatalog(accessToken, options = {}) {
|
|
541
|
+
await ensureApiRouting(options.env);
|
|
403
542
|
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
404
543
|
const signal = options.signal ?? AbortSignal.timeout(options.timeoutMs ?? CATALOG_TIMEOUT_MS);
|
|
405
544
|
const response = await fetchImpl(MODELS_V2_URL, {
|
|
@@ -414,7 +553,7 @@ async function fetchLiveCatalog(accessToken, options = {}) {
|
|
|
414
553
|
if (!response.ok) {
|
|
415
554
|
throw new Error(`Grok models-v2 returned HTTP ${response.status}`);
|
|
416
555
|
}
|
|
417
|
-
const body = await response
|
|
556
|
+
const body = await readResponseJson(response);
|
|
418
557
|
return extractLiveModels(body);
|
|
419
558
|
}
|
|
420
559
|
async function loadCatalog(accessToken, options = {}) {
|
|
@@ -645,6 +784,7 @@ async function fetchBillingUsageOnce(accessToken, options = {}) {
|
|
|
645
784
|
}
|
|
646
785
|
let response;
|
|
647
786
|
try {
|
|
787
|
+
await ensureApiRouting(options.env);
|
|
648
788
|
const signal = options.signal ?? AbortSignal.timeout(options.timeoutMs ?? USAGE_TIMEOUT_MS);
|
|
649
789
|
response = await fetchImpl(BILLING_CREDITS_URL, {
|
|
650
790
|
method: "GET",
|
|
@@ -672,7 +812,7 @@ async function fetchBillingUsageOnce(accessToken, options = {}) {
|
|
|
672
812
|
const looksJson = !contentTypeLower || contentTypeLower.includes("json") || contentTypeLower === "text/plain";
|
|
673
813
|
let text;
|
|
674
814
|
try {
|
|
675
|
-
text = await response
|
|
815
|
+
text = await readResponseText(response);
|
|
676
816
|
} catch {
|
|
677
817
|
return unavailable("Could not read billing body");
|
|
678
818
|
}
|
|
@@ -722,11 +862,11 @@ async function fetchBillingUsage(accessToken, options = {}) {
|
|
|
722
862
|
}
|
|
723
863
|
|
|
724
864
|
// src/adapter.js
|
|
725
|
-
import { existsSync as
|
|
865
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
|
|
726
866
|
import { createRequire } from "node:module";
|
|
727
867
|
import { homedir as homedir2 } from "node:os";
|
|
728
|
-
import { dirname, join as
|
|
729
|
-
import { pathToFileURL } from "node:url";
|
|
868
|
+
import { dirname as dirname2, join as join3 } from "node:path";
|
|
869
|
+
import { pathToFileURL as pathToFileURL2 } from "node:url";
|
|
730
870
|
function withImportTimeout(promise, ms, message) {
|
|
731
871
|
let timer;
|
|
732
872
|
return Promise.race([
|
|
@@ -746,28 +886,28 @@ function hostModuleRoots(env = process.env, argv1 = process.argv[1]) {
|
|
|
746
886
|
const override = env.DSH_GROK_PEER_ROOT;
|
|
747
887
|
if (typeof override === "string" && override.trim()) push(override.trim());
|
|
748
888
|
if (typeof argv1 === "string" && argv1) {
|
|
749
|
-
let dir =
|
|
750
|
-
for (let depth = 0; depth < 6 && dir && dir !==
|
|
751
|
-
push(
|
|
752
|
-
dir =
|
|
889
|
+
let dir = dirname2(argv1);
|
|
890
|
+
for (let depth = 0; depth < 6 && dir && dir !== dirname2(dir); depth++) {
|
|
891
|
+
push(join3(dir, "node_modules"));
|
|
892
|
+
dir = dirname2(dir);
|
|
753
893
|
}
|
|
754
894
|
}
|
|
755
895
|
const prefix = env.NPM_CONFIG_PREFIX;
|
|
756
|
-
if (typeof prefix === "string" && prefix.trim()) push(
|
|
896
|
+
if (typeof prefix === "string" && prefix.trim()) push(join3(prefix.trim(), "lib", "node_modules"));
|
|
757
897
|
if (typeof process.execPath === "string" && process.execPath) {
|
|
758
|
-
push(
|
|
898
|
+
push(join3(dirname2(process.execPath), "..", "lib", "node_modules"));
|
|
759
899
|
}
|
|
760
|
-
push(
|
|
761
|
-
push(
|
|
900
|
+
push(join3("/opt/homebrew", "lib", "node_modules"));
|
|
901
|
+
push(join3("/usr/local", "lib", "node_modules"));
|
|
762
902
|
for (const segment of ["Library/pnpm", ".local/share/pnpm", ".pnpm-global"]) {
|
|
763
|
-
push(
|
|
903
|
+
push(join3(homedir2(), segment, "node_modules"));
|
|
764
904
|
}
|
|
765
905
|
if (typeof env.DSH_PORTABLE_HOME === "string" && env.DSH_PORTABLE_HOME.trim()) {
|
|
766
|
-
push(
|
|
906
|
+
push(join3(env.DSH_PORTABLE_HOME.trim(), "node_modules"));
|
|
767
907
|
}
|
|
768
|
-
push(
|
|
769
|
-
const dshHome = typeof env.DSH_HOME === "string" && env.DSH_HOME.trim() ? env.DSH_HOME.trim() :
|
|
770
|
-
push(
|
|
908
|
+
push(join3(homedir2(), ".local", "lib", "node_modules"));
|
|
909
|
+
const dshHome = typeof env.DSH_HOME === "string" && env.DSH_HOME.trim() ? env.DSH_HOME.trim() : join3(homedir2(), ".dsh");
|
|
910
|
+
push(join3(dshHome, "profiles", "node_modules"));
|
|
771
911
|
return roots;
|
|
772
912
|
}
|
|
773
913
|
function splitSpecifier(specifier) {
|
|
@@ -811,14 +951,14 @@ function resolveExportsTarget(exportsField, subpath) {
|
|
|
811
951
|
function resolveHostSpecifierManually(root, specifier) {
|
|
812
952
|
const { name: name2, subpath } = splitSpecifier(specifier);
|
|
813
953
|
const candidates = [
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
954
|
+
join3(root, name2),
|
|
955
|
+
join3(root, "node_modules", name2),
|
|
956
|
+
join3(root, "@deepseek-ai", "dsh", "node_modules", name2),
|
|
957
|
+
join3(root, "node_modules", "@deepseek-ai", "dsh", "node_modules", name2)
|
|
818
958
|
];
|
|
819
959
|
for (const pkgDir of candidates) {
|
|
820
|
-
const manifest =
|
|
821
|
-
if (!
|
|
960
|
+
const manifest = join3(pkgDir, "package.json");
|
|
961
|
+
if (!existsSync3(manifest)) continue;
|
|
822
962
|
let pkg;
|
|
823
963
|
try {
|
|
824
964
|
pkg = JSON.parse(readFileSync3(manifest, "utf8"));
|
|
@@ -827,15 +967,15 @@ function resolveHostSpecifierManually(root, specifier) {
|
|
|
827
967
|
}
|
|
828
968
|
const target = pkg.exports !== void 0 ? resolveExportsTarget(pkg.exports, subpath) : subpath === "." ? pkg.module ?? pkg.main ?? "index.js" : void 0;
|
|
829
969
|
if (typeof target !== "string" || !target) continue;
|
|
830
|
-
const file =
|
|
831
|
-
if (
|
|
970
|
+
const file = join3(pkgDir, target);
|
|
971
|
+
if (existsSync3(file)) return file;
|
|
832
972
|
}
|
|
833
973
|
return void 0;
|
|
834
974
|
}
|
|
835
975
|
function resolveHostSpecifier(specifier, roots = hostModuleRoots()) {
|
|
836
976
|
for (const root of roots) {
|
|
837
977
|
try {
|
|
838
|
-
const resolved = createRequire(
|
|
978
|
+
const resolved = createRequire(join3(root, "noop.js")).resolve(specifier);
|
|
839
979
|
if (typeof resolved === "string" && resolved) return resolved;
|
|
840
980
|
} catch {
|
|
841
981
|
}
|
|
@@ -851,7 +991,7 @@ async function optionalImport(specifier, options = {}) {
|
|
|
851
991
|
const candidates = [specifier];
|
|
852
992
|
if (!injected && !specifier.startsWith(".") && !specifier.startsWith("/") && !specifier.startsWith("file:")) {
|
|
853
993
|
const resolved = resolveHostSpecifier(specifier, options.hostRoots ?? hostModuleRoots());
|
|
854
|
-
if (resolved) candidates.push(
|
|
994
|
+
if (resolved) candidates.push(pathToFileURL2(resolved).href);
|
|
855
995
|
}
|
|
856
996
|
for (const candidate of candidates) {
|
|
857
997
|
try {
|
|
@@ -1066,6 +1206,7 @@ function jsonSafeChunk(value) {
|
|
|
1066
1206
|
return out;
|
|
1067
1207
|
}
|
|
1068
1208
|
async function* streamResponsesUnsafe(options, token) {
|
|
1209
|
+
await ensureApiRouting();
|
|
1069
1210
|
const headers = {
|
|
1070
1211
|
Accept: "text/event-stream",
|
|
1071
1212
|
"Content-Type": "application/json",
|
|
@@ -1501,6 +1642,7 @@ async function createGrokBuildAdapter(session, options = {}) {
|
|
|
1501
1642
|
if (!responsesApi) {
|
|
1502
1643
|
return { adapter: asHostAdapter(duck), kind: "custom-mvp", note: "openai-responses API module is unavailable; using the custom adapter." };
|
|
1503
1644
|
}
|
|
1645
|
+
await ensureApiRouting();
|
|
1504
1646
|
responsesApi = withEncryptedReasoningInclude(responsesApi);
|
|
1505
1647
|
const store = createStore(session);
|
|
1506
1648
|
const authModels = piAi.createModels({
|
|
@@ -1592,32 +1734,139 @@ async function createGrokBuildAdapter(session, options = {}) {
|
|
|
1592
1734
|
}
|
|
1593
1735
|
|
|
1594
1736
|
// src/session.js
|
|
1595
|
-
function resolveGrokBin(env = process.env, exists =
|
|
1737
|
+
function resolveGrokBin(env = process.env, exists = existsSync4) {
|
|
1596
1738
|
if (typeof env.DSH_GROK_BIN === "string" && env.DSH_GROK_BIN.trim()) return env.DSH_GROK_BIN.trim();
|
|
1597
|
-
const local =
|
|
1739
|
+
const local = join4(grokHome(env), "bin", "grok");
|
|
1598
1740
|
if (exists(local)) return local;
|
|
1599
1741
|
return "grok";
|
|
1600
1742
|
}
|
|
1601
|
-
function grokCliAvailable(env = process.env, exists =
|
|
1743
|
+
function grokCliAvailable(env = process.env, exists = existsSync4) {
|
|
1602
1744
|
const bin = resolveGrokBin(env, exists);
|
|
1603
1745
|
if (bin.includes("/") || bin.includes("\\")) return exists(bin);
|
|
1604
1746
|
const delimiter = process.platform === "win32" ? ";" : ":";
|
|
1605
|
-
return String(env.PATH ?? "").split(delimiter).some((dir) => dir && exists(
|
|
1747
|
+
return String(env.PATH ?? "").split(delimiter).some((dir) => dir && exists(join4(dir, bin)));
|
|
1748
|
+
}
|
|
1749
|
+
var STANDARD_PROXY_NAMES = [
|
|
1750
|
+
"http_proxy",
|
|
1751
|
+
"https_proxy",
|
|
1752
|
+
"all_proxy",
|
|
1753
|
+
"no_proxy",
|
|
1754
|
+
"HTTP_PROXY",
|
|
1755
|
+
"HTTPS_PROXY",
|
|
1756
|
+
"ALL_PROXY",
|
|
1757
|
+
"NO_PROXY"
|
|
1758
|
+
];
|
|
1759
|
+
function cliProxyEnv(env = process.env) {
|
|
1760
|
+
const base = env ?? process.env;
|
|
1761
|
+
const declared = STANDARD_PROXY_NAMES.some((name2) => typeof base[name2] === "string" && base[name2].trim() !== "");
|
|
1762
|
+
if (declared) return base;
|
|
1763
|
+
const named = [base.GROK_CLI_PROXY, base.GROK_PROXY].find((value) => typeof value === "string" && value.trim() !== "");
|
|
1764
|
+
const proxy = typeof named === "string" ? named.trim() : "";
|
|
1765
|
+
if (!proxy) return base;
|
|
1766
|
+
const bypass = typeof base.GROK_CLI_NO_PROXY === "string" && base.GROK_CLI_NO_PROXY.trim() ? base.GROK_CLI_NO_PROXY.trim() : "localhost,127.0.0.1,::1";
|
|
1767
|
+
const httpish = /^https?:\/\//iu.test(proxy);
|
|
1768
|
+
return {
|
|
1769
|
+
...base,
|
|
1770
|
+
...httpish ? { http_proxy: proxy, https_proxy: proxy } : {},
|
|
1771
|
+
all_proxy: proxy,
|
|
1772
|
+
no_proxy: bypass
|
|
1773
|
+
};
|
|
1606
1774
|
}
|
|
1775
|
+
function openExternal(url, options = {}) {
|
|
1776
|
+
const spawnFn = options.spawn ?? spawn;
|
|
1777
|
+
const platform = options.platform ?? process.platform;
|
|
1778
|
+
const [command, args] = platform === "darwin" ? ["open", [url]] : platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
|
|
1779
|
+
return new Promise((resolve) => {
|
|
1780
|
+
let child;
|
|
1781
|
+
try {
|
|
1782
|
+
child = spawnFn(command, args, { stdio: "ignore", detached: false });
|
|
1783
|
+
} catch {
|
|
1784
|
+
resolve(false);
|
|
1785
|
+
return;
|
|
1786
|
+
}
|
|
1787
|
+
let settled = false;
|
|
1788
|
+
const finish = (value) => {
|
|
1789
|
+
if (settled) return;
|
|
1790
|
+
settled = true;
|
|
1791
|
+
resolve(value);
|
|
1792
|
+
};
|
|
1793
|
+
child.on?.("error", () => finish(false));
|
|
1794
|
+
child.on?.("exit", (code) => finish(code === 0));
|
|
1795
|
+
child.unref?.();
|
|
1796
|
+
});
|
|
1797
|
+
}
|
|
1798
|
+
var LOGIN_URL_PATTERN = /https?:\/\/[^\s<>"'`\\]+/u;
|
|
1799
|
+
var DEVICE_CODE_PATTERN = /\b([A-Z0-9]{4}-[A-Z0-9]{4})\b/u;
|
|
1607
1800
|
function spawnGrokLogin(options = {}) {
|
|
1608
1801
|
const spawnFn = options.spawn ?? spawn;
|
|
1609
1802
|
const bin = resolveGrokBin(options.env, options.exists);
|
|
1610
1803
|
const args = options.device ? ["login", "--device-auth"] : ["login"];
|
|
1804
|
+
const startTimeoutMs = typeof options.startTimeoutMs === "number" && options.startTimeoutMs > 0 ? options.startTimeoutMs : LOGIN_START_TIMEOUT_MS;
|
|
1805
|
+
const openUrl = options.openUrl ?? ((url) => openExternal(url, { spawn: options.openSpawn }));
|
|
1611
1806
|
return new Promise((resolve, reject) => {
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1807
|
+
let child;
|
|
1808
|
+
try {
|
|
1809
|
+
child = spawnFn(bin, args, {
|
|
1810
|
+
stdio: options.stdio ?? ["ignore", "pipe", "pipe"],
|
|
1811
|
+
env: cliProxyEnv(options.env)
|
|
1812
|
+
});
|
|
1813
|
+
} catch (error) {
|
|
1814
|
+
reject(new Error(`Could not start grok CLI (${bin})`, { cause: error }));
|
|
1815
|
+
return;
|
|
1816
|
+
}
|
|
1817
|
+
let settled = false;
|
|
1818
|
+
let loginUrl;
|
|
1819
|
+
let userCode;
|
|
1820
|
+
let buffer = "";
|
|
1821
|
+
let timer;
|
|
1822
|
+
const succeed = (value) => {
|
|
1823
|
+
if (settled) return;
|
|
1824
|
+
settled = true;
|
|
1825
|
+
if (timer) clearTimeout(timer);
|
|
1826
|
+
resolve(value);
|
|
1827
|
+
};
|
|
1828
|
+
const scan = (text) => {
|
|
1829
|
+
if (text) buffer = `${buffer}${text}`.slice(-8192);
|
|
1830
|
+
if (!userCode) {
|
|
1831
|
+
const code = DEVICE_CODE_PATTERN.exec(buffer);
|
|
1832
|
+
if (code) userCode = code[1];
|
|
1833
|
+
}
|
|
1834
|
+
if (loginUrl) return;
|
|
1835
|
+
const match = LOGIN_URL_PATTERN.exec(buffer);
|
|
1836
|
+
if (!match) return;
|
|
1837
|
+
loginUrl = match[0];
|
|
1838
|
+
try {
|
|
1839
|
+
void openUrl(loginUrl);
|
|
1840
|
+
} catch {
|
|
1841
|
+
}
|
|
1842
|
+
succeed({ ok: true, pending: true, loginUrl, userCode, child });
|
|
1843
|
+
};
|
|
1844
|
+
const piped = Boolean(child.stdout && child.stderr);
|
|
1845
|
+
if (piped) {
|
|
1846
|
+
child.stdout.on("data", (chunk) => scan(String(chunk)));
|
|
1847
|
+
child.stderr.on("data", (chunk) => scan(String(chunk)));
|
|
1848
|
+
timer = setTimeout(() => {
|
|
1849
|
+
succeed({
|
|
1850
|
+
ok: true,
|
|
1851
|
+
pending: true,
|
|
1852
|
+
loginUrl,
|
|
1853
|
+
userCode,
|
|
1854
|
+
child,
|
|
1855
|
+
warning: `grok login printed no sign-in URL within ${startTimeoutMs}ms`
|
|
1856
|
+
});
|
|
1857
|
+
}, startTimeoutMs);
|
|
1858
|
+
}
|
|
1616
1859
|
child.on("error", (error) => {
|
|
1860
|
+
if (settled) return;
|
|
1861
|
+
settled = true;
|
|
1862
|
+
if (timer) clearTimeout(timer);
|
|
1617
1863
|
reject(new Error(`Could not start grok CLI (${bin})`, { cause: error }));
|
|
1618
1864
|
});
|
|
1619
1865
|
child.on("exit", (code, signal) => {
|
|
1620
|
-
if (
|
|
1866
|
+
if (settled) return;
|
|
1867
|
+
settled = true;
|
|
1868
|
+
if (timer) clearTimeout(timer);
|
|
1869
|
+
if (code === 0) resolve({ ok: true, pending: false, loginUrl, userCode, child });
|
|
1621
1870
|
else reject(new Error(signal ? `grok login terminated by ${signal}` : `grok login exited with code ${code ?? "unknown"}`));
|
|
1622
1871
|
});
|
|
1623
1872
|
});
|
|
@@ -1631,7 +1880,7 @@ function spawnGrokRefresh(options = {}) {
|
|
|
1631
1880
|
try {
|
|
1632
1881
|
child = spawnFn(bin, ["models"], {
|
|
1633
1882
|
stdio: options.stdio ?? "ignore",
|
|
1634
|
-
env: options.env
|
|
1883
|
+
env: cliProxyEnv(options.env)
|
|
1635
1884
|
});
|
|
1636
1885
|
} catch (error) {
|
|
1637
1886
|
reject(new Error(`Could not start grok CLI (${bin})`, { cause: error }));
|
|
@@ -1940,8 +2189,27 @@ function createSessionService({
|
|
|
1940
2189
|
return { account: lastPublic, catalog, usage: lastUsage, cliAvailable: grokCliAvailable(), authPath: authJsonPath() };
|
|
1941
2190
|
};
|
|
1942
2191
|
const login = async (options) => {
|
|
1943
|
-
await spawnGrokLogin(options);
|
|
1944
|
-
|
|
2192
|
+
const started = await spawnGrokLogin(options);
|
|
2193
|
+
const extra = {
|
|
2194
|
+
...started.loginUrl === void 0 ? {} : { loginUrl: started.loginUrl },
|
|
2195
|
+
...started.userCode === void 0 ? {} : { userCode: started.userCode },
|
|
2196
|
+
...started.warning === void 0 ? {} : { warning: started.warning }
|
|
2197
|
+
};
|
|
2198
|
+
if (started.pending) {
|
|
2199
|
+
started.child?.once?.("exit", (code) => {
|
|
2200
|
+
if (code !== 0) return;
|
|
2201
|
+
scheduleDeferred(() => {
|
|
2202
|
+
void pull().catch((error) => {
|
|
2203
|
+
logger?.warn?.(
|
|
2204
|
+
"Grok subscription login sync failed: %s",
|
|
2205
|
+
error instanceof Error ? error.message : "unknown"
|
|
2206
|
+
);
|
|
2207
|
+
});
|
|
2208
|
+
});
|
|
2209
|
+
});
|
|
2210
|
+
return { ok: true, pending: true, ...extra, ...await status() };
|
|
2211
|
+
}
|
|
2212
|
+
return { ...await pull(), pending: false, ...extra };
|
|
1945
2213
|
};
|
|
1946
2214
|
const logout = async () => {
|
|
1947
2215
|
memoryAccessToken = void 0;
|
|
@@ -1979,7 +2247,7 @@ function createSessionService({
|
|
|
1979
2247
|
import { spawn as spawn2 } from "node:child_process";
|
|
1980
2248
|
import { readdir, readFile, realpath } from "node:fs/promises";
|
|
1981
2249
|
import { homedir as homedir3 } from "node:os";
|
|
1982
|
-
import { join as
|
|
2250
|
+
import { join as join5 } from "node:path";
|
|
1983
2251
|
import { fileURLToPath } from "node:url";
|
|
1984
2252
|
var PACKAGE_NAME = PLUGIN_ID;
|
|
1985
2253
|
var NPM_REGISTRY_LATEST_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
@@ -2028,21 +2296,21 @@ async function findInstall({ dshHome, ownPackageJsonUrl }) {
|
|
|
2028
2296
|
}
|
|
2029
2297
|
let entries = [];
|
|
2030
2298
|
try {
|
|
2031
|
-
entries = await readdir(
|
|
2299
|
+
entries = await readdir(join5(dshHome, "profiles"), { withFileTypes: true });
|
|
2032
2300
|
} catch {
|
|
2033
2301
|
entries = [];
|
|
2034
2302
|
}
|
|
2035
2303
|
const declared = [];
|
|
2036
2304
|
for (const entry of entries) {
|
|
2037
2305
|
if (!entry.isDirectory()) continue;
|
|
2038
|
-
const profileDir =
|
|
2039
|
-
const spec = (await readManifest(
|
|
2306
|
+
const profileDir = join5(dshHome, "profiles", entry.name);
|
|
2307
|
+
const spec = (await readManifest(join5(profileDir, "package.json")))?.dependencies?.[PACKAGE_NAME];
|
|
2040
2308
|
if (typeof spec !== "string") continue;
|
|
2041
2309
|
declared.push({ profile: entry.name, spec });
|
|
2042
2310
|
if (ownReal === void 0) continue;
|
|
2043
2311
|
try {
|
|
2044
|
-
const
|
|
2045
|
-
if (
|
|
2312
|
+
const installed2 = await realpath(join5(profileDir, "node_modules", PACKAGE_NAME, "package.json"));
|
|
2313
|
+
if (installed2 === ownReal) return { kind: classifySpec(spec), profile: entry.name };
|
|
2046
2314
|
} catch {
|
|
2047
2315
|
}
|
|
2048
2316
|
}
|
|
@@ -2108,7 +2376,7 @@ function createGrokPluginManager({
|
|
|
2108
2376
|
} = {}) {
|
|
2109
2377
|
if (typeof fetchImpl !== "function") throw new Error("Grok plugin manager requires fetch");
|
|
2110
2378
|
if (typeof runCommand !== "function") throw new Error("Grok plugin manager requires runCommand");
|
|
2111
|
-
const dshHome = typeof env.DSH_HOME === "string" && env.DSH_HOME !== "" ? env.DSH_HOME :
|
|
2379
|
+
const dshHome = typeof env.DSH_HOME === "string" && env.DSH_HOME !== "" ? env.DSH_HOME : join5(homedir3(), ".dsh");
|
|
2112
2380
|
let cached;
|
|
2113
2381
|
let inFlight;
|
|
2114
2382
|
let generation = 0;
|
|
@@ -2130,7 +2398,7 @@ function createGrokPluginManager({
|
|
|
2130
2398
|
if (!response.ok) throw new Error("Grok plugin version check failed");
|
|
2131
2399
|
let version;
|
|
2132
2400
|
try {
|
|
2133
|
-
version = (await response
|
|
2401
|
+
version = (await readResponseJson(response))?.version;
|
|
2134
2402
|
} catch {
|
|
2135
2403
|
version = void 0;
|
|
2136
2404
|
}
|
|
@@ -2357,14 +2625,16 @@ async function dispatch(session, endpoint, diagnostics, payload, signal, pluginM
|
|
|
2357
2625
|
}
|
|
2358
2626
|
function createRpcHandler(session, options = {}) {
|
|
2359
2627
|
const timeoutMs = typeof options.timeoutMs === "number" && options.timeoutMs > 0 ? options.timeoutMs : RPC_HANDLER_TIMEOUT_MS;
|
|
2628
|
+
const loginTimeoutMs = typeof options.loginTimeoutMs === "number" && options.loginTimeoutMs > 0 ? options.loginTimeoutMs : LOGIN_HANDLER_TIMEOUT_MS;
|
|
2360
2629
|
return async function handle(endpoint, payload, signal) {
|
|
2361
2630
|
try {
|
|
2362
2631
|
const operation = dispatch(session, endpoint, options.diagnostics, payload, signal, options.pluginManager);
|
|
2363
2632
|
if (endpoint === "plugin/update") return await operation;
|
|
2633
|
+
const ceiling = endpoint === "login/cli" || endpoint === "login/device" ? loginTimeoutMs : timeoutMs;
|
|
2364
2634
|
return await withTimeout(
|
|
2365
2635
|
operation,
|
|
2366
|
-
|
|
2367
|
-
`Grok subscription RPC "${endpoint}" timed out after ${
|
|
2636
|
+
ceiling,
|
|
2637
|
+
`Grok subscription RPC "${endpoint}" timed out after ${ceiling}ms`,
|
|
2368
2638
|
{ unref: false }
|
|
2369
2639
|
);
|
|
2370
2640
|
} catch (error) {
|
|
@@ -2490,6 +2760,18 @@ function apply(ctx, options = {}) {
|
|
|
2490
2760
|
"grok-subscription: startup lifetime"
|
|
2491
2761
|
);
|
|
2492
2762
|
}
|
|
2763
|
+
void ensureApiRouting().then((mode) => {
|
|
2764
|
+
if (mode === "direct") return;
|
|
2765
|
+
ctx.logger?.debug?.("Grok subscription API route: %s", mode);
|
|
2766
|
+
}).catch((error) => {
|
|
2767
|
+
ctx.logger?.debug?.(
|
|
2768
|
+
"Grok subscription API route skipped: %s",
|
|
2769
|
+
error instanceof Error ? error.message : "unknown"
|
|
2770
|
+
);
|
|
2771
|
+
});
|
|
2772
|
+
if (typeof ctx.effect === "function") {
|
|
2773
|
+
ctx.effect(() => () => resetApiRouting(), "grok-subscription: API proxy route");
|
|
2774
|
+
}
|
|
2493
2775
|
const notifyCatalogChange = () => {
|
|
2494
2776
|
scheduleDeferred2(() => {
|
|
2495
2777
|
if (!active) return;
|