dsh-grok-subscription 1.1.3 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.2.0 — 2026-09-28
4
+
5
+ - Fix **the host's official pi-ai adapter never serving the route**. `ctx.llm.registerAdapter` refuses a second adapter for a provider that already has one (`DUPLICATE_ADAPTER`, all-or-nothing) in every Host this plugin supports, while the plugin registered its bundled adapter synchronously and then re-registered the same `grok-build` route from the deferred upgrade. That throw landed in the upgrade's `catch` and was logged at warn level, so **every release since 0.1.10 — the one that introduced the bundled-then-upgrade design — ran the bundled adapter** while Settings reported `Active path: bundled fallback`. The upgrade now releases the bundled registration and claims the route again in one synchronous step, and restores the bundled adapter if the Host refuses the replacement, so the route is never left unserved. This defect predates 1.1.3 and is unrelated to the 0.2.0 compatibility gate that release fixed. Reported in 1.1.3's release notes and fixed here.
6
+ - Pin the request fields this plugin owns on the pi-ai path, instead of inheriting pi-ai's defaults. `withEncryptedReasoningInclude` (the single seam that injects `include: ['reasoning.encrypted_content']` into `samplingParams`, which pi-ai assigns over the assembled body last) becomes `withGrokRequestSemantics` and now also pins `prompt_cache_key` to the same `grok:<sessionId>` value the bundled adapter builds. A profile that disables caching (`cacheRetention: 'none'`) is left alone: the plugin never switches a cache back on.
7
+ - **Auxiliary requests deliberately share the session's cache key.** `dsh-llm-pi-ai` forwards only `sessionId` from `GenerateOptions` into pi-ai, so compaction and session-title calls arrive with no purpose marker; isolating them would mean suffixing the session id, which pi-ai also writes into the `session_id` and `x-client-request-id` headers. Measured against the live proxy, isolation does not pay: after a large auxiliary call a repeated chat request still read the full cached prefix in 4 of 5 shared-key trials versus 2 of 5 isolated ones, matching a control run with no auxiliary call at all. The bundled adapter keeps its own `grok:compaction:<sessionId>` scheme; this path keeps the shared key, and the guide says so.
8
+ - Add `DSH_GROK_ADAPTER=fallback` as a documented rollback. It keeps the bundled adapter even where the Host's official one is available, without waiting for a release, and is read from DSH's own environment (`~/.dsh/.env`).
9
+ - Verified on the real 0.2.0-rc.1 desktop runtime against the live subscription. Diagnostics now read `pi-ai` with `hostPeersResolved: true`. A three-turn conversation using the Host's own durable message shape (the accumulator's blocks plus `createAssistantMessage` with its replay state) answered `stored` → `42` → `84`, proving encrypted-reasoning replay survives the switch, with `cacheReadTokens` climbing 1152 → 1280 → 1408. A tool turn returned `finish: tool-calls` with `get_weather {"city":"Paris"}`. A `purpose: 'compaction'` turn completed normally. The rollback switch was exercised live: `fallback` serves the bundled adapter, unset and any other value serve `PiAiAdapter`.
10
+ - `tests/pi-ai-request-body.test.mjs` drives the real `@earendil-works/pi-ai` Responses API through the plugin's wrapper with a stubbed transport and asserts the outgoing body — `include`, `prompt_cache_key`, `store: false`, `model`, and the converted input — so a pi-ai release that stops honouring `samplingParams` fails CI instead of silently dropping both fields. `tests/pi-ai-request-semantics.test.mjs` covers the wrapper directly (including the disabled-cache case), and `tests/adapter-upgrade.test.mjs` models the Host's registry rule and the rollback switch. All eight new assertions fail against 1.1.3.
11
+ - No session, login, renewal, model-routing, quota, or proxy changes. The bundled adapter is still shipped and still the automatic fallback. Restart DSH fully after updating.
12
+
3
13
  ## 1.1.3 — 2026-09-28
4
14
 
