dsh-packycode-api 1.0.6 → 1.1.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,32 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.1.0
4
+
5
+ - **Keys are chosen by their groups.** A colleague's Claude turns kept failing with "PackyCode 的上游拒绝了这次 Claude Code CLI 请求……请更新 CLI" because the active key had no Claude Code group; PackyCode answers such a key with the same "only the official Claude Code CLI" wording as a real client fault. The plugin now reads each key's groups and routes by them:
6
+ - A new group reader asks the gateway itself: a request for a model id no gateway routes (`dsh-packycode-group-probe`, `max_tokens: 1`) is refused before any upstream call with the token's groups (`已选分组=[codex deepseek-officially kimi-sale]`), nothing is billed. `/v1/models` adds the key's reachable models, and the public `/api/pricing` maps each catalog model to its groups (`claude-opus-5-5`: `cc` / `cc-expensive` / `cc-sale` / `claude-officially`; `claude-haiku-5-5` only `cc` and `claude-officially`) and gives each group's description. Pricing and every key are read concurrently, 4 keys at a time; the two per-key requests are independent evidence, and a key the gateway rejects (401 / 无效的令牌) counts as reaching nothing. Readings are cached 10 minutes, keep the last good groups when a read fails (marked stale), and never carry key material.
7
+ - When the reading shows the active key cannot reach a request's model and another stored key can, that request runs on that key, on all three paths: HTTP models (the stream wrapper swaps the resolved key per model), Claude CLI runs (`resolveCredential({ model })`), and web search (candidates ordered by coverage). The active pointer does not move, a key is passed over only on evidence, and with no better key the active one is used. A request with two or more usable keys waits at most 6 s for the first reading.
8
+ - Every Claude CLI turn names its key in the reasoning (`▸ 使用 Key「cc」(分组:cc)`, or `当前 Key「A」的分组不能用 claude-opus-5-5,本轮改用 Key「B」`, or a warning that no stored key has a Claude group) — by label only.
9
+ - A CLI refusal from a key without a Claude group — the 403 official-CLI wording or the 400 `当前令牌未覆盖供应商` — is explained as that: the key, its groups, the groups the model needs, and that the CLI does not need updating. The 400 names the groups itself; for the 403, a fresh reading is consulted when the run started before the groups were known. Status tokens are kept out of the sentence so no retry is spent on it. A key that does have a Claude group keeps the CLI explanation, now naming the key.
10
+ - New RPC endpoint `groups`; the reading is dropped when keys are added or removed or the hosts change.
11
+ - The key switch itself already applied per run: reproduced with the real CLI 2.1.39 against a capture gateway through the plugin's own wiring, the turn after **切到此 Key** sent the new key. A restart only appeared to fix it.
12
+ - **Settings panel redesign.** The balance list and the key list showed the same keys twice; one **账户与 Key** card now has the totals as tiles, then one row per key with its balance, a usage bar, its groups (hover for the description), a ✓ / ◐ / ✕ line per model family, and its actions. Web search uses segmented controls with the selected option's hint; the CLI card shows a status dot and a warning callout when the CLI is missing; the version card is one row. The stylesheet uses only host design tokens (`settings-card-fill/stroke`, `radius-*`, layer-1 inputs, primary/outline/danger buttons) so it follows light and dark themes; it referenced two tokens the host never defined (`--dsw-alias-border-default`, `--dsw-alias-state-warning-primary`) and painted cards, rows, buttons, and inputs the same grey. Copy and styles moved to `client-copy.js` and `client-style.js`; both languages carry the same keys, and English no longer shows full-width punctuation. Checked in a harness rendering the built bundle with DSH 0.2.0-rc.2's theme CSS, in both themes and languages.
13
+ - **Refactor.** `index.js` went from 714 to about 330 lines of composition: the HTTP route moved to `route.js`, key choice and rotation to `key-chooser.js`, the `claude_code` tool to `claude-tool.js`, both settings generations to `preferences.js`, and the profile-patch locator (patch path, base config, HMR transaction) next to the composition. The balance, group, and version readers share one cached single-flight reader (`cached-read.js`), and every host-side request goes through one bounded fetch (`request.js`: timeout plus caller signal, redirects refused, transport errors named). The balance reader now resolves fetch at call time like the others. Host helpers live in `hosts.js`; the coverage wording is shared from `failures.js`; test doubles live in `tests/helpers.mjs`. `scripts/live-check.mjs` now drives the plugin's own route composition instead of a hand-built copy, reports groups, separates the group pass-over from the rotation rehearsal, and takes `--with-search`. Public exports are unchanged apart from additions.
14
+ - `compatibility.json`: the 0.2.0-rc.2 note claimed `settings.installSection` exists on that line; it does not (its settings service only writes volatile fields), so host overrides are read-only there.
15
+ - Verified live through the built bundle: groups `codex, deepseek-officially, kimi-sale` with Claude unreachable; `deepseek-flash`, `gpt-6.1-sol`, and `gpt-6-astra` answered through the registered route; an invalid key in front was passed over with group evidence and disabled by rotation without; a Claude turn through the real CLI 2.1.295 with a key lacking a Claude group opened with the no-Claude-group warning and ended with the group explanation; both search backends answered. A full group reading took about 1 s (it took 15 s sequentially while `cf.api.fan` was resetting connections).
16
+
17
+ - **Web search through PackyCode.** The plugin registers two providers with DSH's `web` service: `packycode-api` searches through PackyCode for every request, and `packycode-api-auto` does so only for sessions running on a PackyCode model, handing every other request to the search provider the web row named before. **Settings → PackyCode → Web search** picks the routing (do not take over — the default — / auto / always) and the backend:
18
+ - `deepseek` (default): `deepseek-flash` over Anthropic Messages at `/v1/messages` with the `web_search_20250305` server tool, `max_uses` 3. Results come from `web_search_tool_result` blocks.
19
+ - `gpt`: `gpt-6.1-sol` over Responses at `/v1/responses` with the hosted `web_search` tool (`tool_choice: required`, `reasoning.effort: low`, list input, `prompt_cache_key`). Results come from `url_citation` annotations and the `web_search_call` actions.
20
+ - Both return the model's summary as `content`, with sources deduplicated by URL. A search uses only this plugin's ring keys, is bounded at 55 s, and refuses redirects. An exhausted or rejected key moves aside through the same sink as the model route and the next key is tried. A `model_not_found` / `当前令牌未覆盖供应商` refusal skips to the next key without disabling one, and finally says which backend to switch to. Gateway text is masked so a key never appears in an error.
21
+ - **How DSH reaches the providers.** Auto and Always maintain one marked `- id: web` entry in the running profile's `cordis.patch.yml` (`profileContext.patchPath`, or the owning profile found from the install), which DSH hot-applies. The entry is rewritten in place, written only when it changes, restates the web row's other scalar fields (a profile patch replaces the whole config), and records the base provider it replaced as `# fallback: …`. The runtime slot is never written: the Codex and Kimi subscription plugins rewrite it on every web restart, and their routers already fall back to the web row's base. A request that loops back to the auto provider from another router goes to `deepseek-official`. Writes run inside DSH's HMR transaction when one is free and unlocked when DSH refuses to nest it. Removing the entry (the default routing) leaves the rest of the file byte for byte.
22
+ - The preference is two new Config fields, `searchProvider` and `searchBackend`. On DSH 0.2 they are volatile fields written through `settings.update('packycode-api', …)` and followed through `loader/volatile-update`; the generated duplicate settings page is hidden. On DSH 0.1.5 they live in the installed settings section. New RPC endpoints `search/status` and `search/update` return only the preference, what is in effect, and whether Codex or Kimi routers are present.
23
+ - Verified live through the built bundle against `cf.api.fan`: the DeepSeek backend returned 10 sources in 5.4 s and the GPT backend 20 sources in 14.2 s for one query, with no key disabled. During the same session the gateway refused GPT requests from this key for a while as `当前令牌未覆盖供应商 "OpenAI"` and then served them again. Not yet exercised in a running DSH desktop session.
24
+
25
+ ## 1.0.7
26
+
27
+ - A `gpt-6-astra` session's title now runs on `gpt-6.1-sol`, in the same way a Claude session's title runs on `claude-haiku-5-5`. DSH sends each title request to the session's own model, so Astra was titling its own sessions. The reroute applies only to requests with `purpose: 'session-title'`, on both the prepared and direct dispatch paths, and both models need the same Codex group. Verified live through the real host adapter with the gateway request observed: the title request went to `gpt-6.1-sol` ("为 PackyCode 添加 GPT-6 Astra并发布"), and an ordinary turn stayed on `gpt-6-astra`.
28
+ - `claude-haiku-5-5` now declares a 1,000,000-token context window and 128,000 output tokens (it declared 200,000 and 64,000), matching Anthropic's [Claude Haiku 5.5 overview](https://platform.claude.com/docs/en/models/haiku-5-5/overview) and the other Claude 5.5 models. The protocol, base URL, and effort levels are unchanged. Haiku runs through the local Claude Code CLI, which manages its own context. These figures set what DSH shows and how it budgets the session, such as when it compacts.
29
+
3
30
  ## 1.0.6
