dsh-packycode-api 1.1.7 → 1.1.9

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,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.1.9
4
+
5
+ - Added `gemini-4-argon` (**Gemini 4 Argon**) and `gemini-3.8-flash` (**Gemini 3.8 Flash**), both using `google-generative-ai` at `https://cf.api.fan/v1beta`. PackyCode's [live catalog](https://www.packyapi.com/api/pricing) and its support reply declare only the native `gemini` endpoint for Argon; Flash supports it too, keeping the Gemini family on one native route. Requests use streaming `streamGenerateContent`, with `thinkingLevel: LOW / MEDIUM / HIGH`, text/image input and native tool/signature replay.
6
+ - A compatibility wrapper removes the unsupported `thinkingBudget` emitted for Argon by older pi-ai hosts and defaults both Gemini models to `MEDIUM` when no effort is supplied. The DSH profile continues to select `high` by default. Both models declare a 1,048,576-token context/input window and 65,536-token single-stream output limit, without automatic long-decode continuation. See the [Argon](https://ai.google.dev/gemini-api/docs/models/gemini-4-argon) and [Flash](https://ai.google.dev/gemini-api/docs/models/gemini-3.8-flash) model pages.
7
+ - Both models appear by default in the picker and settings coverage summary. Live checks include them; `--model MODEL [--with-vision]` also allows a bounded check of one model through the built host route. Credentials still come only from the plugin's stored key ring.
8
+ - Flash tool continuations now resend once after one second when the gateway returns HTTP 400 `Corrupted thought signature` / `Invalid thought signature` before any stream event. Fixed-response experiments showed the same signed request being refused and later accepted; three fresh native roundtrips passed with at most one resend. The guard requires a current signed same-model call and matching result, preserves the model/key/signature/request, and does not retry after output, on unrelated failures, or for Argon. Cancellation stops the wait, and a second refusal remains unchanged for the host's normal failure/retry handling.
9
+ - Added `scripts/gemini-tool-check.mjs`, which exercises the built route, DSH's message assembler and JSON-persisted replay state, checking HTTP resend counts and request-body equality without printing signatures or credentials.
10
+ - Validation: 321 offline tests passed, both bundles rebuilt and the tarball generated. The built native Flash route passed three fresh `low` tool roundtrips and one `high` roundtrip; Argon passed its tool roundtrip, and Flash text/red/blue-image checks passed. These final live tool runs required no resend; the exact HTTP-400-then-success path is covered by a real SDK/host regression. A separate Flash Completions comparison passed three tool roundtrips. Both Gemini catalog models remain on the requested native protocol; the guard mitigates an intermittent gateway refusal and does not repair its server-side validator.
11
+
12
+ ## 1.1.8
13
+
14
+ - **Diagnosed an installation that mounts nothing, and made it reportable.** A profile that once had this plugin installed from a local path keeps a `node_modules` inside the installed package directory; a later registry install rewrites the package's files and leaves that tree in place. DSH resolves a plugin's imports from inside the profile first, so the plugin kept loading the old copies — `@deepseek-ai/schemastery` 3.18.2 has no `Schema.volatile()`, the module threw while it was imported, and the Loader created no fiber for the row. `@deepseek-ai/dsh-client-modules` skips a fiber-less entry, so the browser bundle was never composed and the only symptom was a settings page with no PackyCode section, no model route, no RPC channel, and a clean host log. The diagnosis was confirmed on the affected desktop profile: the composed entry `{"id":"packycode-api","name":"dsh-packycode-api"}` is present, the 67-row boot graph carries no bundle for it, `POST /packycode-api/status` is unregistered, and importing the installed package from a copy reproduces `TypeError: schema.volatile is not a function`.
15
+ - **A missing `Schema.volatile()` now names its cause.** `live()` throws an actionable error naming the peer requirement, the shadowing package directory left by an earlier local install, and the recovery, instead of a bare `schema.volatile is not a function` that the host log buries.
16
+ - Added `scripts/install-check.mjs`: read-only, no network. It resolves every installed profile dependency the way DSH does, reports any host package (`@deepseek-ai/*`, `@earendil-works/*`) shadowed by a `node_modules` inside an installed package, names the `schemastery` release that breaks a row, flags an installed-but-unselected bundle and a web-search patch left pointing at a removed backend, prints the recovery steps, and exits 1 when it finds an error. `--profile`, `--home`, and `--json` are supported. A `link:` install, which resolves outside the profile where DSH intercepts peers, is deliberately not reported.
17
+ - Installation guidance: the local-checkout instructions now require the `link:` prefix and say why a bare path or `file:` copy is damaging, the update section says a `pnpm add` update cannot remove that leftover tree and that uninstall (`pnpm remove`) must come first, and the FAQ documents the symptom, the one-line check, and the recovery.
18
+ - Validation: 295 tests passed, both bundles rebuilt and the tarball generated. The new checks were exercised against a fixture profile (clean, shadowed below and at the volatile release, `link:`, unselected bundle, dangling search patch, non-host nested tree, missing manifest) and against the affected desktop profile, where the script reports the shadowed tree and exits 1.
19
+
3
20
  ## 1.1.7
4
21
 
5
22
  - Fixed HTTP 405 from the settings RPC after a first hot install. The shipped Connection getter retains its own row's context, which need not inject `webServer`; the browser channel now registers with this plugin's `connection` / `webServer` injection scope as its explicit owner. Native Connection admission, authentication and RPC envelopes remain in use. The explicit-owner entry is internal to the shipped service, so other Connection implementations retain the public `rpc.handle` contract.
package/README.en.md CHANGED
@@ -33,6 +33,11 @@ The `PackyCode` model group ships with the plugin. No YAML edits and no separate
33
33
  | **Web search through PackyCode** | Optional: DSH's `web_search` searches through PackyCode, with a DeepSeek or GPT backend, for every request or for PackyCode models only |
34
34
  | **Credentials stay on the machine** | Keys live in the DSH credential file. The browser receives labels, state, and balances only |
35
35
 
36
+ <img width="774" height="455" alt="截屏2026-10-15 10 34 28" src="https://github.com/user-attachments/assets/c5eaa535-dd1e-4e2f-be9f-c181404d4dd6" />
37
+ <img width="575" height="522" alt="截屏2026-10-15 10 34 49" src="https://github.com/user-attachments/assets/3cf4d090-a4f9-49fe-9ab3-3aab21d6e909" />
38
+ <img width="257" height="309" alt="截屏2026-10-15 10 37 21" src="https://github.com/user-attachments/assets/621cd800-17a2-42f6-8dfe-c1b4be09af61" />
39
+
40
+
36
41
  ## Install
37
42
 
38
43
  You need **DeepSeek Harness** and a **PackyCode (PackyAPI) API key**. For DSH setup, see the [official instructions](https://github.com/deepseek-ai/deepseek-harness#run).
@@ -45,7 +50,7 @@ Open the desktop application's **Plugins** page, click **Add plugin**, and enter
45
50
  dsh-packycode-api
46
51
  ```
47
52
 
48
- Choose the **official** install source, then click **Install**. To pin a version, enter `dsh-packycode-api@1.1.7`.
53
+ Choose the **official** install source, then click **Install**. To pin a version, enter `dsh-packycode-api@1.1.9`.
49
54
 
50
55
  A **first install requires no DSH restart**: open **Settings → PackyCode** and add a key immediately. When updating an already loaded older version, follow the restart guidance in the **Plugin version** card.
51
56
 
@@ -57,16 +62,16 @@ Open **Settings → PackyCode**. Paste the API key under **Add a key**. A label
57
62
 
58
63
  ### 3. Select a model
59
64
 
60
- Choose a model from the `PackyCode` group. `deepseek-flash`, `gpt-6.1-sol`, `gpt-6-astra`, and `grok-4.7` use HTTP. The three Claude models also need the Claude Code CLI on this device; see below. Your key's groups must cover the selected model.
65
+ Choose a model from the `PackyCode` group. `deepseek-flash`, `gpt-6.1-sol`, `gpt-6-astra`, `grok-4.7`, `gemini-4-argon`, and `gemini-3.8-flash` use HTTP. The three Claude models also need the Claude Code CLI on this device; see below. Your key's groups must cover the selected model.
61
66
 
62
67
  <details>
63
68
  <summary>Verify the install / other profiles / a local checkout</summary>
64
69
 
65
70
  The **Plugins** page shows the installed state and version, and is also where you enable, disable, or uninstall.
66
71
 
67
- **For non-desktop profiles only:** `dsh plugin --profile <name> add dsh-packycode-api@1.1.7`. Do not use it for `desktop` — the CLI refuses it (`profile "desktop" is managed exclusively by the Electron application`).
72
+ **For non-desktop profiles only:** `dsh plugin --profile <name> add dsh-packycode-api@1.1.9`. Do not use it for `desktop` — the CLI refuses it (`profile "desktop" is managed exclusively by the Electron application`).
68
73
 
69
- A local checkout can be linked with `dsh plugin --profile <name> add link:/path/to/dsh-packycode-api`.
74
+ For a local checkout, keep the `link:` prefix: `dsh plugin --profile <name> add link:/path/to/dsh-packycode-api`. **Do not pass a bare path or `file:`** — that install copies the whole checkout, `node_modules` included, into the profile, where it then shadows the packages the DSH installation itself provides and the plugin looks uninstalled; see the FAQ. If you already installed that way, uninstall first and reinstall as above.
70
75
 
71
76
  A profile without a browser can still use the model route. It just has no settings panel and no composer balance.
72
77
 
@@ -142,13 +147,19 @@ Claude Code CLI → temporary local MCP web_search → selected PackyCode DeepSe
142
147
  | `gpt-6.1-sol` | HTTP, Responses, images allowed | Cannot be off; `low` / `medium` / `high` / `xhigh` / `max` | `codex`, `codex-sale` |
143
148
  | `gpt-6-astra` | Same | Same | Same |
144
149
  | `grok-4.7` (Grok 4.7) | HTTP, Responses, text/image input; 500K context | Cannot be off; `low` / `medium` / `high` / `xhigh` | The Grok coverage read from the gateway in Settings |
150
+ | `gemini-4-argon` (Gemini 4 Argon) | HTTP, native Gemini streaming, text/image input; 1,048,576 context, 65,536 output | Cannot be off; `low` / `medium` / `high` | `gemini-slb` |
151
+ | `gemini-3.8-flash` (Gemini 3.8 Flash) | HTTP, native Gemini streaming, text/image input; 1,048,576 input, 65,536 output | Cannot be off; `low` / `medium` / `high` | `gemini-slb` |
145
152
  | `claude-opus-5-5` | Local Claude Code CLI | The picker offers the same levels; the CLI chooses the actual effort | A group the official CLI is allowed to use; see below |
146
153
  | `claude-sonnet-5-5` | Same | Same | Same |
147
154
  | `claude-haiku-5-5` | Same; session titles always use this model | Same | Same |
148
155
 
149
156
  A model in the list does not mean the current token group allows it. When changing groups, do not drop the group that serves the model you still want.
150
157
 
151
- All seven models in the current catalog declare text and image input. DeepSeek Flash follows the [official vision guide](https://api-docs.deepseek.com/guides/vision/); images are read through DSH's attachment service and sent with the Chat Completions request.
158
+ All nine models in the current catalog declare text and image input. DeepSeek Flash follows the [official vision guide](https://api-docs.deepseek.com/guides/vision/); images are read through DSH's attachment service and sent with the Chat Completions request.
159
+
160
+ Gemini capabilities and limits follow Google's model pages: [Gemini 4 Argon](https://ai.google.dev/gemini-api/docs/models/gemini-4-argon), [Gemini 3.8 Flash](https://ai.google.dev/gemini-api/docs/models/gemini-3.8-flash). Both models use the native Gemini protocol through `/v1beta/models/{model}:streamGenerateContent`, streaming text, thinking and tool calls; images are read through DSH's attachment service. Thinking levels are sent with `thinkingLevel`. Both routes use the single-stream output limit without automatic continuation beyond 65,536 tokens. Available endpoints and groups follow the [PackyAPI live model catalog](https://www.packyapi.com/api/pricing).
161
+
162
+ **Flash native tool continuation:** The gateway sometimes refuses an unchanged valid signature with `Corrupted thought signature` / `Invalid thought signature`. For a current signed tool continuation returning HTTP 400 before any stream event, the plugin waits one second and resends once with the same key, signature and request. A second failure is returned unchanged; the protocol and model stay the same. Text, images and tool continuation passed live checks. This mitigates intermittent gateway refusals, rather than repairing persistent upstream signature errors.
152
163
 
153
164
  Grok 4.7's context and reasoning levels follow the [official guide](https://docs.x.ai/developers/grok-4-7). There is no separate text-output limit; the plugin uses the context size as its numeric output ceiling, bounded by the remaining context per request.
154
165
 
@@ -179,10 +190,22 @@ Only GPT Responses requests keep `prompt_cache_key`; other Responses models omit
179
190
 
180
191
  **Update:** open **Settings → PackyCode** and, when a newer version is listed, click **Update plugin**. It installs that exact version into the owning profile with DSH's bundled pnpm and reads `package.json` back before reporting success. A local `link:` checkout does not get the button. Then quit and reopen the desktop application. A page refresh does not reload the Host. Reinstalling `dsh-packycode-api` from the **Plugins** page does the same job.
181
192
 
193
+ **Coming back from a local-path install, uninstall first.** An update is a `pnpm add`: it rewrites the files inside the package directory and leaves a `node_modules` that an earlier local-path install left there. That older tree keeps shadowing the packages the DSH installation provides, so the plugin row fails to load with no visible reason. **Uninstall** runs `pnpm remove`, which deletes the package directory outright — only a reinstall after that is clean. `node scripts/install-check.mjs` reports whether a profile is in that state.
194
+
182
195
  **Uninstall:** if you used PackyCode web search, first switch **Settings → PackyCode → Web search** back to the default. Then remove this plugin on the **Plugins** page and restart the desktop application. Otherwise `cordis.patch.yml` keeps a web entry naming a provider that no longer exists, and `web_search` fails with `configured web provider "packycode-api-auto" is not registered`; delete the block marked `dsh-packycode-api: web search provider` by hand to recover. Uninstalling does not remove other plugins, the profile, or the token in the PackyCode console.
183
196
 
184
197
  ## FAQ
185
198
 
199
+ **Installed, but Settings shows no PackyCode at all?** The plugin row failed to load at startup, and DSH only records that in the host log — the settings page shows no reason. Check with `node scripts/install-check.mjs` from this repository (read-only, no network); by hand, look for this directory:
200
+
201
+ ```sh
202
+ ls ~/.dsh/profiles/desktop/node_modules/dsh-packycode-api/node_modules
203
+ ```
204
+
205
+ Output means a leftover tree: a registry install ships no `node_modules`, so this one usually comes from an earlier local-path install (`file:`, or a bare path instead of `link:`). It sits inside the profile directory, and DSH resolves a plugin's imports from inside the profile first, so the plugin gets those old copies — for example `@deepseek-ai/schemastery` 3.18.2, which has no `Schema.volatile()`, so the module throws while it is imported. When an import fails the Loader creates no fiber for that row and no client bundle is composed either, so the model route, the RPC channel, and the settings panel all stay absent.
206
+
207
+ Recovery: **uninstall** the plugin on the **Plugins** page, **install** `dsh-packycode-api` again, then quit and reopen the desktop application. Uninstall runs `pnpm remove` and deletes the whole package directory; reinstalling or clicking **Update** only rewrites files and keeps the leftover tree. Deleting that `node_modules` by hand and restarting works too.
208
+
186
209
  **Web search says "no stored API key's group covers …"?** The search backend's model is not in your token groups (or the gateway currently routes it to another supplier). Pick the other backend under **Settings → PackyCode → Web search**, or add a key whose group covers it.
187
210
 
188
211
  **One model fails, but the key is valid?** Look at that key's groups and ✓ / ✕ under **Accounts and keys**. When its groups do not cover the model, use a key whose groups do, or add the group to the key in the PackyCode console. With such a key stored, the plugin runs that model on it automatically. A group refusal never disables a key.
@@ -226,6 +249,8 @@ pnpm run check
226
249
 
227
250
  `node scripts/cli-plan-check.mjs` checks plan mode with the real CLI and a local fake gateway: stdio handoff, concurrent plan ownership, invocation-only file fallback, stale-plan exclusion, actual denial limits and cleanup. It reads no real credentials, spends no quota and starts no DSH instance.
228
251
 
252
+ `node scripts/install-check.mjs` diagnoses an installed plugin that never appears: it reads one profile, lists any host package shadowed by a leftover `node_modules` inside an installed package (with versions) plus the recovery steps, and exits 1 when it finds one. `--profile <name>`, `--home <dir>`, and `--json` are available.
253
+
229
254
  Read [AGENTS.md](AGENTS.md) before agent-assisted changes or a release. Publishing is the [Publish workflow](.github/workflows/publish.yml), triggered by pushing a `v*` tag. Do not run `npm publish` locally.
230
255
 
231
256
  ## License
package/README.md CHANGED
@@ -33,7 +33,8 @@
33
33
  | **网页搜索可走 PackyCode** | 可选:DSH 的 `web_search` 改用 PackyCode 搜索,后端在 DeepSeek 与 GPT 之间切换;可只对 PackyCode 模型生效 |
34
34
  | **凭据不离开本机** | Key 只存在 DSH 凭据文件里。浏览器只能看到备注、状态和余额,拿不到 Key |
35
35
 
36
- <img width="790" height="332" alt="截屏2026-10-13 10 08 27" src="https://github.com/user-attachments/assets/2faa7667-300c-4f12-bb94-aecc35c5cb7f" />
36
+ <img width="774" height="455" alt="截屏2026-10-15 10 34 28" src="https://github.com/user-attachments/assets/060c4192-00ea-40e6-9b85-bbec605b5c31" />
37
+ <img width="575" height="522" alt="截屏2026-10-15 10 34 49" src="https://github.com/user-attachments/assets/89cf89ca-fce3-427d-95cb-94fa2b05d6aa" />
37
38
  <img width="272" height="425" alt="截屏2026-10-13 10 07 46" src="https://github.com/user-attachments/assets/040fc57a-2dd2-4195-9bbc-5fa3357d1eb1" />
38
39
 
39
40
 
@@ -49,7 +50,7 @@
49
50
  dsh-packycode-api
50
51
  ```
51
52
 
52
- 在**安装源**中选择**官方源**,然后点击**安装**。需要固定版本时填入 `dsh-packycode-api@1.1.7`。
53
+ 在**安装源**中选择**官方源**,然后点击**安装**。需要固定版本时填入 `dsh-packycode-api@1.1.9`。
53
54
 
54
55
  首次安装后**无需重启 DSH**,可直接打开**设置 → PackyCode** 添加 Key。更新已加载的旧版时,按「插件版本」卡片的重启提示操作。
55
56
 
@@ -61,16 +62,16 @@ dsh-packycode-api
61
62
 
62
63
  ### 3. 选择模型
63
64
 
64
- 回到对话,在模型选择器里选 `PackyCode` 分组下的模型。`deepseek-flash`、`gpt-6.1-sol`、`gpt-6-astra` 和 `grok-4.7` 通过 HTTP 调用;三个 Claude 模型还需要本机装有 Claude Code CLI,见下文。Key 的分组必须覆盖所选模型。
65
+ 回到对话,在模型选择器里选 `PackyCode` 分组下的模型。`deepseek-flash`、`gpt-6.1-sol`、`gpt-6-astra`、`grok-4.7`、`gemini-4-argon` 和 `gemini-3.8-flash` 通过 HTTP 调用;三个 Claude 模型还需要本机装有 Claude Code CLI,见下文。Key 的分组必须覆盖所选模型。
65
66
 
66
67
  <details>
67
68
  <summary>检查安装 / 其它 profile / 本地目录</summary>
68
69
 
69
70
  桌面应用的**插件**页可以看到已安装状态与版本,也可以在那里启用、停用或卸载。
70
71
 
71
- **仅用于非 desktop profile:** `dsh plugin --profile <name> add dsh-packycode-api@1.1.7`。不要对 `desktop` 使用它 —— CLI 会直接拒绝(`profile "desktop" is managed exclusively by the Electron application`)。
72
+ **仅用于非 desktop profile:** `dsh plugin --profile <name> add dsh-packycode-api@1.1.9`。不要对 `desktop` 使用它 —— CLI 会直接拒绝(`profile "desktop" is managed exclusively by the Electron application`)。
72
73
 
73
- 本地开发可以用绝对路径:`dsh plugin --profile <name> add link:/path/to/dsh-packycode-api`。
74
+ 本地开发要加 `link:` 前缀:`dsh plugin --profile <name> add link:/path/to/dsh-packycode-api`。**不要直接写路径(或 `file:`)** —— 那样的安装会把整个仓库目录(连同 `node_modules`)复制进 profile,之后它会在 profile 内部挡住 DSH 自带的包,插件看起来像没装,见「常见问题」。已经那样装过的话,先卸载再按上面的方式重装。
74
75
 
75
76
  没有浏览器的 profile 也能走模型路由,只是看不到设置页和输入框额度。
76
77
 
@@ -146,13 +147,19 @@ Claude Code CLI → 本机临时 MCP web_search → 插件选定的 DeepSeek/GPT
146
147
  | `gpt-6.1-sol` | HTTP,Responses,可带图 | 不能关;`low` / `medium` / `high` / `xhigh` / `max` | `codex`、`codex-sale` |
147
148
  | `gpt-6-astra` | 同上 | 同上 | 同上 |
148
149
  | `grok-4.7`(Grok 4.7) | HTTP,Responses,可带图;500k 上下文 | 不能关;`low` / `medium` / `high` / `xhigh` | 以设置页从网关读取的 Grok 分组覆盖为准 |
150
+ | `gemini-4-argon`(Gemini 4 Argon) | HTTP,Gemini 原生流式,可带图;1,048,576 上下文,65,536 输出 | 不能关;`low` / `medium` / `high` | `gemini-slb` |
151
+ | `gemini-3.8-flash`(Gemini 3.8 Flash) | HTTP,Gemini 原生流式,可带图;1,048,576 输入,65,536 输出 | 不能关;`low` / `medium` / `high` | `gemini-slb` |
149
152
  | `claude-opus-5-5` | 本机 Claude Code CLI | 选择器档位同上,实际档位由 CLI 决定 | 能给官方 CLI 用的分组,比如cc分组 |
150
153
  | `claude-sonnet-5-5` | 同上 | 同上 | 同上 |
151
154
  | `claude-haiku-5-5` | 同上;会话标题也固定走它 | 同上 | 同上 |
152
155
 
153
156
  列表里看得到模型,不代表当前令牌分组一定放行。换分组时不要把对应分组一起去掉。
154
157
 
155
- 当前目录中的 7 个模型都声明了文字和图像输入。DeepSeek Flash 的识图能力按 [官方图像理解文档](https://api-docs.deepseek.com/zh-cn/guides/vision/) 配置;图片通过 DSH 附件服务读取,再随 chat/completions 请求发送。
158
+ 当前目录中的 9 个模型都声明了文字和图像输入。DeepSeek Flash 的识图能力按 [官方图像理解文档](https://api-docs.deepseek.com/zh-cn/guides/vision/) 配置;图片通过 DSH 附件服务读取,再随 chat/completions 请求发送。
159
+
160
+ Gemini 的能力与限制来自 Google 官方模型页:[Gemini 4 Argon](https://ai.google.dev/gemini-api/docs/models/gemini-4-argon)、[Gemini 3.8 Flash](https://ai.google.dev/gemini-api/docs/models/gemini-3.8-flash)。两款模型统一使用 Gemini 原生协议,通过 `/v1beta/models/{model}:streamGenerateContent` 流式返回文字、思考和工具调用,图片仍由 DSH 附件服务读取。思考档位通过 `thinkingLevel` 传入。两者均使用单次流式输出上限,不执行超过 65,536 token 的自动续写。可用端点与分组以 [PackyAPI 实时模型目录](https://www.packyapi.com/api/pricing) 为准。
161
+
162
+ **Flash 原生工具续聊:** 网关偶尔会拒绝原样回传的有效签名,返回 `Corrupted thought signature` / `Invalid thought signature`。插件仅在当前工具续聊、HTTP 400 且尚未输出任何流事件时,等待 1 秒,用相同 Key、签名及请求原样重发一次;再次失败时仍返回原错误,不更换协议或模型。文字、图片和工具续聊均已实测;该处理缓解网关的间歇性拒绝,不能保证修复持续性的上游签名错误。
156
163
 
157
164
  Grok 4.7 的上下文与推理档位来自 [官方文档](https://docs.x.ai/developers/grok-4-7)。官方没有单独限制文字输出长度;插件将输出上限设为上下文大小,并按每次请求的剩余上下文收紧。
158
165
 
@@ -183,10 +190,22 @@ Responses 请求只有 GPT 模型保留 `prompt_cache_key`,其他 Responses
183
190
 
184
191
  **更新:** 打开 **设置 → PackyCode**,有新版本时点「更新插件」。它会在当前 profile 目录里用 DSH 自带的 pnpm 安装精确版本,并读回 `package.json` 确认版本落地。本地 `link:` 开发安装不会出现这个按钮。完成后完全退出并重新打开桌面应用。只刷新页面不会重载 Host。也可以在**插件**页重新安装 `dsh-packycode-api`。
185
192
 
193
+ **从本地路径安装切回官方源时,必须先卸载。** 「更新」是 `pnpm add`,它只重写包目录里的文件,不会删除更早一次本地路径安装留在包目录内的 `node_modules`;那份旧依赖会继续挡住 DSH 自带的包,插件行加载失败且看不出原因。「卸载」执行的是 `pnpm remove`,会删掉整个包目录,重装才是干净的。用 `node scripts/install-check.mjs` 可以确认当前 profile 是否处于这个状态。
194
+
186
195
  **卸载:** 用过 PackyCode 网页搜索的话,先在 **设置 → PackyCode → 网页搜索** 切回「不接管」,再到**插件**页卸载本插件,然后重启桌面应用。否则 `cordis.patch.yml` 里会留下指向已卸载后端的 web 配置,`web_search` 会报 `configured web provider "packycode-api-auto" is not registered`;这时手动删掉带 `dsh-packycode-api: web search provider` 标记的那一段即可。卸载不会删除其它插件、整个 profile,也不会撤销 PackyCode 控制台里的令牌。
187
196
 
188
197
  ## 常见问题
189
198
 
199
+ **装好了,但设置里完全没有 PackyCode 这一项?** 说明插件行在启动时加载失败了 —— DSH 只在宿主日志里记录这件事,设置页不会显示原因。先用仓库里的 `node scripts/install-check.mjs` 查一次(只读,不发网络请求);手动检查则是看这个目录是否存在:
200
+
201
+ ```sh
202
+ ls ~/.dsh/profiles/desktop/node_modules/dsh-packycode-api/node_modules
203
+ ```
204
+
205
+ 有输出就是残留:从 npm 安装的包不会自带 `node_modules`,这一份通常来自更早一次「本地路径」安装(`file:` 或直接写路径,而不是 `link:`)。它位于 profile 目录内部,而 DSH 解析插件导入时优先使用 profile 内的副本,所以插件拿到的仍是那份旧依赖 —— 例如 `@deepseek-ai/schemastery` 3.18.2 没有 `Schema.volatile()`,模块在导入阶段就抛错。导入失败时 Loader 不会为这一行创建 fiber,客户端 bundle 也不会被组合,于是模型路由、RPC 通道和设置页全都不会出现。
206
+
207
+ 修复:到**插件**页**卸载**本插件,再重新**安装** `dsh-packycode-api`,然后完全退出并重新打开桌面应用。卸载执行的是 `pnpm remove`,会删掉整个包目录;直接重装或点「更新」只重写文件,不会清掉这份残留。也可以手动删掉上面那个 `node_modules` 再重启。
208
+
190
209
  **某个模型不可用,但 Key 是有效的?** 看「账户与 Key」里这个 Key 的分组和 ✓ / ✕:分组不覆盖这个模型时,换一个分组合适的 Key,或在 PackyCode 控制台给 Key 加上对应分组。存了分组合适的 Key 时,插件会自动用它跑这个模型。分组拒绝不会停用 Key。
191
210
 
192
211
  **Claude 报「PackyCode 的上游拒绝了这次 Claude Code CLI 请求」或「只接受官方 Claude Code CLI」?** 最常见的原因是这一轮用的 Key 没有 Claude 分组:PackyCode 对这种 Key 也会回这句话(或 400 `当前令牌未覆盖供应商`)。现在报错会直接写明「本轮使用的 Key「…」所在分组(…)不包含能用 claude-… 的分组」,并说明不需要更新 CLI。只有 Key 确实有 Claude 分组时仍被拒,才需要按报错里的提示检查 CLI 版本或联系 PackyCode。
@@ -230,6 +249,8 @@ Claude CLI 的 MCP 接口验证用 `node scripts/cli-search-check.mjs`:运行
230
249
 
231
250
  计划模式另用 `node scripts/cli-plan-check.mjs` 验证:真实 CLI 对接本地模拟网关,检查 stdio 计划提交、并发计划归属、本轮文件回退、旧计划隔离、实际拒绝次数上限和清理;不读取真实凭据、不消费额度、不启动 DSH。
232
251
 
252
+ 排查「装了却看不到插件」用 `node scripts/install-check.mjs`:它只读当前 profile,列出被包内残留 `node_modules` 挡住的宿主包(含版本)和对应的修复步骤,发现问题时以退出码 1 结束;`--profile <name>`、`--home <dir>` 可指定其它 profile,`--json` 输出结构化结果。
253
+
233
254
  使用 Agent 修改或发布时,先读 [AGENTS.md](AGENTS.md)。发布由推送 `v*` 标签触发 [Publish workflow](.github/workflows/publish.yml),不要在本机执行 `npm publish`。
234
255
 
235
256
  ## License
package/lib/client.js CHANGED
@@ -498,6 +498,7 @@ window.__ModuleLoader__.load({
498
498
  ["DeepSeek", ["deepseek-flash"]],
499
499
  ["GPT", ["gpt-6.1-sol", "gpt-6-astra"]],
500
500
  ["Grok", ["grok-4.7"]],
501
+ ["Gemini", ["gemini-4-argon", "gemini-3.8-flash"]],
501
502
  ["Claude", [
502
503
  "claude-opus-5-5",
503
504
  "claude-sonnet-5-5",
package/lib/index.js CHANGED
@@ -11,10 +11,13 @@ import { defineTool } from "@deepseek-ai/dsh-tools";
11
11
  import { credentialKey } from "@deepseek-ai/dsh-credentials";
12
12
  import { fileURLToPath } from "node:url";
13
13
  import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
14
- import { createProvider } from "@earendil-works/pi-ai";
14
+ import { setTimeout as setTimeout$1 } from "node:timers/promises";
15
+ import { lazyStream } from "@earendil-works/pi-ai/api/lazy";
16
+ import { clampThinkingLevel, createProvider } from "@earendil-works/pi-ai";
15
17
  import { anthropicMessagesApi } from "@earendil-works/pi-ai/api/anthropic-messages.lazy";
16
18
  import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy";
17
19
  import { openAIResponsesApi } from "@earendil-works/pi-ai/api/openai-responses.lazy";
20
+ import { googleGenerativeAIApi } from "@earendil-works/pi-ai/api/google-generative-ai.lazy";
18
21
  import { AsyncLocalStorage } from "node:async_hooks";
19
22
  //#region src/request.js
20
23
  /**
@@ -544,8 +547,9 @@ function createBalanceReader({ listKeys, getHosts, fetchImpl = ambientFetch, now
544
547
  * model does not support is refused before the request is sent: Claude Opus
545
548
  * 5.5 (platform.claude.com effort docs), GPT-6.1 Sol and GPT-6 Astra
546
549
  * `reasoning.effort` (developers.openai.com model pages), DeepSeek thinking
547
- * mode (`thinking` toggle plus `low|high|max` effort), and Grok 4.7
548
- * (docs.x.ai/developers/grok-4-7).
550
+ * mode (`thinking` toggle plus `low|high|max` effort), Grok 4.7
551
+ * (docs.x.ai/developers/grok-4-7), and Gemini 4 Argon
552
+ * (ai.google.dev/gemini-api/docs/models/gemini-4-argon and docs/openai).
549
553
  *
550
554
  * @module dsh-packycode-api/catalog
551
555
  */
@@ -559,6 +563,7 @@ const NO_COST = Object.freeze({
559
563
  const COMPLETIONS_BASE = `${DEFAULT_API_HOST}/v1`;
560
564
  const RESPONSES_BASE = `${DEFAULT_API_HOST}/v1`;
561
565
  const MESSAGES_BASE = DEFAULT_API_HOST;
566
+ const GEMINI_BASE = `${DEFAULT_API_HOST}/v1beta`;
562
567
  /**
563
568
  * pi-ai thinking-level maps. A level pinned to `null` is unsupported; an
564
569
  * absent key keeps pi-ai's own default for that level, which is how `off`
@@ -592,6 +597,12 @@ const GROK_LEVELS = Object.freeze({
592
597
  ...ALWAYS_THINKING_LEVELS,
593
598
  max: null
594
599
  });
600
+ /** Gemini 4 Argon and 3.8 Flash support low | medium | high, with no off. */
601
+ const GEMINI_LEVELS = Object.freeze({
602
+ ...ALWAYS_THINKING_LEVELS,
603
+ xhigh: null,
604
+ max: null
605
+ });
595
606
  const PACKYCODE_MODELS = Object.freeze([
596
607
  Object.freeze({
597
608
  id: "deepseek-flash",
@@ -652,6 +663,32 @@ const PACKYCODE_MODELS = Object.freeze([
652
663
  thinkingLevelMap: GROK_LEVELS,
653
664
  compat: Object.freeze({ supportsLongCacheRetention: false })
654
665
  }),
666
+ Object.freeze({
667
+ id: "gemini-4-argon",
668
+ name: "Gemini 4 Argon",
669
+ api: "google-generative-ai",
670
+ provider: PROVIDER,
671
+ baseUrl: GEMINI_BASE,
672
+ reasoning: true,
673
+ input: Object.freeze(["text", "image"]),
674
+ cost: NO_COST,
675
+ contextWindow: 1048576,
676
+ maxTokens: 65536,
677
+ thinkingLevelMap: GEMINI_LEVELS
678
+ }),
679
+ Object.freeze({
680
+ id: "gemini-3.8-flash",
681
+ name: "Gemini 3.8 Flash",
682
+ api: "google-generative-ai",
683
+ provider: PROVIDER,
684
+ baseUrl: GEMINI_BASE,
685
+ reasoning: true,
686
+ input: Object.freeze(["text", "image"]),
687
+ cost: NO_COST,
688
+ contextWindow: 1048576,
689
+ maxTokens: 65536,
690
+ thinkingLevelMap: GEMINI_LEVELS
691
+ }),
655
692
  Object.freeze({
656
693
  id: "claude-opus-5-5",
657
694
  name: "Claude Opus 5.5",
@@ -4933,10 +4970,19 @@ var PackyCodeKeyRing = class {
4933
4970
  */
4934
4971
  /**
4935
4972
  * Mark a preference editable without restarting the plugin. DSH 0.2 stores
4936
- * plugin preferences as volatile Config fields and writes only those. The
4937
- * peer dependency requires the schema version that implements `volatile()`.
4938
- */
4939
- const live = (schema) => schema.volatile();
4973
+ * plugin preferences as volatile Config fields and writes only those, so the
4974
+ * schema version that implements `volatile()` is a peer requirement.
4975
+ *
4976
+ * Name the cause when it is missing instead of letting a bare
4977
+ * `schema.volatile is not a function` reach the host log: the Loader creates
4978
+ * no fiber for a plugin whose module throws while it is imported, so DSH shows
4979
+ * nothing at all — no settings section, no model route, no RPC channel — and
4980
+ * the only explanation is that one log line.
4981
+ */
4982
+ const live = (schema) => {
4983
+ if (typeof schema?.volatile !== "function") throw new Error("PackyCode needs @deepseek-ai/schemastery ^3.18.4, which implements Schema.volatile(); the copy resolved for this plugin is older. A plugin installed from a registry has no node_modules of its own, so this usually means a stale dependency tree inside the installed package directory (for example <profile>/node_modules/dsh-packycode-api/node_modules, left by an earlier local-path install) is shadowing the DSH installation. Remove that directory, or uninstall the plugin and install it again, then restart DSH.");
4984
+ return schema.volatile();
4985
+ };
4940
4986
  /** Read a Config field that may arrive as a volatile reference. */
4941
4987
  const fieldValue = (value) => value !== null && typeof value === "object" && typeof value.get === "function" ? value.get() : value;
4942
4988
  /** A preference target nothing can write to yet. */
@@ -5366,13 +5412,119 @@ function createPackyCodePluginManager({ fetchImpl = ambientFetch, runCommand = s
5366
5412
  });
5367
5413
  }
5368
5414
  //#endregion
5415
+ //#region src/gemini-replay.js
5416
+ /** A bounded resend for the gateway's intermittent Flash signature refusal. */
5417
+ const waitForRetry = (ms, signal) => setTimeout$1(ms, void 0, { signal });
5418
+ /** Only a current-turn result for a signed same-model native call qualifies. */
5419
+ function hasSignedResult(model, context) {
5420
+ if (model?.id !== "gemini-3.8-flash" || model.api !== "google-generative-ai") return false;
5421
+ const messages = context?.messages ?? [];
5422
+ const calls = /* @__PURE__ */ new Set();
5423
+ for (const message of messages.slice(messages.findLastIndex((item) => item.role === "user") + 1)) {
5424
+ if (message.role === "assistant" && message.provider === model.provider && message.model === model.id && message.api === model.api) for (const block of Array.isArray(message.content) ? message.content : []) {
5425
+ const signature = block.thoughtSignature;
5426
+ if (block.type === "toolCall" && typeof block.id === "string" && typeof signature === "string" && signature.length % 4 === 0 && /^[A-Za-z0-9+/]+={0,2}$/u.test(signature)) calls.add(block.id);
5427
+ }
5428
+ if (message.role === "toolResult" && calls.has(message.toolCallId)) return true;
5429
+ }
5430
+ return false;
5431
+ }
5432
+ function isSignatureRefusal(event) {
5433
+ if (event.type !== "error" || event.reason !== "error" || event.error.content?.length) return false;
5434
+ const text = event.error.errorMessage ?? "";
5435
+ return /\b400\b/u.test(text) && /\b(?:Corrupted|Invalid) thought signature\b/iu.test(text) && !/\b(?:401|403|408|409|429|5\d\d)\b/u.test(text) && classifyGatewayFailure(text) === void 0;
5436
+ }
5437
+ /**
5438
+ * The caller closes over one resolved model/context/key/session. Reopening it
5439
+ * resends that request once; it never chooses a different key or changes the
5440
+ * signature. SDK retries remain disabled by the host. Its ordinary retry
5441
+ * policy still receives any terminal failure after this one compatibility
5442
+ * resend. Never replay after even a start event has reached the consumer.
5443
+ */
5444
+ function guardGeminiReplay(open, { model, context, signal, wait = waitForRetry } = {}) {
5445
+ const initial = open();
5446
+ if (!hasSignedResult(model, context) || signal?.aborted) return initial;
5447
+ let finalMessage;
5448
+ return lazyStream(model, async () => ({
5449
+ async *[Symbol.asyncIterator]() {
5450
+ for (let attempt = 0; attempt < 2; attempt += 1) {
5451
+ const source = attempt === 0 ? initial : open();
5452
+ let observed = false;
5453
+ let refused;
5454
+ for await (const event of source) if (attempt === 0 && !observed && isSignatureRefusal(event)) refused = event;
5455
+ else {
5456
+ observed = true;
5457
+ yield event;
5458
+ }
5459
+ finalMessage = await source.result();
5460
+ if (refused === void 0) return;
5461
+ try {
5462
+ await wait(1e3, signal);
5463
+ signal?.throwIfAborted();
5464
+ } catch (error) {
5465
+ if (!signal?.aborted) throw error;
5466
+ finalMessage = {
5467
+ ...refused.error,
5468
+ stopReason: "aborted",
5469
+ errorMessage: "Request aborted"
5470
+ };
5471
+ yield {
5472
+ type: "error",
5473
+ reason: "aborted",
5474
+ error: finalMessage
5475
+ };
5476
+ return;
5477
+ }
5478
+ }
5479
+ },
5480
+ result: async () => finalMessage
5481
+ }));
5482
+ }
5483
+ //#endregion
5484
+ //#region src/gemini-api.js
5485
+ /** Gemini's native API, including level-based thinking on older pi-ai hosts. */
5486
+ /**
5487
+ * pi-ai 0.85 treats Argon as a budget-based model, and defaults Flash to
5488
+ * MINIMAL when reasoning is omitted. Both models support LOW/MEDIUM/HIGH,
5489
+ * default to MEDIUM and cannot disable thinking. Keep the native SDK stream,
5490
+ * credentials, tools and replay signatures; correct only that config.
5491
+ */
5492
+ function createGeminiApi() {
5493
+ const api = googleGenerativeAIApi();
5494
+ return {
5495
+ ...api,
5496
+ streamSimple(model, context, options) {
5497
+ if (!["gemini-4-argon", "gemini-3.8-flash"].includes(model.id)) return api.streamSimple(model, context, options);
5498
+ const level = options?.reasoning === void 0 ? "medium" : clampThinkingLevel(model, options.reasoning);
5499
+ const wireLevel = (model.thinkingLevelMap?.[level] ?? level).toUpperCase();
5500
+ return api.streamSimple(model, context, {
5501
+ ...options,
5502
+ onPayload: async (payload, descriptor) => {
5503
+ const { thinkingBudget: _budget, ...thinking } = payload.config?.thinkingConfig ?? {};
5504
+ const normalized = {
5505
+ ...payload,
5506
+ config: {
5507
+ ...payload.config,
5508
+ thinkingConfig: {
5509
+ ...thinking,
5510
+ thinkingLevel: wireLevel
5511
+ }
5512
+ }
5513
+ };
5514
+ return await options?.onPayload?.(normalized, descriptor) ?? normalized;
5515
+ }
5516
+ });
5517
+ }
5518
+ };
5519
+ }
5520
+ //#endregion
5369
5521
  //#region src/provider.js
5370
5522
  /**
5371
5523
  * The PackyCode pi-ai provider, built in code so the user configures no route.
5372
5524
  *
5373
5525
  * `createProvider` dispatches on `model.api`, which is what lets one route
5374
- * carry a chat/completions model, a Responses model, and an Anthropic Messages
5375
- * model — the gateway refuses each of them over the other protocols.
5526
+ * carry Chat Completions, Responses, Anthropic Messages and native Gemini
5527
+ * models — the gateway refuses some of them over other protocols.
5376
5528
  *
5377
5529
  * @module dsh-packycode-api/provider
5378
5530
  */
@@ -5404,7 +5556,8 @@ function createPackyCodeProvider() {
5404
5556
  api: {
5405
5557
  "openai-completions": openAICompletionsApi(),
5406
5558
  "openai-responses": openAIResponsesApi(),
5407
- "anthropic-messages": anthropicMessagesApi()
5559
+ "anthropic-messages": anthropicMessagesApi(),
5560
+ "google-generative-ai": createGeminiApi()
5408
5561
  }
5409
5562
  });
5410
5563
  const requestOptions = (model, options) => model.api === "openai-responses" && !model.id.startsWith("gpt-") ? {
@@ -5496,7 +5649,7 @@ function guardKeyRotation(stream, { onKeyFailure, key } = {}) {
5496
5649
  //#region src/route.js
5497
5650
  /**
5498
5651
  * The HTTP model route: the pi-ai provider profile and the adapter that
5499
- * serves the catalog's HTTP models (DeepSeek, GPT and Grok) with the ring's keys.
5652
+ * serves the catalog's HTTP models (DeepSeek, GPT, Grok and Gemini) with the ring's keys.
5500
5653
  *
5501
5654
  * @module dsh-packycode-api/route
5502
5655
  */
@@ -5608,7 +5761,11 @@ function createRouteAdapter({ ring, keys, fallbackSessionId, resolveAttachments,
5608
5761
  };
5609
5762
  const guarded = (method) => (model, context, options) => {
5610
5763
  const routed = requestOptions(model, options);
5611
- return guardKeyRotation(raw[method](model, context, routed), {
5764
+ return guardKeyRotation(guardGeminiReplay(() => raw[method](model, context, routed), {
5765
+ model,
5766
+ context,
5767
+ signal: routed?.signal
5768
+ }), {
5612
5769
  key: routed?.apiKey,
5613
5770
  onKeyFailure: keys.onKeyFailure
5614
5771
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-packycode-api",
3
- "version": "1.1.7",
3
+ "version": "1.1.9",
4
4
  "description": "Use a PackyCode (PackyAPI) API key in DeepSeek Harness through a built-in multi-protocol model route with key rotation and balance display",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",