5
15
  - Fix **the plugin disappearing from the desktop app after it updated to DSH 0.2.0-rc.1**. DSH 0.2.0 gates every profile entry on the plugin's `@deepseek-ai/dsh*` peer ranges (`evaluatePluginCompatibility` in `@deepseek-ai/dsh-app-boot`) and drops the whole bundle before any plugin code loads when one of them does not admit the running version. This plugin declared `^0.1.5-rc.2`, which is `<0.2.0`, so the Host reported `skipping profile bundle "dsh-grok-subscription": Plugin dsh-grok-subscription@1.1.2 is incompatible with dsh 0.2.0-rc.1` and started without a `grok-build` route, without a Settings section, and without any error in the UI. Reproduced against the real desktop runtime, and confirmed fixed with the same `dsh --profile <name> --dump-config`: the composition now emits the `grok-subscription` row.
package/docs/guide.en.md CHANGED
@@ -18,7 +18,7 @@ dsh plugin --profile web list dsh-grok-subscription --depth 0
18
18
  For reproducibility, pin a published version, for example:
19
19
 
20
20
  ```sh
21
- dsh plugin --profile web add dsh-grok-subscription@1.1.3
21
+ dsh plugin --profile web add dsh-grok-subscription@1.2.0
22
22
  ```
23
23
 
24
24
  **Install from GitHub:**