4
31
 
5
32
  - New model `gpt-6-astra` (GPT-6 Astra), on the same route as `gpt-6.1-sol`: `openai-responses` at `https://cf.api.fan/v1`, text and image input, 1,050,000-token context, 128,000 output tokens, and `reasoning.effort` `low` / `medium` / `high` / `xhigh` / `max` (reasoning cannot be turned off). All of these are from OpenAI's [model page](https://developers.openai.com/api/docs/models/gpt-6-astra). The gateway lists it in `/v1/models` as `gpt-6-astra`, and it needs a key whose group covers Codex (`codex`, `codex-sale`). Verified live through the built bundle: `live-check` answered `ok` from it alongside `deepseek-flash` and `gpt-6.1-sol`, and each of the five effort levels answered `ok`. One `medium` request first hit a transient `Connection error` and answered on retry.
package/README.en.md CHANGED
@@ -26,8 +26,10 @@ The `PackyCode` model group ships with the plugin. No YAML edits and no separate
26
26
  | --- | --- |
27
27
  | **Built-in model route** | Adds a `PackyCode` group to the model picker. No provider configuration and no `cordis.patch.yml` edits |
28
28
  | **Keys used in order** | An exhausted or rejected key is disabled and the next usable key takes over; the failed step is retried on that key |
29
- | **Visible balance** | Settings lists each key's remaining, used, limit, and reset period, plus a total of those keys. The composer shows the active key's remaining balance |
29
+ | **Keys chosen by group** | Each key lists its PackyCode groups and the models they reach; when the active key's groups cannot reach the selected model (no `cc` group for Claude, say), that turn runs on a stored key whose groups can |
30
+ | **Visible balance** | Settings lists each key's remaining, used, limit, reset period, and a usage bar, plus a total of those keys. The composer shows the active key's remaining balance |
30
31
  | **Claude through the local CLI** | `claude-opus-5-5`, `claude-sonnet-5-5`, and `claude-haiku-5-5` run the official Claude Code CLI on this device. Settings can detect it and install it |
