dsh-grok-subscription 1.0.11 → 1.0.19
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 -63
- 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 only to **this plugin's own Grok requests** (the host undici's `fetch`; the process-wide dispatcher is never replaced) and to the **`grok` CLI** it spawns. Every other plugin, DSH itself, and every other provider keep the host's original `fetch`. To proxy the whole Harness, set the standard `https_proxy` (in `~/.dsh/.env`, never in a project `.env`): `@deepseek-ai/dsh-http-proxy` takes over and this plugin adds no tunnel of its own. 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.
|
|
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 请求**(走宿主 undici 的 fetch,不替换进程的全局调度器)和它启动的 **`grok` CLI**。其他插件、DSH 自身和其他 provider 继续用宿主原来的 `fetch`。想让整个 Harness 走代理时,用标准的 `https_proxy`(可写在 `~/.dsh/.env`,不要写进项目目录),宿主的 `@deepseek-ai/dsh-http-proxy` 会接管,本插件不再另加隧道。写好后重启客户端;登录链接会交给系统浏览器打开,面板里同时显示一次性验证码与可点链接。
|
|
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.19",
|
|
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.19",
|
|
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,131 @@ 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 HTTP_PROXY_PATTERN = /^https?:\/\//iu;
|
|
330
|
+
var HOST_PROXY_PACKAGE = "@deepseek-ai/dsh-http-proxy";
|
|
331
|
+
var GROK_ORIGIN = "https://cli-chat-proxy.grok.com/";
|
|
332
|
+
var cached;
|
|
333
|
+
var dependencies = { undici: void 0, hostProxies: void 0, hostProxyEnv: 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
|
+
async function loadHostUndici() {
|
|
342
|
+
let dir = dirname(process.argv[1] ?? "");
|
|
343
|
+
for (let depth = 0; depth < 6 && dir && dir !== dirname(dir); depth++) {
|
|
344
|
+
const entry = join2(dir, "node_modules", "undici", "index.js");
|
|
345
|
+
if (existsSync2(entry)) {
|
|
346
|
+
try {
|
|
347
|
+
return await import(pathToFileURL(entry).href);
|
|
348
|
+
} catch {
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
dir = dirname(dir);
|
|
353
|
+
}
|
|
354
|
+
try {
|
|
355
|
+
return await import("undici");
|
|
356
|
+
} catch {
|
|
357
|
+
return void 0;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
async function hostProxyModule() {
|
|
361
|
+
try {
|
|
362
|
+
return await import(HOST_PROXY_PACKAGE);
|
|
363
|
+
} catch {
|
|
364
|
+
return void 0;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
async function hostAlreadyProxiesGrok() {
|
|
368
|
+
if (typeof dependencies.hostProxies === "function") return dependencies.hostProxies();
|
|
369
|
+
const mod = await hostProxyModule();
|
|
370
|
+
try {
|
|
371
|
+
return mod?.proxyRouteFor?.(GROK_ORIGIN)?.proxied === true;
|
|
372
|
+
} catch {
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
var CHILD_PROXY_NAMES = ["http_proxy", "https_proxy", "all_proxy", "HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY"];
|
|
377
|
+
function proxyOverlay(value) {
|
|
378
|
+
if (!value || typeof value !== "object") return void 0;
|
|
379
|
+
return CHILD_PROXY_NAMES.some((name2) => typeof value[name2] === "string" && value[name2].trim()) ? value : void 0;
|
|
380
|
+
}
|
|
381
|
+
async function hostProxyEnvironment() {
|
|
382
|
+
if (typeof dependencies.hostProxyEnv === "function") return proxyOverlay(dependencies.hostProxyEnv());
|
|
383
|
+
const mod = await hostProxyModule();
|
|
384
|
+
try {
|
|
385
|
+
const overlay = typeof mod?.proxyEnvironmentForChild === "function" ? mod.proxyEnvironmentForChild() : void 0;
|
|
386
|
+
return proxyOverlay(overlay);
|
|
387
|
+
} catch {
|
|
388
|
+
return void 0;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
async function grokFetch(env = process.env) {
|
|
392
|
+
const url = apiProxyUrl(env);
|
|
393
|
+
if (!url || !HTTP_PROXY_PATTERN.test(url)) return void 0;
|
|
394
|
+
if (await hostAlreadyProxiesGrok()) return void 0;
|
|
395
|
+
if (cached?.url === url) return cached.fetch;
|
|
396
|
+
const undici = dependencies.undici ?? await loadHostUndici();
|
|
397
|
+
if (typeof undici?.fetch !== "function" || typeof undici?.ProxyAgent !== "function") return void 0;
|
|
398
|
+
try {
|
|
399
|
+
const dispatcher = new undici.ProxyAgent(url);
|
|
400
|
+
const fetchImpl = (input, init) => undici.fetch(input, { ...init, dispatcher });
|
|
401
|
+
cached = { url, fetch: fetchImpl };
|
|
402
|
+
return fetchImpl;
|
|
403
|
+
} catch {
|
|
404
|
+
return void 0;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
281
408
|
// src/catalog.js
|
|
282
409
|
var REASONING_LEVELS = Object.freeze(["low", "medium", "high", "xhigh"]);
|
|
283
410
|
function asId(value) {
|
|
@@ -400,7 +527,7 @@ function toLlmModels(models) {
|
|
|
400
527
|
});
|
|
401
528
|
}
|
|
402
529
|
async function fetchLiveCatalog(accessToken, options = {}) {
|
|
403
|
-
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
530
|
+
const fetchImpl = options.fetch ?? await grokFetch(options.env) ?? globalThis.fetch;
|
|
404
531
|
const signal = options.signal ?? AbortSignal.timeout(options.timeoutMs ?? CATALOG_TIMEOUT_MS);
|
|
405
532
|
const response = await fetchImpl(MODELS_V2_URL, {
|
|
406
533
|
method: "GET",
|
|
@@ -414,7 +541,7 @@ async function fetchLiveCatalog(accessToken, options = {}) {
|
|
|
414
541
|
if (!response.ok) {
|
|
415
542
|
throw new Error(`Grok models-v2 returned HTTP ${response.status}`);
|
|
416
543
|
}
|
|
417
|
-
const body = await response
|
|
544
|
+
const body = await readResponseJson(response);
|
|
418
545
|
return extractLiveModels(body);
|
|
419
546
|
}
|
|
420
547
|
async function loadCatalog(accessToken, options = {}) {
|
|
@@ -646,7 +773,8 @@ async function fetchBillingUsageOnce(accessToken, options = {}) {
|
|
|
646
773
|
let response;
|
|
647
774
|
try {
|
|
648
775
|
const signal = options.signal ?? AbortSignal.timeout(options.timeoutMs ?? USAGE_TIMEOUT_MS);
|
|
649
|
-
|
|
776
|
+
const tunnelFetch = options.fetch ? void 0 : await grokFetch(options.env);
|
|
777
|
+
response = await (tunnelFetch ?? fetchImpl)(BILLING_CREDITS_URL, {
|
|
650
778
|
method: "GET",
|
|
651
779
|
headers: {
|
|
652
780
|
Accept: "application/json",
|
|
@@ -672,7 +800,7 @@ async function fetchBillingUsageOnce(accessToken, options = {}) {
|
|
|
672
800
|
const looksJson = !contentTypeLower || contentTypeLower.includes("json") || contentTypeLower === "text/plain";
|
|
673
801
|
let text;
|
|
674
802
|
try {
|
|
675
|
-
text = await response
|
|
803
|
+
text = await readResponseText(response);
|
|
676
804
|
} catch {
|
|
677
805
|
return unavailable("Could not read billing body");
|
|
678
806
|
}
|
|
@@ -722,11 +850,11 @@ async function fetchBillingUsage(accessToken, options = {}) {
|
|
|
722
850
|
}
|
|
723
851
|
|
|
724
852
|
// src/adapter.js
|
|
725
|
-
import { existsSync as
|
|
853
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
|
|
726
854
|
import { createRequire } from "node:module";
|
|
727
855
|
import { homedir as homedir2 } from "node:os";
|
|
728
|
-
import { dirname, join as
|
|
729
|
-
import { pathToFileURL } from "node:url";
|
|
856
|
+
import { dirname as dirname2, join as join3 } from "node:path";
|
|
857
|
+
import { pathToFileURL as pathToFileURL2 } from "node:url";
|
|
730
858
|
function withImportTimeout(promise, ms, message) {
|
|
731
859
|
let timer;
|
|
732
860
|
return Promise.race([
|
|
@@ -746,28 +874,28 @@ function hostModuleRoots(env = process.env, argv1 = process.argv[1]) {
|
|
|
746
874
|
const override = env.DSH_GROK_PEER_ROOT;
|
|
747
875
|
if (typeof override === "string" && override.trim()) push(override.trim());
|
|
748
876
|
if (typeof argv1 === "string" && argv1) {
|
|
749
|
-
let dir =
|
|
750
|
-
for (let depth = 0; depth < 6 && dir && dir !==
|
|
751
|
-
push(
|
|
752
|
-
dir =
|
|
877
|
+
let dir = dirname2(argv1);
|
|
878
|
+
for (let depth = 0; depth < 6 && dir && dir !== dirname2(dir); depth++) {
|
|
879
|
+
push(join3(dir, "node_modules"));
|
|
880
|
+
dir = dirname2(dir);
|
|
753
881
|
}
|
|
754
882
|
}
|
|
755
883
|
const prefix = env.NPM_CONFIG_PREFIX;
|
|
756
|
-
if (typeof prefix === "string" && prefix.trim()) push(
|
|
884
|
+
if (typeof prefix === "string" && prefix.trim()) push(join3(prefix.trim(), "lib", "node_modules"));
|
|
757
885
|
if (typeof process.execPath === "string" && process.execPath) {
|
|
758
|
-
push(
|
|
886
|
+
push(join3(dirname2(process.execPath), "..", "lib", "node_modules"));
|
|
759
887
|
}
|
|
760
|
-
push(
|
|
761
|
-
push(
|
|
888
|
+
push(join3("/opt/homebrew", "lib", "node_modules"));
|
|
889
|
+
push(join3("/usr/local", "lib", "node_modules"));
|
|
762
890
|
for (const segment of ["Library/pnpm", ".local/share/pnpm", ".pnpm-global"]) {
|
|
763
|
-
push(
|
|
891
|
+
push(join3(homedir2(), segment, "node_modules"));
|
|
764
892
|
}
|
|
765
893
|
if (typeof env.DSH_PORTABLE_HOME === "string" && env.DSH_PORTABLE_HOME.trim()) {
|
|
766
|
-
push(
|
|
894
|
+
push(join3(env.DSH_PORTABLE_HOME.trim(), "node_modules"));
|
|
767
895
|
}
|
|
768
|
-
push(
|
|
769
|
-
const dshHome = typeof env.DSH_HOME === "string" && env.DSH_HOME.trim() ? env.DSH_HOME.trim() :
|
|
770
|
-
push(
|
|
896
|
+
push(join3(homedir2(), ".local", "lib", "node_modules"));
|
|
897
|
+
const dshHome = typeof env.DSH_HOME === "string" && env.DSH_HOME.trim() ? env.DSH_HOME.trim() : join3(homedir2(), ".dsh");
|
|
898
|
+
push(join3(dshHome, "profiles", "node_modules"));
|
|
771
899
|
return roots;
|
|
772
900
|
}
|
|
773
901
|
function splitSpecifier(specifier) {
|
|
@@ -811,14 +939,14 @@ function resolveExportsTarget(exportsField, subpath) {
|
|
|
811
939
|
function resolveHostSpecifierManually(root, specifier) {
|
|
812
940
|
const { name: name2, subpath } = splitSpecifier(specifier);
|
|
813
941
|
const candidates = [
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
942
|
+
join3(root, name2),
|
|
943
|
+
join3(root, "node_modules", name2),
|
|
944
|
+
join3(root, "@deepseek-ai", "dsh", "node_modules", name2),
|
|
945
|
+
join3(root, "node_modules", "@deepseek-ai", "dsh", "node_modules", name2)
|
|
818
946
|
];
|
|
819
947
|
for (const pkgDir of candidates) {
|
|
820
|
-
const manifest =
|
|
821
|
-
if (!
|
|
948
|
+
const manifest = join3(pkgDir, "package.json");
|
|
949
|
+
if (!existsSync3(manifest)) continue;
|
|
822
950
|
let pkg;
|
|
823
951
|
try {
|
|
824
952
|
pkg = JSON.parse(readFileSync3(manifest, "utf8"));
|
|
@@ -827,15 +955,15 @@ function resolveHostSpecifierManually(root, specifier) {
|
|
|
827
955
|
}
|
|
828
956
|
const target = pkg.exports !== void 0 ? resolveExportsTarget(pkg.exports, subpath) : subpath === "." ? pkg.module ?? pkg.main ?? "index.js" : void 0;
|
|
829
957
|
if (typeof target !== "string" || !target) continue;
|
|
830
|
-
const file =
|
|
831
|
-
if (
|
|
958
|
+
const file = join3(pkgDir, target);
|
|
959
|
+
if (existsSync3(file)) return file;
|
|
832
960
|
}
|
|
833
961
|
return void 0;
|
|
834
962
|
}
|
|
835
963
|
function resolveHostSpecifier(specifier, roots = hostModuleRoots()) {
|
|
836
964
|
for (const root of roots) {
|
|
837
965
|
try {
|
|
838
|
-
const resolved = createRequire(
|
|
966
|
+
const resolved = createRequire(join3(root, "noop.js")).resolve(specifier);
|
|
839
967
|
if (typeof resolved === "string" && resolved) return resolved;
|
|
840
968
|
} catch {
|
|
841
969
|
}
|
|
@@ -851,7 +979,7 @@ async function optionalImport(specifier, options = {}) {
|
|
|
851
979
|
const candidates = [specifier];
|
|
852
980
|
if (!injected && !specifier.startsWith(".") && !specifier.startsWith("/") && !specifier.startsWith("file:")) {
|
|
853
981
|
const resolved = resolveHostSpecifier(specifier, options.hostRoots ?? hostModuleRoots());
|
|
854
|
-
if (resolved) candidates.push(
|
|
982
|
+
if (resolved) candidates.push(pathToFileURL2(resolved).href);
|
|
855
983
|
}
|
|
856
984
|
for (const candidate of candidates) {
|
|
857
985
|
try {
|
|
@@ -1066,6 +1194,7 @@ function jsonSafeChunk(value) {
|
|
|
1066
1194
|
return out;
|
|
1067
1195
|
}
|
|
1068
1196
|
async function* streamResponsesUnsafe(options, token) {
|
|
1197
|
+
const fetchImpl = await grokFetch() ?? globalThis.fetch;
|
|
1069
1198
|
const headers = {
|
|
1070
1199
|
Accept: "text/event-stream",
|
|
1071
1200
|
"Content-Type": "application/json",
|
|
@@ -1091,7 +1220,7 @@ async function* streamResponsesUnsafe(options, token) {
|
|
|
1091
1220
|
const tools = cacheStableTools(options.tools);
|
|
1092
1221
|
if (tools.length) body.tools = tools;
|
|
1093
1222
|
if (options.reasoningEffort) body.reasoning = { effort: options.reasoningEffort };
|
|
1094
|
-
const response = await
|
|
1223
|
+
const response = await fetchImpl(RESPONSES_URL, {
|
|
1095
1224
|
method: "POST",
|
|
1096
1225
|
headers,
|
|
1097
1226
|
body: JSON.stringify(body),
|
|
@@ -1429,9 +1558,10 @@ function buildAuthConfig() {
|
|
|
1429
1558
|
}
|
|
1430
1559
|
};
|
|
1431
1560
|
}
|
|
1432
|
-
function withEncryptedReasoningInclude(api) {
|
|
1561
|
+
function withEncryptedReasoningInclude(api, fetchImpl) {
|
|
1433
1562
|
const inject2 = (options) => ({
|
|
1434
1563
|
...options,
|
|
1564
|
+
...typeof fetchImpl === "function" ? { fetch: fetchImpl } : {},
|
|
1435
1565
|
samplingParams: {
|
|
1436
1566
|
...options?.samplingParams,
|
|
1437
1567
|
include: ["reasoning.encrypted_content"]
|
|
@@ -1501,7 +1631,7 @@ async function createGrokBuildAdapter(session, options = {}) {
|
|
|
1501
1631
|
if (!responsesApi) {
|
|
1502
1632
|
return { adapter: asHostAdapter(duck), kind: "custom-mvp", note: "openai-responses API module is unavailable; using the custom adapter." };
|
|
1503
1633
|
}
|
|
1504
|
-
responsesApi = withEncryptedReasoningInclude(responsesApi);
|
|
1634
|
+
responsesApi = withEncryptedReasoningInclude(responsesApi, await grokFetch());
|
|
1505
1635
|
const store = createStore(session);
|
|
1506
1636
|
const authModels = piAi.createModels({
|
|
1507
1637
|
credentials: store,
|
|
@@ -1592,46 +1722,166 @@ async function createGrokBuildAdapter(session, options = {}) {
|
|
|
1592
1722
|
}
|
|
1593
1723
|
|
|
1594
1724
|
// src/session.js
|
|
1595
|
-
function resolveGrokBin(env = process.env, exists =
|
|
1725
|
+
function resolveGrokBin(env = process.env, exists = existsSync4) {
|
|
1596
1726
|
if (typeof env.DSH_GROK_BIN === "string" && env.DSH_GROK_BIN.trim()) return env.DSH_GROK_BIN.trim();
|
|
1597
|
-
const local =
|
|
1727
|
+
const local = join4(grokHome(env), "bin", "grok");
|
|
1598
1728
|
if (exists(local)) return local;
|
|
1599
1729
|
return "grok";
|
|
1600
1730
|
}
|
|
1601
|
-
function grokCliAvailable(env = process.env, exists =
|
|
1731
|
+
function grokCliAvailable(env = process.env, exists = existsSync4) {
|
|
1602
1732
|
const bin = resolveGrokBin(env, exists);
|
|
1603
1733
|
if (bin.includes("/") || bin.includes("\\")) return exists(bin);
|
|
1604
1734
|
const delimiter = process.platform === "win32" ? ";" : ":";
|
|
1605
|
-
return String(env.PATH ?? "").split(delimiter).some((dir) => dir && exists(
|
|
1735
|
+
return String(env.PATH ?? "").split(delimiter).some((dir) => dir && exists(join4(dir, bin)));
|
|
1736
|
+
}
|
|
1737
|
+
var STANDARD_PROXY_NAMES = [
|
|
1738
|
+
"http_proxy",
|
|
1739
|
+
"https_proxy",
|
|
1740
|
+
"all_proxy",
|
|
1741
|
+
"no_proxy",
|
|
1742
|
+
"HTTP_PROXY",
|
|
1743
|
+
"HTTPS_PROXY",
|
|
1744
|
+
"ALL_PROXY",
|
|
1745
|
+
"NO_PROXY"
|
|
1746
|
+
];
|
|
1747
|
+
function cliProxyEnv(env = process.env) {
|
|
1748
|
+
const base = env ?? process.env;
|
|
1749
|
+
const declared = STANDARD_PROXY_NAMES.some((name2) => typeof base[name2] === "string" && base[name2].trim() !== "");
|
|
1750
|
+
if (declared) return base;
|
|
1751
|
+
const named = [base.GROK_CLI_PROXY, base.GROK_PROXY].find((value) => typeof value === "string" && value.trim() !== "");
|
|
1752
|
+
const proxy = typeof named === "string" ? named.trim() : "";
|
|
1753
|
+
if (!proxy) return base;
|
|
1754
|
+
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";
|
|
1755
|
+
const httpish = /^https?:\/\//iu.test(proxy);
|
|
1756
|
+
return {
|
|
1757
|
+
...base,
|
|
1758
|
+
...httpish ? { http_proxy: proxy, https_proxy: proxy } : {},
|
|
1759
|
+
all_proxy: proxy,
|
|
1760
|
+
no_proxy: bypass
|
|
1761
|
+
};
|
|
1762
|
+
}
|
|
1763
|
+
async function resolveCliEnv(env = process.env) {
|
|
1764
|
+
const base = env ?? process.env;
|
|
1765
|
+
const overlay = await hostProxyEnvironment();
|
|
1766
|
+
if (!overlay) return cliProxyEnv(base);
|
|
1767
|
+
const next = { ...base };
|
|
1768
|
+
for (const [key, value] of Object.entries(overlay)) {
|
|
1769
|
+
if (value === void 0) delete next[key];
|
|
1770
|
+
else next[key] = value;
|
|
1771
|
+
}
|
|
1772
|
+
return next;
|
|
1773
|
+
}
|
|
1774
|
+
function openExternal(url, options = {}) {
|
|
1775
|
+
const spawnFn = options.spawn ?? spawn;
|
|
1776
|
+
const platform = options.platform ?? process.platform;
|
|
1777
|
+
const [command, args] = platform === "darwin" ? ["open", [url]] : platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
|
|
1778
|
+
return new Promise((resolve) => {
|
|
1779
|
+
let child;
|
|
1780
|
+
try {
|
|
1781
|
+
child = spawnFn(command, args, { stdio: "ignore", detached: false });
|
|
1782
|
+
} catch {
|
|
1783
|
+
resolve(false);
|
|
1784
|
+
return;
|
|
1785
|
+
}
|
|
1786
|
+
let settled = false;
|
|
1787
|
+
const finish = (value) => {
|
|
1788
|
+
if (settled) return;
|
|
1789
|
+
settled = true;
|
|
1790
|
+
resolve(value);
|
|
1791
|
+
};
|
|
1792
|
+
child.on?.("error", () => finish(false));
|
|
1793
|
+
child.on?.("exit", (code) => finish(code === 0));
|
|
1794
|
+
child.unref?.();
|
|
1795
|
+
});
|
|
1606
1796
|
}
|
|
1607
|
-
|
|
1797
|
+
var LOGIN_URL_PATTERN = /https?:\/\/[^\s<>"'`\\]+/u;
|
|
1798
|
+
var DEVICE_CODE_PATTERN = /\b([A-Z0-9]{4}-[A-Z0-9]{4})\b/u;
|
|
1799
|
+
async function spawnGrokLogin(options = {}) {
|
|
1608
1800
|
const spawnFn = options.spawn ?? spawn;
|
|
1609
1801
|
const bin = resolveGrokBin(options.env, options.exists);
|
|
1610
1802
|
const args = options.device ? ["login", "--device-auth"] : ["login"];
|
|
1803
|
+
const startTimeoutMs = typeof options.startTimeoutMs === "number" && options.startTimeoutMs > 0 ? options.startTimeoutMs : LOGIN_START_TIMEOUT_MS;
|
|
1804
|
+
const openUrl = options.openUrl ?? ((url) => openExternal(url, { spawn: options.openSpawn }));
|
|
1805
|
+
const childEnv = await resolveCliEnv(options.env);
|
|
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: childEnv
|
|
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
|
});
|
|
1624
1873
|
}
|
|
1625
|
-
function spawnGrokRefresh(options = {}) {
|
|
1874
|
+
async function spawnGrokRefresh(options = {}) {
|
|
1626
1875
|
const spawnFn = options.spawn ?? spawn;
|
|
1627
1876
|
const bin = resolveGrokBin(options.env, options.exists);
|
|
1628
1877
|
const timeoutMs = typeof options.timeoutMs === "number" && options.timeoutMs > 0 ? options.timeoutMs : CLI_REFRESH_TIMEOUT_MS;
|
|
1878
|
+
const childEnv = await resolveCliEnv(options.env);
|
|
1629
1879
|
return new Promise((resolve, reject) => {
|
|
1630
1880
|
let child;
|
|
1631
1881
|
try {
|
|
1632
1882
|
child = spawnFn(bin, ["models"], {
|
|
1633
1883
|
stdio: options.stdio ?? "ignore",
|
|
1634
|
-
env:
|
|
1884
|
+
env: childEnv
|
|
1635
1885
|
});
|
|
1636
1886
|
} catch (error) {
|
|
1637
1887
|
reject(new Error(`Could not start grok CLI (${bin})`, { cause: error }));
|
|
@@ -1940,8 +2190,27 @@ function createSessionService({
|
|
|
1940
2190
|
return { account: lastPublic, catalog, usage: lastUsage, cliAvailable: grokCliAvailable(), authPath: authJsonPath() };
|
|
1941
2191
|
};
|
|
1942
2192
|
const login = async (options) => {
|
|
1943
|
-
await spawnGrokLogin(options);
|
|
1944
|
-
|
|
2193
|
+
const started = await spawnGrokLogin(options);
|
|
2194
|
+
const extra = {
|
|
2195
|
+
...started.loginUrl === void 0 ? {} : { loginUrl: started.loginUrl },
|
|
2196
|
+
...started.userCode === void 0 ? {} : { userCode: started.userCode },
|
|
2197
|
+
...started.warning === void 0 ? {} : { warning: started.warning }
|
|
2198
|
+
};
|
|
2199
|
+
if (started.pending) {
|
|
2200
|
+
started.child?.once?.("exit", (code) => {
|
|
2201
|
+
if (code !== 0) return;
|
|
2202
|
+
scheduleDeferred(() => {
|
|
2203
|
+
void pull().catch((error) => {
|
|
2204
|
+
logger?.warn?.(
|
|
2205
|
+
"Grok subscription login sync failed: %s",
|
|
2206
|
+
error instanceof Error ? error.message : "unknown"
|
|
2207
|
+
);
|
|
2208
|
+
});
|
|
2209
|
+
});
|
|
2210
|
+
});
|
|
2211
|
+
return { ok: true, pending: true, ...extra, ...await status() };
|
|
2212
|
+
}
|
|
2213
|
+
return { ...await pull(), pending: false, ...extra };
|
|
1945
2214
|
};
|
|
1946
2215
|
const logout = async () => {
|
|
1947
2216
|
memoryAccessToken = void 0;
|
|
@@ -1979,7 +2248,7 @@ function createSessionService({
|
|
|
1979
2248
|
import { spawn as spawn2 } from "node:child_process";
|
|
1980
2249
|
import { readdir, readFile, realpath } from "node:fs/promises";
|
|
1981
2250
|
import { homedir as homedir3 } from "node:os";
|
|
1982
|
-
import { join as
|
|
2251
|
+
import { join as join5 } from "node:path";
|
|
1983
2252
|
import { fileURLToPath } from "node:url";
|
|
1984
2253
|
var PACKAGE_NAME = PLUGIN_ID;
|
|
1985
2254
|
var NPM_REGISTRY_LATEST_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
@@ -2028,20 +2297,20 @@ async function findInstall({ dshHome, ownPackageJsonUrl }) {
|
|
|
2028
2297
|
}
|
|
2029
2298
|
let entries = [];
|
|
2030
2299
|
try {
|
|
2031
|
-
entries = await readdir(
|
|
2300
|
+
entries = await readdir(join5(dshHome, "profiles"), { withFileTypes: true });
|
|
2032
2301
|
} catch {
|
|
2033
2302
|
entries = [];
|
|
2034
2303
|
}
|
|
2035
2304
|
const declared = [];
|
|
2036
2305
|
for (const entry of entries) {
|
|
2037
2306
|
if (!entry.isDirectory()) continue;
|
|
2038
|
-
const profileDir =
|
|
2039
|
-
const spec = (await readManifest(
|
|
2307
|
+
const profileDir = join5(dshHome, "profiles", entry.name);
|
|
2308
|
+
const spec = (await readManifest(join5(profileDir, "package.json")))?.dependencies?.[PACKAGE_NAME];
|
|
2040
2309
|
if (typeof spec !== "string") continue;
|
|
2041
2310
|
declared.push({ profile: entry.name, spec });
|
|
2042
2311
|
if (ownReal === void 0) continue;
|
|
2043
2312
|
try {
|
|
2044
|
-
const installed = await realpath(
|
|
2313
|
+
const installed = await realpath(join5(profileDir, "node_modules", PACKAGE_NAME, "package.json"));
|
|
2045
2314
|
if (installed === ownReal) return { kind: classifySpec(spec), profile: entry.name };
|
|
2046
2315
|
} catch {
|
|
2047
2316
|
}
|
|
@@ -2108,8 +2377,8 @@ function createGrokPluginManager({
|
|
|
2108
2377
|
} = {}) {
|
|
2109
2378
|
if (typeof fetchImpl !== "function") throw new Error("Grok plugin manager requires fetch");
|
|
2110
2379
|
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 :
|
|
2112
|
-
let
|
|
2380
|
+
const dshHome = typeof env.DSH_HOME === "string" && env.DSH_HOME !== "" ? env.DSH_HOME : join5(homedir3(), ".dsh");
|
|
2381
|
+
let cached2;
|
|
2113
2382
|
let inFlight;
|
|
2114
2383
|
let generation = 0;
|
|
2115
2384
|
let updating = false;
|
|
@@ -2130,7 +2399,7 @@ function createGrokPluginManager({
|
|
|
2130
2399
|
if (!response.ok) throw new Error("Grok plugin version check failed");
|
|
2131
2400
|
let version;
|
|
2132
2401
|
try {
|
|
2133
|
-
version = (await response
|
|
2402
|
+
version = (await readResponseJson(response))?.version;
|
|
2134
2403
|
} catch {
|
|
2135
2404
|
version = void 0;
|
|
2136
2405
|
}
|
|
@@ -2160,11 +2429,11 @@ function createGrokPluginManager({
|
|
|
2160
2429
|
return Object.freeze({
|
|
2161
2430
|
async read({ force = false, signal } = {}) {
|
|
2162
2431
|
signal?.throwIfAborted();
|
|
2163
|
-
if (!force &&
|
|
2432
|
+
if (!force && cached2 !== void 0 && now() - cached2.fetchedAt < ttlMs) return clone(cached2);
|
|
2164
2433
|
const createPending = (requestSignal) => {
|
|
2165
2434
|
const observedGeneration = generation;
|
|
2166
2435
|
return load(requestSignal).then((value) => {
|
|
2167
|
-
if (generation === observedGeneration)
|
|
2436
|
+
if (generation === observedGeneration) cached2 = value;
|
|
2168
2437
|
return value;
|
|
2169
2438
|
});
|
|
2170
2439
|
};
|
|
@@ -2207,7 +2476,7 @@ function createGrokPluginManager({
|
|
|
2207
2476
|
}
|
|
2208
2477
|
if (result.code !== 0) throw new Error("Grok plugin update failed");
|
|
2209
2478
|
generation += 1;
|
|
2210
|
-
|
|
2479
|
+
cached2 = void 0;
|
|
2211
2480
|
return { version: latest, profile: install.profile };
|
|
2212
2481
|
} finally {
|
|
2213
2482
|
updating = false;
|
|
@@ -2215,7 +2484,7 @@ function createGrokPluginManager({
|
|
|
2215
2484
|
},
|
|
2216
2485
|
invalidate() {
|
|
2217
2486
|
generation += 1;
|
|
2218
|
-
|
|
2487
|
+
cached2 = void 0;
|
|
2219
2488
|
}
|
|
2220
2489
|
});
|
|
2221
2490
|
}
|
|
@@ -2357,14 +2626,16 @@ async function dispatch(session, endpoint, diagnostics, payload, signal, pluginM
|
|
|
2357
2626
|
}
|
|
2358
2627
|
function createRpcHandler(session, options = {}) {
|
|
2359
2628
|
const timeoutMs = typeof options.timeoutMs === "number" && options.timeoutMs > 0 ? options.timeoutMs : RPC_HANDLER_TIMEOUT_MS;
|
|
2629
|
+
const loginTimeoutMs = typeof options.loginTimeoutMs === "number" && options.loginTimeoutMs > 0 ? options.loginTimeoutMs : LOGIN_HANDLER_TIMEOUT_MS;
|
|
2360
2630
|
return async function handle(endpoint, payload, signal) {
|
|
2361
2631
|
try {
|
|
2362
2632
|
const operation = dispatch(session, endpoint, options.diagnostics, payload, signal, options.pluginManager);
|
|
2363
2633
|
if (endpoint === "plugin/update") return await operation;
|
|
2634
|
+
const ceiling = endpoint === "login/cli" || endpoint === "login/device" ? loginTimeoutMs : timeoutMs;
|
|
2364
2635
|
return await withTimeout(
|
|
2365
2636
|
operation,
|
|
2366
|
-
|
|
2367
|
-
`Grok subscription RPC "${endpoint}" timed out after ${
|
|
2637
|
+
ceiling,
|
|
2638
|
+
`Grok subscription RPC "${endpoint}" timed out after ${ceiling}ms`,
|
|
2368
2639
|
{ unref: false }
|
|
2369
2640
|
);
|
|
2370
2641
|
} catch (error) {
|