@@ -80,7 +80,7 @@ These exact IDs come from `IMAGE_INPUT_MODEL_IDS` in [constants.js](../src/const
80
80
 
81
81
  Paste or attach an image in the composer. DSH's attachment service processes it within pixel and byte budgets; over-budget images may degrade to a text description. Do not assume every image is sent at its original resolution.
82
82
 
83
- Both the host's pi-ai adapter and the bundled fallback have image-handling paths, but both depend on the host attachment capability. The **Active path** row in Settings shows the adapter and whether image input is available. After an update, a refreshed browser can still be using an old adapter if the Host was not restarted.
83
+ From 1.2.0 the plugin runs the host's official pi-ai adapter, which owns the image pipeline (attachment resolution plus pixel and byte budgets). The bundled adapter stays as the automatic fallback: it is what serves the route if the pi-ai packages are missing, or if the Host refuses the hand-over. The **Active path** row in Settings names the adapter actually serving the route, and whether image input is available. After an update, a refreshed browser can still be using an old adapter if the Host was not restarted. To pin the bundled adapter deliberately — for instance if a Host adapter misbehaves on your account — set `DSH_GROK_ADAPTER=fallback` in `~/.dsh/.env` and restart DSH.
84
84
 
85
85
  ## Quota and caching
86
86
 
@@ -88,7 +88,7 @@ Usage is read through the undocumented `/v1/billing?format=credits` endpoint and
88
88
 
89
89
  The composer badge appears only for `grok-build` with a successful usage read; hover or click for weekly quota and its reset time. Missing data does not mean zero quota. A failed read does not invent percentages or independently block chat.
90
90
 
91
- For prefix caching, the plugin keeps a stable `prompt_cache_key` and tool ordering within a DSH session, and the bundled adapter preserves `reasoning.encrypted_content`. These are request-construction measures, not a guarantee of backend cache hits or quota savings. No invented hit rate is reported. See [adapter.js](../src/adapter.js).
91
+ For prefix caching, the plugin pins one stable `prompt_cache_key` per DSH session on **both** adapter paths — the bundled adapter builds it, and on the host's pi-ai path the plugin assigns the same `grok:<sessionId>` value into the request. Tool ordering stays stable. Auxiliary requests (compaction, session titles) share the session's key rather than taking a separate one; measured against the proxy, isolating them did not improve the chat prefix's cache reads. These are request-construction measures, not a guarantee of backend cache hits or quota savings. No invented hit rate is reported. See [adapter.js](../src/adapter.js).
92
92
 
93
93
  ## Headless usage
94
94
 
@@ -134,7 +134,7 @@ In the desktop app the `desktop` profile is owned exclusively by the Electron ap
134
134
 
135
135
  ```sh
136
136
  cd "$HOME/.dsh/profiles/desktop" # Windows: cd "$env:USERPROFILE\.dsh\profiles\desktop"
137
- pnpm add --save-exact dsh-grok-subscription@1.1.3
137
+ pnpm add --save-exact dsh-grok-subscription@1.2.0
138
138
  ```
139
139
 
140
140
  Then quit and restart the desktop app completely. After a successful update the profile's `package.json` dependency should show the target version while its `dsh.profile.bundles` entry is unchanged. Refreshing the browser is not enough: without a Host restart the old version keeps loading.
@@ -18,7 +18,7 @@ dsh plugin --profile web list dsh-grok-subscription --depth 0
18
18
  需要可重复安装时,固定已发布版本,例如:
19
19
 
20
20
  ```sh
21
- dsh plugin --profile web add dsh-grok-subscription@1.1.3
21
+ dsh plugin --profile web add dsh-grok-subscription@1.2.0
22
22
  ```
23
23
 
24
24
  **GitHub 来源安装:**
@@ -80,7 +80,7 @@ grok-4.6
80
80
 
81
81
  在输入框粘贴或附加图片后,DSH 的附件服务会按像素与体积预算处理。图片超出可用预算时可能降级为文本说明;不要假设每张原图都以原始分辨率发出。
82
82
 
83
- 宿主官方 pi-ai 适配器和内置回退适配器都有对应图片通路,但都依赖宿主附件能力。设置页的 **运行通路** 会显示当前适配器和图片输入是否可用。更新后只有界面刷新、Host 未重启时,可能仍运行旧适配器。
83
+ 1.2.0 起,插件默认运行宿主官方的 pi-ai 适配器,图片通路(附件解析与像素、体积预算)由它负责。内置适配器保留为自动回退:pi-ai 相关包缺失、或宿主拒绝交接时,仍由它服务该路由。设置页的 **运行通路** 会显示实际在服务的适配器和图片输入是否可用。更新后只有界面刷新、Host 未重启时,可能仍运行旧适配器。若要主动固定在内置适配器(例如宿主适配器在你的账号上表现异常),在 `~/.dsh/.env` 里设置 `DSH_GROK_ADAPTER=fallback` 并重启 DSH。
84
84
 
85
85
  ## 额度与缓存
86
86
 
@@ -88,7 +88,7 @@ grok-4.6
88
88
 
89
89
  输入框徽章仅在当前 provider 为 `grok-build` 且用量读取成功时显示,悬停或点击查看每周余量与重置时间。缺失数据不是额度为零;读取失败不会编造百分比,也不会因为单独的额度读取故障阻止聊天。
90
90
 
91
- 前缀缓存方面,插件为同一 DSH 会话保持稳定的 `prompt_cache_key` 和工具顺序,内置回退适配器保留 `reasoning.encrypted_content`。这些是请求构造措施,不保证服务端缓存命中或节省额度;项目不提供虚构的命中率。实现见 [adapter.js](../src/adapter.js)。
91
+ 前缀缓存方面,插件在**两条通路**上都为同一 DSH 会话固定同一个 `prompt_cache_key`:内置适配器自行构造,宿主 pi-ai 通路上插件把同样的 `grok:<sessionId>` 注入请求;工具顺序保持稳定。压缩、会话标题这类辅助请求与会话共用同一个 key,而不是另开一个——对代理实测下来,隔离它们并不会提高聊天前缀的缓存命中。这些是请求构造措施,不保证服务端缓存命中或节省额度;项目不提供虚构的命中率。实现见 [adapter.js](../src/adapter.js)。
92
92
 
93
93
  ## Headless 使用
94
94
 
@@ -134,7 +134,7 @@ npm 安装可在设置页更新,或运行 `dsh plugin --profile web add dsh-gr
134
134
 
135
135
  ```sh
136
136
  cd "$HOME/.dsh/profiles/desktop" # Windows: cd "$env:USERPROFILE\.dsh\profiles\desktop"
137
- pnpm add --save-exact dsh-grok-subscription@1.1.3
137
+ pnpm add --save-exact dsh-grok-subscription@1.2.0
138
138
  ```
139
139
 
140
140
  然后完全退出并重启桌面版。更新成功后该 profile 的 `package.json` 里依赖应变为目标版本,`dsh.profile.bundles` 条目保持不变。仅刷新网页不够:Host 未重启时仍会加载旧版本。
package/lib/client.js CHANGED
@@ -39,7 +39,7 @@ var import_react2 = require("react");
39
39
  var zh = {
40
40
  nav: "Grok \u8BA2\u9605",
41
41
  title: "Grok \u8BA2\u9605",
42
- subtitle: "\u7528 SuperGrok / X Premium\uFF08Grok Build\uFF09\u4F1A\u8BDD\uFF0C\u800C\u4E0D\u662F XAI_API_KEY\u3002\xB7 1.1.3",
42
+ subtitle: "\u7528 SuperGrok / X Premium\uFF08Grok Build\uFF09\u4F1A\u8BDD\uFF0C\u800C\u4E0D\u662F XAI_API_KEY\u3002\xB7 1.2.0",
43
43
  signedIn: "\u5DF2\u767B\u5F55",
44
44
  signedOut: "\u672A\u767B\u5F55",
45
45
  account: "\u8D26\u6237",
@@ -119,7 +119,7 @@ var zh = {
119
119
  var en = {
120
120
  nav: "Grok Subscription",
121
121
  title: "Grok Subscription",
122
- subtitle: "Use a SuperGrok / X Premium (Grok Build) session, not XAI_API_KEY. \xB7 1.1.3",
122
+ subtitle: "Use a SuperGrok / X Premium (Grok Build) session, not XAI_API_KEY. \xB7 1.2.0",
123
123
  signedIn: "Signed in",
124
124
  signedOut: "Signed out",
125
125
  account: "Account",
package/lib/index.js CHANGED
@@ -1473,14 +1473,18 @@ function buildAuthConfig() {
1473
1473
  }
1474
1474
  };
1475
1475
  }
1476
- function withEncryptedReasoningInclude(api) {
1477
- const inject2 = (options) => ({
1478
- ...options,
1479
- samplingParams: {
1480
- ...options?.samplingParams,
1481
- include: ["reasoning.encrypted_content"]
1482
- }
1483
- });
1476
+ function withGrokRequestSemantics(api) {
1477
+ const inject2 = (options) => {
1478
+ const cacheKey = options?.cacheRetention === "none" ? void 0 : promptCacheKey(options);
1479
+ return {
1480
+ ...options,
1481
+ samplingParams: {
1482
+ ...options?.samplingParams,
1483
+ include: ["reasoning.encrypted_content"],
1484
+ ...cacheKey === void 0 ? {} : { prompt_cache_key: cacheKey }
1485
+ }
1486
+ };
1487
+ };
1484
1488
  return {
1485
1489
  stream: (model, context, options) => api.stream(model, context, inject2(options)),
1486
1490
  streamSimple: (model, context, options) => api.streamSimple(model, context, inject2(options))
@@ -1545,7 +1549,7 @@ async function createGrokBuildAdapter(session, options = {}) {
1545
1549
  if (!responsesApi) {
1546
1550
  return { adapter: asHostAdapter(duck), kind: "custom-mvp", note: "openai-responses API module is unavailable; using the custom adapter." };
1547
1551
  }
1548
- responsesApi = withEncryptedReasoningInclude(responsesApi);
1552
+ responsesApi = withGrokRequestSemantics(responsesApi);
1549
1553
  const store = createStore(session);
1550
1554
  const authModels = piAi.createModels({
1551
1555
  credentials: store,
@@ -2693,6 +2697,34 @@ function softService(ctx, key) {
2693
2697
  }
2694
2698
  return void 0;
2695
2699
  }
2700
+ function registerProviderAdapter(ctx, adapter) {
2701
+ const handle = ctx.llm.registerAdapter([PROVIDER_ID], adapter);
2702
+ return typeof handle === "function" ? handle : void 0;
2703
+ }
2704
+ function upgradeProviderAdapter(ctx, adapter, registration) {
2705
+ const previous = registration.adapter;
2706
+ registration.release?.();
2707
+ registration.release = void 0;
2708
+ registration.adapter = void 0;
2709
+ try {
2710
+ registration.release = registerProviderAdapter(ctx, adapter);
2711
+ registration.adapter = adapter;
2712
+ } catch (error) {
2713
+ if (previous !== void 0) {
2714
+ try {
2715
+ registration.release = registerProviderAdapter(ctx, previous);
2716
+ registration.adapter = previous;
2717
+ } catch {
2718
+ registration.release = void 0;
2719
+ }
2720
+ }
2721
+ throw error;
2722
+ }
2723
+ }
2724
+ function adapterOverride(env = process.env) {
2725
+ const value = typeof env?.DSH_GROK_ADAPTER === "string" ? env.DSH_GROK_ADAPTER.trim().toLowerCase() : "";
2726
+ return value === "fallback" ? "fallback" : void 0;
2727
+ }
2696
2728
  function attachmentResolver(ctx) {
2697
2729
  return () => {
2698
2730
  try {
@@ -2742,11 +2774,13 @@ function apply(ctx, options = {}) {
2742
2774
  });
2743
2775
  const resolveAttachments = attachmentResolver(ctx);
2744
2776
  const createSync = options.createSync ?? createGrokBuildAdapterSync;
2777
+ const registration = { adapter: void 0, release: void 0 };
2745
2778
  try {
2746
2779
  const created = createSync(session, { resolveAttachments });
2747
2780
  if (created.note) ctx.logger?.debug?.(created.note);
2748
2781
  adapterState.kind = created.kind;
2749
- ctx.llm.registerAdapter([PROVIDER_ID], created.adapter);
2782
+ registration.adapter = created.adapter;
2783
+ registration.release = registerProviderAdapter(ctx, created.adapter);
2750
2784
  notifyCatalogChange();
2751
2785
  } catch (error) {
2752
2786
  ctx.logger?.warn?.(
@@ -2755,15 +2789,16 @@ function apply(ctx, options = {}) {
2755
2789
  );
2756
2790
  }
2757
2791
  const defer = options.defer ?? scheduleDeferred2;
2792
+ const deferredOptions = adapterOverride() === "fallback" ? { ...options, skipUpgrade: true } : options;
2758
2793
  defer(() => {
2759
- void deferredBoot(ctx, session, notifyCatalogChange, options, adapterState);
2794
+ void deferredBoot(ctx, session, notifyCatalogChange, deferredOptions, adapterState, registration);
2760
2795
  });
2761
2796
  ctx.inject(["connection"], (connectionContext) => connectionContext.effect(
2762
2797
  () => registerSubscriptionTransport(connectionContext.connection, handler),
2763
2798
  "grok-subscription: host-only account RPC"
2764
2799
  ));
2765
2800
  }
2766
- async function deferredBoot(ctx, session, notifyCatalogChange, options = {}, adapterState) {
2801
+ async function deferredBoot(ctx, session, notifyCatalogChange, options = {}, adapterState, registration) {
2767
2802
  if (!options.skipSettings) {
2768
2803
  await tryRegisterSettings(ctx);
2769
2804
  }
@@ -2794,7 +2829,8 @@ async function deferredBoot(ctx, session, notifyCatalogChange, options = {}, ada
2794
2829
  const created = await createAsync(session, { ...options.adapterOptions, resolveAttachments: attachmentResolver(ctx) });
2795
2830
  if (created.kind === "pi-ai") {
2796
2831
  if (created.note) ctx.logger?.warn?.(created.note);
2797
- ctx.llm.registerAdapter([PROVIDER_ID], created.adapter);
2832
+ if (registration) upgradeProviderAdapter(ctx, created.adapter, registration);
2833
+ else ctx.llm.registerAdapter([PROVIDER_ID], created.adapter);
2798
2834
  if (adapterState) {
2799
2835
  adapterState.kind = created.kind;
2800
2836
  adapterState.upgraded = true;
@@ -2832,10 +2868,13 @@ export {
2832
2868
  DISPLAY_NAME,
2833
2869
  DISPLAY_NAME_ZH,
2834
2870
  PROVIDER_ID,
2871
+ adapterOverride,
2835
2872
  apply,
2836
2873
  deferredBoot,
2837
2874
  inject,
2838
2875
  name,
2876
+ registerProviderAdapter,
2839
2877
  scheduleDeferred2 as scheduleDeferred,
2840
- softService
2878
+ softService,
2879
+ upgradeProviderAdapter
2841
2880
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-grok-subscription",
3
- "version": "1.1.3",
3
+ "version": "1.2.0",
4
4
  "description": "Use a SuperGrok or X Premium Grok Build subscription in DeepSeek Harness",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -127,6 +127,7 @@
127
127
  }
128
128
  },
129
129
  "devDependencies": {
130
+ "@earendil-works/pi-ai": "0.85.1",
130
131
  "esbuild": "^0.25.0",
131
132
  "react": "^18.3.1",
132
133
  "react-dom": "^18.3.1",