32
+ | **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 |
31
33
  | **Credentials stay on the machine** | Keys live in the DSH credential file. The browser receives labels, state, and balances only |
32
34
 
33
35
  ## Install
@@ -42,7 +44,7 @@ Open the desktop application's **Plugins** page, click **Add plugin**, and enter
42
44
  dsh-packycode-api
43
45
  ```
44
46
 
45
- Choose the **official** install source, then click **Install**. To pin a version, enter `dsh-packycode-api@1.0.6`.
47
+ Choose the **official** install source, then click **Install**. To pin a version, enter `dsh-packycode-api@1.1.0`.
46
48
 
47
49
  Then **manually restart the desktop application**. Refreshing the page does not reload the plugin inside the Host process.
48
50
 
@@ -61,7 +63,7 @@ Choose a model from the `PackyCode` group. `deepseek-flash`, `gpt-6.1-sol`, and
61
63
 
62
64
  The **Plugins** page shows the installed state and version, and is also where you enable, disable, or uninstall.
63
65
 
64
- **For non-desktop profiles only:** `dsh plugin --profile <name> add dsh-packycode-api@1.0.6`. Do not use it for `desktop` — the CLI refuses it (`profile "desktop" is managed exclusively by the Electron application`).
66
+ **For non-desktop profiles only:** `dsh plugin --profile <name> add dsh-packycode-api@1.1.0`. Do not use it for `desktop` — the CLI refuses it (`profile "desktop" is managed exclusively by the Electron application`).
65
67
 
66
68
  A local checkout can be linked with `dsh plugin --profile <name> add link:/path/to/dsh-packycode-api`.
67
69
 
@@ -71,16 +73,47 @@ A profile without a browser can still use the model route. It just has no settin
71
73
 
72
74
  ## Daily use
73
75
 
74
- **Switch keys.** Keys are used in the order they were added. **Use this key** selects one; **Rename** changes its label (at most 60 characters, empty names are refused); **Re-enable** puts a disabled key back after a top-up or replacement.
76
+ **Switch keys.** Keys are used in the order they were added. **Use this key** picks the preferred key, effective from the next turn; **Rename** changes its label (at most 60 characters, empty names are refused); **Re-enable** puts a disabled key back after a top-up or replacement.
75
77
 
76
- **Read the balance.** The settings total is the remaining, used, and limit of **the keys listed here**, not the bill for every token on the PackyCode account. A key that could not be read is left out of the total and counted in a note; a key whose read failed this time but succeeded before keeps showing its last balance with the time it was read (the composer shows it as `≈US$…`) until DSH restarts. The composer shows the active key's remaining balance only while a PackyCode model is selected. **Refresh** bypasses the roughly 60-second cache. The default model host is `https://cf.api.fan` and the balance host is `https://www.packyapi.ai`; both can be changed under **Advanced: endpoints**. The old default, `https://www.packyapi.com`, is DNS-poisoned on mainland networks and answers `fetch failed` without a proxy; even when settings still name it, `.ai` is tried first.
78
+ **See groups.** Under **Accounts and keys**, each key lists its PackyCode groups (hover for the group's description) and which models they reach: ✓ yes, ◐ partly, ✕ no. The groups come from the gateway itself (its refusal of a model id it cannot route names the token's groups; nothing is billed), matched against PackyCode's public model-group table. Claude needs one of `cc` / `cc-sale` / `cc-expensive` / `claude-officially` (`claude-haiku-5-5` only `cc` and `claude-officially`); GPT needs `codex` / `codex-sale`; DeepSeek needs `deepseek-officially` / `deepseek-sale`.
77
79
 
78
- **Use Claude.** The **Claude Code CLI** card at the top of Settings checks this device. If the CLI is missing, **Install Claude Code CLI** runs `npm install -g @anthropic-ai/claude-code` (Node.js 18+). No DSH restart is required after that install. Windows also needs [Git for Windows](https://git-scm.com/downloads/win).
80
+ **Read the balance.** The total at the top of **Accounts and keys** is the remaining, used, and limit of **the keys listed here**, not the bill for every token on the PackyCode account. A key that could not be read is left out of the total and counted in a note; a key whose read failed this time but succeeded before keeps showing its last balance with the time it was read (the composer shows it as `≈US$…`) until DSH restarts. The composer shows the active key's remaining balance only while a PackyCode model is selected. **Refresh** bypasses the roughly 60-second cache. The default model host is `https://cf.api.fan` and the balance host is `https://www.packyapi.ai`; both can be changed under **Advanced: endpoints**. The old default, `https://www.packyapi.com`, is DNS-poisoned on mainland networks and answers `fetch failed` without a proxy; even when settings still name it, `.ai` is tried first.
81
+
82
+ **Use Claude.** The **Claude Code CLI** card in Settings checks this device. Every Claude turn's reasoning block names the key it ran with (by label only). If the CLI is missing, **Install Claude Code CLI** runs `npm install -g @anthropic-ai/claude-code` (Node.js 18+). No DSH restart is required after that install. Windows also needs [Git for Windows](https://git-scm.com/downloads/win).
83
+
84
+ **Web search.** DSH's `web_search` calls DeepSeek's official API by default (billed to `DEEPSEEK_API_KEY`). **Settings → PackyCode → Web search** can route it through PackyCode instead:
85
+
86
+ | Routing | Effect |
87
+ | --- | --- |
88
+ | Do not take over (default) | DSH's search settings stay as they are |
89
+ | Auto (route by model) | PackyCode models search through PackyCode; other models keep their search |
90
+ | Always use PackyCode search | Every request DSH's default search would have served goes through PackyCode |
91
+
92
+ | Backend | Model and protocol | Notes |
93
+ | --- | --- | --- |
94
+ | DeepSeek (default) | `deepseek-flash`, `/v1/messages` + `web_search_20250305` | A few thousand tokens per search; needs a DeepSeek group |
95
+ | GPT | `gpt-6.1-sol`, `/v1/responses` + `web_search` | Usually fresher and more precise, about 20–30K input tokens per search; needs the gateway to route this key's GPT requests to its group |
96
+
97
+ PackyCode has no dedicated search endpoint: one search is one model call with a server-side search tool, billed to the keys stored here. An exhausted or rejected key rotates exactly as on the model route.
98
+
99
+ <details>
100
+ <summary>How web search is wired into DSH</summary>
101
+
102
+ - The plugin registers two search providers with DSH's `web` service: `packycode-api` (always PackyCode) and `packycode-api-auto` (routes by the initiating session's model). Registering them changes nothing by itself.
103
+ - Auto and Always append one `- id: web` entry, marked `# >>> dsh-packycode-api: web search provider`, to the end of the profile's `cordis.patch.yml`. It points the web row's base `searchProvider` at one of those providers; DSH hot-applies the file, no restart needed. Switching back to the default removes the entry and leaves everything else as it was.
104
+ - The plugin does not write the web runtime's search slot. Subscription plugins such as Codex and Kimi rewrite that slot whenever the web row restarts, so two writers would restart each other forever. They fall back to the web row's base config for models they do not own, so writing the base coexists with them: Codex models keep Codex search and only the remaining requests reach the choice made here.
105
+ - The entry records the base search it replaced as a comment (`# fallback: …`). In Auto, requests from non-PackyCode models go there; if that provider hands a request back to this plugin, it goes straight to DSH's built-in `deepseek-official` instead of looping.
106
+ - Results come from structured data only: DeepSeek's `web_search_tool_result` blocks, GPT's `url_citation` annotations and `web_search_call` records. The model's summary is returned as `content`.
107
+ - When a key's group does not cover the backend's model (the gateway says `当前令牌未覆盖供应商`), the search tries the next key without disabling this one, and finally suggests switching backends.
108
+ - Settings shows the base and runtime search provider in effect, and warns when another plugin's later patch overrides the base.
109
+
110
+ </details>
79
111
 
80
112
  <details>
81
113
  <summary>When a key is rotated</summary>
82
114
 
83
115
  - **Rotates and disables the current key:** the gateway reports exhausted quota, or it rejects the key (HTTP 401 / invalid token). The harness retries the failed step, which is what actually sends it on the next key.
116
+ - **Passes over a key by its groups, without disabling it:** when the group reading shows the active key cannot reach this turn's model and another stored key can, the turn runs on that key (a Claude turn says so in its reasoning: “当前 Key「A」的分组不能用 …,本轮改用 Key「B」”). The active key stays active for every other model. Only evidence counts: a key whose groups are not read yet is used as before.
84
117
  - **Does not rotate:** rate limits (429); a 400 that this gateway uses for a dead upstream channel; and a 403 that means the group accepts only the vendor's official client. That last case is explained directly instead of being shown as an invalid API key.
85
118
  - When every stored key is disabled, the request fails and says so. There is no fallback credential.
86
119
 
@@ -114,6 +147,7 @@ A model in the list does not mean the current token group allows it. When changi
114
147
  - The conversation and its images reach the CLI over stdin (`--input-format stream-json`), not argv: a Windows command line is length-limited and cannot carry an image. Images in the conversation are sent to Claude as images, at the HTTP route's request size (at most 2048×2048, about 1 MiB each; at most 20 images and 20 MiB per turn, dropping the oldest first). An image left out is named with its read-only local copy, which the CLI can open with Read.
115
148
  - Every run carries `--append-system-prompt` (the delegation note). In `-p` mode the official CLI introduces itself as "You are Claude Code, Anthropic's official CLI for Claude …" when it has appended instructions and as "You are a Claude agent, built on Anthropic's Claude Agent SDK." when it has none; the latter was refused upstream as not coming from the official CLI (403 `only accepts requests from the official Claude Code CLI`). That is the official CLI's own behavior; the plugin does not rewrite its system prompt.
116
149
  - While the session model is a Claude model, titles are generated with `claude-haiku-5-5`, read-only, in a temporary directory, instead of a full Opus turn.
150
+ - While the session model is `gpt-6-astra`, titles are generated with `gpt-6.1-sol` (same Codex group); the turns themselves stay on Astra. Other models title their own sessions.
117
151
  - The `claude_code` tool can delegate one task to the local CLI while the session model is `gpt-6.1-sol`, `gpt-6-astra`, or `deepseek-flash`. The requested `mode` cannot exceed what the session preset allows.
118
152
  - An effort level the model does not support is rejected before the request is sent. Do not edit a model's effort map casually.
119
153
 
@@ -123,11 +157,15 @@ A model in the list does not mean the current token group allows it. When changi
123
157
 
124
158
  **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.
125
159
 
126
- **Uninstall:** remove this plugin on the **Plugins** page, then restart the desktop application. Uninstalling does not remove other plugins, the profile, or the token in the PackyCode console.
160
+ **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.
127
161
 
128
162
  ## FAQ
129
163
 
130
- **One model fails, but the key is valid?** Check whether the token group covers that model. The `cc` group does not serve Claude to a normal API client; this plugin's Claude models need the local CLI. A group refusal does not disable the key or rotate to the next one.
164
+ **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.
165
+
166
+ **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.
167
+
168
+ **Claude says the gateway refused the request as not coming from the official Claude Code CLI?** The usual cause is that the turn's key has no Claude group: PackyCode answers such a key with that same wording (or a 400 `当前令牌未覆盖供应商`). The error now says so directly — the key, its groups, and the groups the model needs — and that the CLI does not need updating. Only a key that does have a Claude group and is still refused points at the CLI.
131
169
 
132
170
  **The balance does not match the account bill?** Settings totals only the keys stored here, not other tokens on the same PackyCode account. A key that failed to read is noted separately and is not treated as zero.
133
171
 
@@ -160,7 +198,7 @@ pnpm run check
160
198
 
161
199
  `pnpm run check` runs every test, rebuilds, and writes a tarball under `.artifacts/`. `lib/` is a committed build; regenerate it with `pnpm run build` after editing `src/`, and do not hand-edit it.
162
200
 
163
- `node scripts/live-check.mjs` checks the live gateway. It reads a key already stored on this machine and spends a small amount of quota.
201
+ `node scripts/live-check.mjs` checks the live gateway through the same route the plugin registers: balance and groups, one answer per HTTP model, a rotation rehearsal, and the failure classification, printing only the key's last four characters. `--with-cli` also runs the three Claude models through the local Claude Code CLI, and `--with-search` runs one search per backend; the default run spends a small amount of quota.
164
202
 
165
203
  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.
166
204
 
package/README.md CHANGED
@@ -26,10 +26,16 @@
26
26
  | --- | --- |
27
27
  | **自带模型路由** | 安装后模型选择器出现 `PackyCode` 分组,不需要配置 provider,也不用改 `cordis.patch.yml` |
28
28
  | **多 Key 按顺序使用** | 额度用尽或 Key 被拒绝时自动切到下一个可用 Key;失败的那一步由重试接手 |
29
- | **余额一眼可见** | 设置页列出每个 Key 的剩余、已用、额度和重置周期,并给出这些 Key 的合计;输入框右侧显示当前 Key 的剩余额度 |
29
+ | **按分组选 Key** | 每个 Key 后面列出它所在的 PackyCode 分组和能用的模型;当前 Key 的分组用不了所选模型(例如没有 cc 分组却选了 Claude)时,这一轮自动改用分组合适的 Key |
30
+ | **余额一眼可见** | 设置页列出每个 Key 的剩余、已用、额度、重置周期和用量条,并给出这些 Key 的合计;输入框右侧显示当前 Key 的剩余额度 |
30
31
  | **Claude 走本机 CLI** | `claude-opus-5-5`、`claude-sonnet-5-5`、`claude-haiku-5-5` 由本机官方 Claude Code CLI 执行;设置页可以检测并一键安装 |
32
+ | **网页搜索可走 PackyCode** | 可选:DSH 的 `web_search` 改用 PackyCode 搜索,后端在 DeepSeek 与 GPT 之间切换;可只对 PackyCode 模型生效 |
31
33
  | **凭据不离开本机** | Key 只存在 DSH 凭据文件里。浏览器只能看到备注、状态和余额,拿不到 Key |
32
34
 
35
+ <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="272" height="425" alt="截屏2026-10-13 10 07 46" src="https://github.com/user-attachments/assets/040fc57a-2dd2-4195-9bbc-5fa3357d1eb1" />
37
+
38
+
33
39
  ## 安装
34
40
 
35
41
  先准备好 **DeepSeek Harness** 和 **PackyCode(PackyAPI)的 API Key**。尚未安装 DSH,可查看 [DSH 官方说明](https://github.com/deepseek-ai/deepseek-harness#run)。
@@ -42,7 +48,7 @@
42
48
  dsh-packycode-api
43
49
  ```
44
50
 
45
- 在**安装源**中选择**官方源**,然后点击**安装**。需要固定版本时填入 `dsh-packycode-api@1.0.6`。
51
+ 在**安装源**中选择**官方源**,然后点击**安装**。需要固定版本时填入 `dsh-packycode-api@1.1.0`。
46
52
 
47
53
  完成后**手动重启桌面应用**。仅刷新页面不会重新加载 Host 里的插件。
48
54
 
@@ -61,7 +67,7 @@ dsh-packycode-api
61
67
 
62
68
  桌面应用的**插件**页可以看到已安装状态与版本,也可以在那里启用、停用或卸载。
63
69
 
64
- **仅用于非 desktop profile:** `dsh plugin --profile <name> add dsh-packycode-api@1.0.6`。不要对 `desktop` 使用它 —— CLI 会直接拒绝(`profile "desktop" is managed exclusively by the Electron application`)。
70
+ **仅用于非 desktop profile:** `dsh plugin --profile <name> add dsh-packycode-api@1.1.0`。不要对 `desktop` 使用它 —— CLI 会直接拒绝(`profile "desktop" is managed exclusively by the Electron application`)。
65
71
 
66
72
  本地开发可以用绝对路径:`dsh plugin --profile <name> add link:/path/to/dsh-packycode-api`。
67
73
 
@@ -71,16 +77,47 @@ dsh-packycode-api
71
77
 
72
78
  ## 日常使用
73
79
 
74
- **换 Key。** Key 按添加顺序使用。点「切到此 Key」可以指定当前 Key;「重命名」改备注(最多 60 字,不能为空);充值或更换后点「重新启用」。
80
+ **换 Key。** Key 按添加顺序使用。点「切到此 Key」指定优先使用的 Key,下一轮就生效;「重命名」改备注(最多 60 字,不能为空);充值或更换后点「重新启用」。
81
+
82
+ **看分组。** 「账户与 Key」里每个 Key 下面列出它所在的 PackyCode 分组(鼠标悬停可看分组说明),以及能用哪些模型:✓ 可用、◐ 部分可用、✕ 不可用。分组由网关自己报出(对一个不存在的模型 id 的拒绝里会写明「已选分组」,不产生费用),再对照 PackyCode 公开的模型分组表。Claude 需要 `cc` / `cc-sale` / `cc-expensive` / `claude-officially` 之一(`claude-haiku-5-5` 只在 `cc` 和 `claude-officially`);GPT 需要 `codex` / `codex-sale`;DeepSeek 需要 `deepseek-officially` / `deepseek-sale`。
83
+
84
+ **看余额。** 「账户与 Key」顶部的合计是**已列出的这些 Key** 的剩余、已用与额度,不是 PackyCode 账号下全部令牌的账单。读失败的 Key 不计入合计,并会注明个数;某个 Key 这次读失败但之前读到过,会继续显示上次的余额并注明读取时间(输入框里显示为 `≈US$…`),重启 DSH 后才清空。输入框右侧只在选中 PackyCode 模型时显示当前 Key 的剩余额度。「刷新」会跳过约 60 秒的缓存。默认模型接口是 `https://cf.api.fan`,余额接口是 `https://www.packyapi.ai`,可在「高级:接口地址」里改。旧默认值 `https://www.packyapi.com` 在国内网络下 DNS 被污染、直连会 `fetch failed`,即使设置里保存的是它,插件也会先试 `.ai`。
85
+
86
+ **用 Claude。** 设置页的「Claude Code CLI」卡片会检测本机 CLI。每一轮 Claude 对话的思考区都会写明这一轮用的是哪个 Key(只显示备注名)。没装时点「安装 Claude Code CLI」,本机执行 `npm install -g @anthropic-ai/claude-code`(需要 Node.js 18+)。装好后不用重启 DSH。Windows 上另外需要 [Git for Windows](https://git-scm.com/downloads/win)。
87
+
88
+ **网页搜索。** DSH 的 `web_search` 默认调用 DeepSeek 官方接口(用 `DEEPSEEK_API_KEY` 计费)。在 **设置 → PackyCode → 网页搜索** 里可以改走 PackyCode:
89
+
90
+ | 搜索路由 | 效果 |
91
+ | --- | --- |
92
+ | 不接管(默认) | 不改 DSH 的搜索设置 |
93
+ | 自动(按模型路由) | PackyCode 模型用 PackyCode 搜索,其他模型照旧 |
94
+ | 始终使用 PackyCode 搜索 | 原本走 DSH 默认搜索的请求都改走 PackyCode |
75
95
 
76
- **看余额。** 设置页的合计是**已列出的这些 Key** 的剩余、已用与额度,不是 PackyCode 账号下全部令牌的账单。读失败的 Key 不计入合计,并会注明个数;某个 Key 这次读失败但之前读到过,会继续显示上次的余额并注明读取时间(输入框里显示为 `≈US$…`),重启 DSH 后才清空。输入框右侧只在选中 PackyCode 模型时显示当前 Key 的剩余额度。「刷新」会跳过约 60 秒的缓存。默认模型接口是 `https://cf.api.fan`,余额接口是 `https://www.packyapi.ai`,可在「高级:接口地址」里改。旧默认值 `https://www.packyapi.com` 在国内网络下 DNS 被污染、直连会 `fetch failed`,即使设置里保存的是它,插件也会先试 `.ai`。
96
+ | 搜索后端 | 模型与协议 | 说明 |
97
+ | --- | --- | --- |
98
+ | DeepSeek(默认) | `deepseek-flash`,`/v1/messages` + `web_search_20250305` | 每次约几千 token;需要 DeepSeek 分组 |
99
+ | GPT | `gpt-6.1-sol`,`/v1/responses` + `web_search` | 结果通常更新、更准,每次约 2–3 万输入 token;需要网关把这个 Key 的 GPT 请求路由到它的分组 |
77
100
 
78
- **用 Claude。** 设置页顶部的「Claude Code CLI」卡片会检测本机 CLI。没装时点「安装 Claude Code CLI」,本机执行 `npm install -g @anthropic-ai/claude-code`(需要 Node.js 18+)。装好后不用重启 DSH。Windows 上另外需要 [Git for Windows](https://git-scm.com/downloads/win)。
101
+ PackyCode 没有专用搜索接口:一次搜索就是一次带服务端搜索工具的模型调用,用这里的 Key 计费,额度用尽或 Key 被拒绝时同样切到下一个 Key。
102
+
103
+ <details>
104
+ <summary>网页搜索是怎么接进 DSH 的</summary>
105
+
106
+ - 插件向 DSH 的 `web` 服务注册两个搜索后端:`packycode-api`(始终走 PackyCode)和 `packycode-api-auto`(按发起会话的模型路由)。只注册不会改变任何行为。
107
+ - 选「自动」或「始终」时,插件在当前 profile 的 `cordis.patch.yml` 末尾写入一段带 `# >>> dsh-packycode-api: web search provider` 标记的 `- id: web` 配置,把 web 行的基础 `searchProvider` 指向上面的后端;DSH 会热加载这个文件,不用重启。切回「不接管」会删掉这段,其它内容原样保留。
108
+ - 不直接改 web 运行时的搜索槽位:Codex、Kimi 等订阅插件每次 web 重启都会重写那个槽位,两边都写会互相触发重启。它们对自己不负责的模型会回退到 web 行的基础配置,所以写基础配置能和它们共存:Codex 模型仍走 Codex 搜索,其余请求才轮到这里的选择。
109
+ - 这段配置用注释记下写入前的基础搜索(`# fallback: …`)。「自动」模式下非 PackyCode 模型的请求交给它;如果它又把请求转回本插件,就直接交给 DSH 内置的 `deepseek-official`,不会循环。
110
+ - 结果只取结构化数据:DeepSeek 用 `web_search_tool_result` 块,GPT 用 `url_citation` 标注和 `web_search_call` 记录;模型写的摘要作为 `content` 一并返回。
111
+ - 当前 Key 的分组覆盖不到所选后端的模型(网关报 `当前令牌未覆盖供应商`)时,会试下一个 Key,不停用 Key;都不行就提示切换后端。
112
+ - 设置页会显示当前生效的基础搜索和运行时搜索。如果另一个插件的补丁排在后面并覆盖了基础搜索,这里会提示。
113
+
114
+ </details>
79
115
 
80
116
  <details>
81
117
  <summary>什么时候会自动切换 Key</summary>
82
118
 
83
119
  - **会切换并停用当前 Key:** 额度用尽(网关报额度、余额、insufficient quota 之类),或 Key 被拒绝(HTTP 401 / `无效的令牌`)。失败的那一步由 harness 重试,从而用上新 Key。
120
+ - **按分组改用,不停用:** 分组读数显示当前 Key 用不了这一轮的模型、而另一个 Key 能用时,这一轮直接用那个 Key(Claude 轮次会在思考区写明「当前 Key「A」的分组不能用 …,本轮改用 Key「B」」)。「使用中」的 Key 不变,其它模型照常用它。只在有证据时才改用:分组还没读到的 Key 照旧使用。
84
121
  - **不会切换:** 限速(429);网关把上游渠道故障误报成 400(`当前令牌未覆盖供应商 …`);以及「只允许官方客户端」的 403。后一种会直接说明原因,而不是显示成 API Key 无效。
85
122
  - 全部 Key 都停用时,请求失败并说明没有可用 Key,不会回退到别的凭据。
86
123
 
@@ -93,7 +130,7 @@ dsh-packycode-api
93
130
  | `deepseek-flash` | HTTP,chat/completions | 可关;`low` / `high` / `max` | `deepseek-officially`、`deepseek-sale` |
94
131
  | `gpt-6.1-sol` | HTTP,Responses,可带图 | 不能关;`low` / `medium` / `high` / `xhigh` / `max` | `codex`、`codex-sale` |
95
132
  | `gpt-6-astra` | 同上 | 同上 | 同上 |
96
- | `claude-opus-5-5` | 本机 Claude Code CLI | 选择器档位同上,实际档位由 CLI 决定 | 要能给官方 CLI 用的分组,见下 |
133
+ | `claude-opus-5-5` | 本机 Claude Code CLI | 选择器档位同上,实际档位由 CLI 决定 | 能给官方 CLI 用的分组,比如cc分组 |
97
134
  | `claude-sonnet-5-5` | 同上 | 同上 | 同上 |
98
135
  | `claude-haiku-5-5` | 同上;会话标题也固定走它 | 同上 | 同上 |
99
136
 
@@ -114,6 +151,7 @@ dsh-packycode-api
114
151
  - 对话记录和图片通过 stdin 交给 CLI(`--input-format stream-json`),不走命令行参数:Windows 命令行长度有上限,而且命令行带不了图片。对话里的图片会以图片形式发给 Claude(与 HTTP 路由相同的压缩规格:每张不超过 2048×2048、约 1 MiB;一回合最多 20 张、合计 20 MiB,超出时先舍弃最早的)。没随消息发出的图片会写明本机只读副本的路径,CLI 需要时可以用 Read 读取。
115
152
  - 每次运行都带 `--append-system-prompt`(委派说明)。在 `-p` 模式下,官方 CLI 有追加说明时会以 "You are Claude Code, Anthropic's official CLI for Claude …" 自我介绍,没有时是 "You are a Claude agent, built on Anthropic's Claude Agent SDK.";后者曾被上游当成非官方 CLI 拒绝(403 `only accepts requests from the official Claude Code CLI`)。这是官方 CLI 自己的行为,插件没有改写它的系统提示词。
116
153
  - 会话跑在 Claude 模型上时,生成标题改用 `claude-haiku-5-5`,只读、临时目录,避免为起标题跑一整轮 Opus。
154
+ - 会话跑在 `gpt-6-astra` 上时,标题改由 `gpt-6.1-sol` 生成(同一个 Codex 分组);对话本身仍然用 Astra。其它模型的标题由会话模型自己生成。
117
155
  - 另有 `claude_code` 工具:当前模型是 `gpt-6.1-sol`、`gpt-6-astra` 或 `deepseek-flash` 时,可以把一个任务委派给本机 Claude Code。请求的 `mode` 不会超过会话权限允许的上限。
118
156
  - 声明了模型不支持的推理档位会被 harness 在发请求前拒绝,不要随手改档位映射。
119
157
 
@@ -123,11 +161,13 @@ dsh-packycode-api
123
161
 
124
162
  **更新:** 打开 **设置 → PackyCode**,有新版本时点「更新插件」。它会在当前 profile 目录里用 DSH 自带的 pnpm 安装精确版本,并读回 `package.json` 确认版本落地。本地 `link:` 开发安装不会出现这个按钮。完成后完全退出并重新打开桌面应用。只刷新页面不会重载 Host。也可以在**插件**页重新安装 `dsh-packycode-api`。
125
163
 
126
- **卸载:** 在**插件**页卸载本插件,然后重启桌面应用。卸载不会删除其它插件、整个 profile,也不会撤销 PackyCode 控制台里的令牌。
164
+ **卸载:** 用过 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 控制台里的令牌。
127
165
 
128
166
  ## 常见问题
129
167
 
130
- **某个模型不可用,但 Key 是有效的?** 先看令牌分组是否覆盖这个模型。`cc` 分组不会把 Claude 交给普通 API 客户端;本插件的 Claude 模型要靠本机 CLI。分组拒绝不会停用 Key,也不会切到下一个 Key。
168
+ **某个模型不可用,但 Key 是有效的?** 看「账户与 Key」里这个 Key 的分组和 ✓ / ✕:分组不覆盖这个模型时,换一个分组合适的 Key,或在 PackyCode 控制台给 Key 加上对应分组。存了分组合适的 Key 时,插件会自动用它跑这个模型。分组拒绝不会停用 Key。
169
+
170
+ **Claude 报「PackyCode 的上游拒绝了这次 Claude Code CLI 请求」或「只接受官方 Claude Code CLI」?** 最常见的原因是这一轮用的 Key 没有 Claude 分组:PackyCode 对这种 Key 也会回这句话(或 400 `当前令牌未覆盖供应商`)。现在报错会直接写明「本轮使用的 Key「…」所在分组(…)不包含能用 claude-… 的分组」,并说明不需要更新 CLI。只有 Key 确实有 Claude 分组时仍被拒,才需要按报错里的提示检查 CLI 版本或联系 PackyCode。
131
171
 
132
172
  **余额对不上账号账单?** 设置页合计的是这里列出的 Key,不含同账号下没添加到本插件的其它令牌。个别 Key 读失败时会单独注明,不会被算成 0。
133
173
 
@@ -139,6 +179,8 @@ dsh-packycode-api
139
179
 
140
180
  **插件页安装报 `ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION`?** pnpm 默认拒绝发布不满 24 小时的包,并且会检查 profile 里的整个 lockfile。报错里点名的往往是另一个刚更新过的插件,不是本插件。等它发布满 24 小时再重试,或把报错里的 `包名@版本` 加到 profile 目录 `pnpm-workspace.yaml` 的 `minimumReleaseAgeExclude` 下。已安装本插件后,设置页的「更新插件」不受这个限制。
141
181
 
182
+ **网页搜索报「no stored API key's group covers …」?** 所选搜索后端的模型不在你的令牌分组里(或网关暂时把它路由到了别的供应商)。在 **设置 → PackyCode → 网页搜索** 换一个后端,或添加分组覆盖它的 Key。
183
+
142
184
  **装了 CLI 还是不能用 Claude?** 从 Finder 或开始菜单启动时,`PATH` 往往比终端短。插件会再查官方安装位置、常见包管理器目录和 `npm prefix -g`。仍找不到就在设置里填 `claudeCliPath`,或点「重新检测」。改的是 Host 或重新安装了插件时,要完全退出并重启桌面应用。
143
185
 
144
186
  ## 安全边界
@@ -160,7 +202,7 @@ pnpm run check
160
202
 
161
203
  `pnpm run check` 会跑全部测试、重新构建,并在 `.artifacts/` 打出 tarball。`lib/` 是已提交的构建产物,改 `src/` 后用 `pnpm run build` 生成,不要手改。
162
204
 
163
- 真机网关用 `node scripts/live-check.mjs` 验证。它会读本机已保存的 Key,并消耗少量额度。
205
+ 真机网关用 `node scripts/live-check.mjs` 验证:它通过插件注册的同一条路由,读余额和分组、让每个 HTTP 模型回答一次、演练 Key 轮换和失败分类,只打印 Key 的末 4 位。加 `--with-cli` 会用本机 Claude Code CLI 跑三个 Claude 模型,加 `--with-search` 会每个搜索后端各搜一次;默认运行只消耗少量额度。
164
206
 
165
207
  使用 Agent 修改或发布时,先读 [AGENTS.md](AGENTS.md)。发布由推送 `v*` 标签触发 [Publish workflow](.github/workflows/publish.yml),不要在本机执行 `npm publish`。
166
208
 
@@ -8,6 +8,6 @@
8
8
  "piAi": "0.85.1",
9
9
  "validation": {
10
10
  "0.1.5-rc.2": "36 offline tests against the published host packages: the key ring (add/select/disable/rotate/enable/remove and a hand-edited record), balance parsing and host fallback with a fake fetch, failure classification against the gateway's real Chinese wording (including that a raw 403 quota message must not read as auth), the catalog contract (protocol, base URL, and vendor effort levels per model), the stream guard (terminal events, pre-chunk throws, and that a mid-stream failure never blames a key), and the real PiAiAdapter resolving every advertised model and preparing its call without reading a credential or opening a stream. Live check against the gateway: balance read (account and per-key), one answer from each of the three models over its own protocol, a rotation rehearsal where an invalid key was disabled and the next attempt answered with the real key, and the host adapter classifying a rejected key as AUTH.",
11
- "0.2.0-rc.2": "Declared in the accepted range; the plugin uses only APIs verified present on this line (llm.registerAdapter, llm.registerConfigurableProviders, settings.installSection, credential records, connection.rpc.handle, client slots). Not yet exercised by a live boot on this line."
11
+ "0.2.0-rc.2": "Declared in the accepted range; checked against the bundled 0.2.0-rc.2 host packages, not yet in a live boot on this line. Present and used: llm.registerAdapter (fiber-scoped, duplicates refused), llm.registerConfigurableProviders, credential records (credentials-local keeps records in memory, updated on modifyRecord and by a file watcher), connection.rpc.handle, client slots, web.registerSearchProvider with web.searchProviders, loader.entries(), profileContext.patchPath, hmr.runExclusive (refuses nested transactions), and the SettingsForms service: settings.update(rowId, patch) for volatile Config fields, settings.configure, and loader/volatile-update. settings.installSection does NOT exist on this line (an earlier note said it did), so the host overrides (apiHost/statusHost, not volatile) are read-only here while the volatile web search preference is writable."
12
12
  }
13
13
  }