dsh-packycode-api 0.0.0-stage → 1.0.3

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/AGENTS.md ADDED
@@ -0,0 +1,97 @@
1
+ # Repository agent guide
2
+
3
+ This file governs the entire `dsh-packycode-api` repository.
4
+
5
+ ## Repository identity
6
+
7
+ - Canonical repository: `https://github.com/BaronCyrus/dsh-packycode-api`
8
+ - npm package: `dsh-packycode-api`
9
+ - DSH plugin row: `packycode-api`
10
+ - Model group display name: `PackyCode`
11
+ - LLM provider route key: `packycode`
12
+
13
+ ## Project boundaries
14
+
15
+ - This is a permanent DSH profile plugin, not a temporary dynamic Cordis Plugin.
16
+ - Host code belongs in `src/index.js` and its sibling modules; browser UI belongs in `src/client.jsx` and is built through the client wrapper in `tsdown.config.mjs`.
17
+ - The route is self-contained: the plugin builds its own pi-ai provider, catalog, key ring, and balance reader. It must never require the user to edit `cordis.patch.yml` or `llm-pi-ai` provider YAML.
18
+ - Never introduce a silent fallback to another provider, another credential source, or an ambient `PACKY_CODE_API_KEY`. Only keys the user added through this plugin may authenticate a request.
19
+ - The catalog declares protocols and effort levels from vendor documentation. Changing a model's `api`, `baseUrl`, or `thinkingLevelMap` requires a note in `CHANGELOG.md` and a live check.
20
+
21
+ ## Security requirements
22
+
23
+ - Never print, log, commit, return to the browser, or include in diagnostics any API key. `scripts/live-check.mjs` may read the stored key but must only ever print its masked tail.
24
+ - Credentials and balance requests stay host-side. Browser RPC returns only the projections in `src/rpc.js`.
25
+ - Balance requests stay bounded by a timeout and configured with `redirect: 'error'`.
26
+ - Preserve unrelated DSH plugins, profiles, sessions, settings, and credentials.
27
+ - Do not start, stop, or restart DSH without explicit user permission.
28
+
29
+ ## Source and generated files
30
+
31
+ - Edit `src/`; never hand-edit generated `lib/index.js` or `lib/client.js`.
32
+ - `lib/` is tracked and must be regenerated with `pnpm run build` after source changes.
33
+ - `.artifacts/` and `node_modules/` are ignored and must never be committed.
34
+ - Add or update tests under `tests/` for behavior changes.
35
+
36
+ ## Required validation
37
+
38
+ ```sh
39
+ pnpm install
40
+ pnpm run check
41
+ ```
42
+
43
+ `pnpm run check` must run every test, rebuild both bundles, and produce the versioned tarball in `.artifacts/`.
44
+
45
+ Live gateway behavior is verified with `node scripts/live-check.mjs` after a build. It consumes a small amount of quota; run it when a change touches the catalog, the provider composition, or the failure classification. Do not claim runtime success from a passing build alone.
46
+
47
+ ## Release authorization
48
+
49
+ `npm publish`, Git tags, GitHub Releases, and version publication are externally visible and partially irreversible. Execute them only when the user explicitly asks to publish or release and confirms the target version. A request to modify, test, or commit alone is not release authorization.
50
+
51
+ Never reuse, move, delete, or overwrite a published npm version or Git tag. If a published version needs a correction, create a new patch version.
52
+
53
+ ## Release checklist
54
+
55
+ 1. Synchronize and confirm a clean base:
56
+
57
+ ```sh
58
+ git fetch origin --tags --prune
59
+ git status --short --branch
60
+ git log --oneline HEAD..origin/main
61
+ ```
62
+
63
+ Fast-forward clean remote changes before editing. Never discard remote changes.
64
+
65
+ 2. Confirm the requested SemVer is unused:
66
+
67
+ ```sh
68
+ npm view dsh-packycode-api@<version> version --json
69
+ git ls-remote --exit-code --tags origin refs/tags/v<version>
70
+ ```
71
+
72
+ For an unused npm version, `npm view` normally returns `E404`. An existing npm version or Git tag requires choosing a new version.
73
+
74
+ 3. Update all release metadata together: `package.json` version, `CHANGELOG.md`, and any version pinned in `README.md` / `README.en.md`.
75
+
76
+ 4. Validate and inspect the exact release artifact:
77
+
78
+ ```sh
79
+ pnpm install --frozen-lockfile
80
+ pnpm run check
81
+ npm publish .artifacts/dsh-packycode-api-<version>.tgz --access public --dry-run --json
82
+ ```
83
+
84
+ 5. Commit the release and push `main` without force. [`.github/workflows/publish.yml`](.github/workflows/publish.yml) must already be on the commit that will be tagged. Do not push the tag in this step.
85
+
86
+ 6. Confirm the package has a GitHub Actions trusted publisher before pushing the tag: repository `BaronCyrus/dsh-packycode-api`, workflow filename `publish.yml`, environment empty. Never request that a token, password, or one-time code be pasted into chat.
87
+
88
+ 7. Create the annotated tag and push it. That push publishes with GitHub OIDC. Do not run `npm publish` locally for a release. A local publish cannot attach provenance. Do not set `NODE_AUTH_TOKEN` or a setup-node `registry-url` in the publish workflow; npm treats either as token auth and skips OIDC.
89
+
90
+ ```sh
91
+ git tag -a v<version> -m "dsh-packycode-api v<version>"
92
+ git push origin v<version>
93
+ ```
94
+
95
+ `workflow_dispatch` on the same workflow only prints the OIDC identity npm will match. It does not publish.
96
+
97
+ 8. Confirm the publish workflow succeeded for that tag before declaring the release public. On an OIDC `package not found` error, fix the trusted publisher and re-run the same workflow. Do not move the tag. If the tagged tree itself must change, publish a new patch version.
package/CHANGELOG.md ADDED
@@ -0,0 +1,45 @@
1
+ # Changelog
2
+
3
+ ## 1.0.3
4
+
5
+ - The Claude CLI timeout test holds the event loop open. The runner unrefs its timer and the test stub is not a real process, so Node 22's test runner cancelled the still-pending test and stopped the publish workflow. Plugin behavior is unchanged. `1.0.2` was tagged but not published.
6
+
7
+ ## 1.0.2
8
+
9
+ - The plugin card and both READMEs show the PackyCode mascot. The package declares a top-level `icon` and ships a 512×512 transparent lossless WebP, under DSH's 256 KiB icon limit. The original README PNG is unchanged.
10
+ - Keys can be renamed from the settings panel (`keys/rename`, up to 60 characters, empty names refused). Before, a label was fixed at add time, so keys stored under a duplicate default name could only be fixed by removing and re-adding them.
11
+ - The account-balance rows now show the key's own name instead of the PackyCode token name, so a rename shows up in both lists; the token name moves to the row's detail line when it differs.
12
+
13
+ ## 1.0.1
14
+
15
+ - The balance card no longer shows PackyCode's account-level billing line (`PackyCode 账号(含同账号其它用量):剩余 … · 已用 … · 额度 …`). It counts every token on the account, including usage from keys not stored here, so it could read as a large negative remaining beside the per-key totals. The headline figures — the sum of the listed keys — are unchanged.
16
+ - With no keys stored, the balance card showed the "no keys yet" note twice; it now shows it once.
17
+
18
+ ## 1.0.0
19
+
20
+ First release.
21
+
22
+ - Ships a self-contained `packycode` LLM route: the plugin builds its own pi-ai provider, model catalog, retry policy, key ring, and balance reader, and registers them through `llm.registerAdapter`. Installing the package is the whole setup.
23
+ - Built-in catalog: `deepseek-flash` (openai-completions), `gpt-6.1-sol` (openai-responses), `claude-opus-5-5` (anthropic-messages), each pinned to the protocol and base URL the gateway serves and carrying the vendor's documented effort levels.
24
+ - Multiple API keys in one credential record, used in order. A key the gateway exhausts (`额度/余额/insufficient quota`) or rejects (401 / `无效的令牌`) is marked disabled and the active pointer moves to the next usable key, so the harness retry of the failed step resolves a different key.
25
+ - Route retry policy retries `QUOTA`, `AUTH`, and `INVALID_REQUEST` in addition to the defaults: the first two make rotation reachable, the last absorbs this gateway's misleading 400 when an upstream channel is dead.
26
+ - A Responses request without a `prompt_cache_key` is refused by the gateway's Codex-group check with 403. The harness supplies a session id; the plugin adds a per-process fallback when a caller does not.
27
+ - Balance display: the settings section lists every key with its available/used quota, reset period, and state; the composer shows the active key's remaining balance. Reads come from `/api/usage/token/` with a host fallback, plus the account's `/v1/dashboard/billing/*` pair.
28
+ - Validation: 39 offline tests (key ring, balance parsing and fallback, failure classification, catalog contract, stream guard, `apply()` wiring with and without a connection service, and the real `PiAiAdapter` resolving every advertised model and preparing its call). `scripts/live-check.mjs` verified against the live gateway: balance read, one answer from each of the three models, a rotation rehearsal with an invalid key in front of the real one, and the host adapter classifying the rejected key as `AUTH`.
29
+ - The plugin injects only `llm` and `credentials`; the browser RPC surface is requested through `ctx.inject(['connection', 'webServer'], …)`. An isolated end-to-end boot (headless profile in a scratch `DSH_HOME`) proved this matters: with `connection` in the inject list the entry stayed pending and the boot failed, and without it the same boot answered through `packycode/gpt-6.1-sol`.
30
+ - Default key names no longer collide after a removal: the ring shrinks when a key is deleted, so `Key N` numbering follows the highest name in use instead of the key count (removing the first of two keys and adding another used to produce a second "Key 2").
31
+ - The balance card lists one row per key/account (PackyCode token name, remaining, used, limit, reset period, active/disabled state), and the key rows show only the label plus that same account name so the two line up; the site-wide account total moved to a summary line. The account read now walks past a key the gateway rejects instead of letting whichever key sits first blank the total.
32
+ - The package also ships an entry patch for the `connection` row: `inject: [credentials, webRuntime, webServer]`. Connection registers a channel through `owner.webServer` read on *its own row's fiber*, so the shipped `dsh-web-app` row (which injects `webRuntime` alone) made every browser call answer HTTP 405 while the boot log stayed clean. Verified in a scratch web profile over the real HTTP transport: `POST /packycode-api/status` moved from 405 to 401 (registered, unauthorized), and with the root token exchanged for its cookie `status`, `keys/add`, and `balance` all returned their projections — including a live per-key balance and account total, with no key material in any response.
33
+ - `claude-sonnet-5-5` and `claude-haiku-5-5` join the catalog, CLI-backed like `claude-opus-5-5`. A session-title request (`purpose: 'session-title'`) on any CLI-backed model now runs on `claude-haiku-5-5`, read-only, in a temp directory (so no project memory is loaded), with the title instructions passed as `--append-system-prompt` and only the request text as the prompt — instead of a full Opus delegated turn in the workspace for every new session.
34
+ - The settings section now has a **Claude Code CLI** card: it detects whether this device has the CLI and shows its version (`claude --version`, run in the plugin's isolated config home) and location; without one it warns that the Claude Opus model cannot work and offers an **Install** button that runs `npm install -g @anthropic-ai/claude-code` on the host — the same command on macOS and Windows. The install starts over `claude-cli/install` and the panel polls `claude-cli/status`, so no request is held open for minutes; failures are classified (`npm-missing`, `permission`, `network`, `timeout`, `not-found-after-install`) with npm's last output lines. Lookup covers the official installer, npm/Homebrew/Volta/nvm locations, `%APPDATA%\npm` on Windows, PATH with each platform's rules (Windows `Path`, quoted entries), and finally `npm prefix -g`, because a GUI-launched host's PATH is usually shorter than a terminal's. The runner resolves the CLI per run, so a CLI installed after startup is used without a restart, and puts the CLI's directory on the child's PATH so an npm launcher's `env node` resolves. On Windows an npm `claude.cmd` launcher is never run through `cmd.exe` (Node requires a shell for `.cmd`, which would make `cmd.exe` parse the prompt): the runner reads the launcher and starts the program it forwards to (`node …\cli.js` or a packaged `.exe`) directly. A turn whose CLI is missing now says so and points to the install button instead of `spawn claude ENOENT`.
35
+ - The Claude Code CLI's mode now follows the session's own permission preset by default (`claudeCliMode: auto`), so installing the plugin needs no profile patch: `read-only` → `read-only`, `workspace-write` → `edit`, `danger-full-access` with approval `never` → `full`. The CLI cannot ask the harness user for approval, so a session that still asks never gets `full`. The `claude_code` tool's `mode` argument is capped at the same ceiling, and an explicit `read-only`/`edit`/`full` setting still pins the mode. Read through `ctx.get('sandboxPolicy')` and `ctx.get('approval')`; without either service the previous `edit` default stands.
36
+ - A CLI-backed `claude-opus-5-5` turn now shows its work live: the CLI's thinking and every tool call it makes (`▸ Bash · git status`, `▸ Read · …`, plus `✗ …` when a tool fails) stream into a reasoning block while it runs, and its answer streams as text, each run bracketed by `block-start`/`block-end` as the harness stream grammar requires. Before, only answer text was forwarded, so a turn spent in the CLI's own tool loop showed nothing until the end.
37
+ - A CLI-backed turn's usage now carries cache traffic. The CLI's requests are almost entirely cache hits (`input_tokens: 2`, `cache_read_input_tokens: 42880`), and only `input_tokens` was forwarded, so the session showed a 0% cache hit rate and a context of a few tokens. The prompt side now comes from the CLI's **last** request (uncached input, cache read, cache write) — the harness reads it as context occupancy, which a sum over a multi-step CLI turn would overstate — and output is the CLI's whole-turn total.
38
+ - The CLI's stdout and stderr are decoded as UTF-8 on the stream instead of chunk by chunk, so a multi-byte character (Chinese text, emoji) that straddles two pipe reads no longer turns into `�` in the answer, the live reasoning, or a failure message; the same applies to `npm` and `claude --version` output. A consumer that stops reading a streaming CLI turn early now stops the CLI instead of leaving it running until its 30-minute timeout.
39
+ - `claude-opus-5-5` is now served by the **local official Claude Code CLI**: selecting that model runs the CLI per turn instead of an HTTP request, which is the only client PackyCode's `cc` group serves Claude models to. The route dispatches per model, so `deepseek-flash` and `gpt-6.1-sol` keep the pi-ai path. A delegated turn drops the harness system prompt and tool schemas (the CLI brings its own), renders the recent conversation into the CLI prompt, and streams the CLI's text back as harness chunks. New settings: `claudeCliPath`, `claudeCliMode` (`read-only`/`edit`/`full`, default `edit`), `claudeCliCwd` (empty = the session's workspace, resolved through the `agents` service). Verified live through the plugin's own adapter: `claude-opus-5-5` → `finish=stop`, `adapter-cli-ok`, 3s, while the HTTP-backed models kept answering.
40
+ - New `claude_code` tool: a delegated task is run by the **local official Claude Code CLI**, which is the only client PackyCode's `cc` group serves its Claude models to. The child process receives this plugin's endpoint and API key plus an isolated `CLAUDE_CONFIG_DIR` — required because `~/.claude/settings.json` env overrides an inherited environment, so without it the user's own CLI configuration would silently win. `mode` bounds the CLI's own tools (`read-only` / `edit` / `full`). Verified live through the plugin's runner against the real CLI and gateway: `cli-bridge-ok`, 4s, $0.1984; and offline by a stub-CLI test that asserts the argv, environment isolation, session-workspace cwd, and rendered result. Unlike a forged client identity this runs the genuine binary, so the group's requirement is met rather than bypassed; the trade-offs (per-call cost, tens-of-seconds latency, and the CLI's own tools running outside the harness's sandbox and transcript) are documented in the README.
41
+ - A gateway group that serves only the vendor's official client (for example PackyCode's `cc` group answering `403 This API endpoint is only accessible via the official Claude CLI`) is now recognised as a client-policy refusal: the plugin does not retry it, does not disable or rotate a key, and replaces the bare 403 — which the harness renders as an invalid-API-key problem — with a sentence naming the real cause and the groups that do serve API clients. Verified live: the harness receives `PI_AI_ERROR` (outside the route's retryable codes) with no key rotated, while `deepseek-flash` and `gpt-6.1-sol` keep answering.
42
+ - The balance card's headline figures are now the **sum of the listed keys** (remaining, used, limit) instead of PackyCode's account-level billing numbers, which count every token on the account and read as a wrong total when several keys are stored. Keys whose read failed are excluded and reported in a note; a key with no quota limit marks the total as unlimited rather than inflating a limit. The account-level numbers remain, on their own clearly-labelled line. Verified in a real browser against the app's own 0.2.0 runtime with two keys: `合计剩余 US$999.07 · 合计已用 US$0.93 · 合计额度 US$1,000.00`, with the account line reading `PackyCode 账号(含同账号其它用量):剩余 US$407.00 · 已用 US$93.00 · 额度 US$500.00`.
43
+ - The route retries up to 20 times (was 3) for every model it serves. One route carries all three models, so the budget is shared: this gateway drops an upstream channel or key per request, and the retry is also what reaches the next key. With the default backoff (500 ms doubling, capped at 10 s, 10% jitter) a failure is surfaced after roughly three minutes of retrying instead of about seven seconds.
44
+ - The browser half sets a default `payload` on every channel call. Connection's request envelope requires the field, and `JSON.stringify` drops an `undefined` one, so `call('status')` was refused with `invalid client-request message` and the settings panel showed a read failure. Reproduced in a real browser against the app's own 0.2.0 runtime (`payload` omitted → rejected, `payload: {}` → accepted) and verified fixed there: opening Settings → PackyCode, pasting a key, and clicking Add now renders the account row (`战魂銘人-张洋凡 · 使用中 · 剩余 US$499.07 · 已用 US$0.93 · 额度 US$500.00 · 每月`), the account totals, and the key row with its matching account name.
45
+ - Client styling no longer fills a button with `--dsw-alias-brand-primary` while writing its label in `--dsw-alias-label-inverse`: on this theme the brand colour is light and the inverse token is absent, so the Add button rendered as an empty box. Emphasis is now border-and-weight only, the active-key chip keeps the readable label colour, and the host-override inputs are disabled when the runtime cannot write settings.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 BaronCyrus
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.en.md ADDED
@@ -0,0 +1,159 @@
1
+ <div align="center">
2
+
3
+ <img src="docs/assets/dsh-packycode-mascot.png" width="220" height="220" alt="DSH PackyCode API mascot">
4
+
5
+ # DSH PackyCode API
6
+
7
+ [简体中文](README.md) · **English**
8
+
9
+ **Pick models, rotate keys, and read balances in DSH with a PackyCode API key.**
10
+
11
+ The `PackyCode` model group ships with the plugin. No YAML edits and no separate provider setup.
12
+
13
+ [![CI](https://github.com/BaronCyrus/dsh-packycode-api/actions/workflows/ci.yml/badge.svg)](https://github.com/BaronCyrus/dsh-packycode-api/actions/workflows/ci.yml)
14
+ [![npm](https://img.shields.io/npm/v/dsh-packycode-api?logo=npm&label=npm)](https://www.npmjs.com/package/dsh-packycode-api)
15
+ [![Total npm downloads](https://img.shields.io/npm/dt/dsh-packycode-api?logo=npm&label=total%20downloads)](https://www.npmjs.com/package/dsh-packycode-api)
16
+ [![MIT](https://img.shields.io/badge/license-MIT-111111.svg)](LICENSE)
17
+ [![Star](https://img.shields.io/github/stars/BaronCyrus/dsh-packycode-api?style=flat&logo=github&label=Star)](https://github.com/BaronCyrus/dsh-packycode-api/stargazers)
18
+
19
+ [Features](#features) · [Install](#install) · [Daily use](#daily-use) · [Models and groups](#models-and-groups) · [Update and uninstall](#update-and-uninstall)
20
+
21
+ </div>
22
+
23
+ ## Features
24
+
25
+ | Capability | What it does |
26
+ | --- | --- |
27
+ | **Built-in model route** | Adds a `PackyCode` group to the model picker. No provider configuration and no `cordis.patch.yml` edits |
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 |
30
+ | **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 |
31
+ | **Credentials stay on the machine** | Keys live in the DSH credential file. The browser receives labels, state, and balances only |
32
+
33
+ ## Install
34
+
35
+ 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).
36
+
37
+ ### 1. Install the plugin
38
+
39
+ Open the desktop application's **Plugins** page, click **Add plugin**, and enter this as the **package name or address**:
40
+
41
+ ```text
42
+ dsh-packycode-api
43
+ ```
44
+
45
+ Choose the **official** install source, then click **Install**. To pin a version, enter `dsh-packycode-api@1.0.3`.
46
+
47
+ Then **manually restart the desktop application**. Refreshing the page does not reload the plugin inside the Host process.
48
+
49
+ ### 2. Add a key
50
+
51
+ Open **Settings → PackyCode**. Paste the API key under **Add a key**. A label is optional and only distinguishes keys. Click **Add**.
52
+
53
+ > Do not paste a key into a public issue, chat, or screenshot. The plugin does not read `PACKY_CODE_API_KEY` from the environment, and it does not silently switch to another provider when a request fails.
54
+
55
+ ### 3. Select a model
56
+
57
+ Choose a model from the `PackyCode` group. `deepseek-flash` and `gpt-6.1-sol` work once a key is stored. The three Claude models also need the Claude Code CLI on this device; see below.
58
+
59
+ <details>
60
+ <summary>Verify the install / other profiles / a local checkout</summary>
61
+
62
+ The **Plugins** page shows the installed state and version, and is also where you enable, disable, or uninstall.
63
+
64
+ **For non-desktop profiles only:** `dsh plugin --profile <name> add dsh-packycode-api@1.0.3`. Do not use it for `desktop` — the CLI refuses it (`profile "desktop" is managed exclusively by the Electron application`).
65
+
66
+ A local checkout can be linked with `dsh plugin --profile <name> add link:/path/to/dsh-packycode-api`.
67
+
68
+ A profile without a browser can still use the model route. It just has no settings panel and no composer balance.
69
+
70
+ </details>
71
+
72
+ ## Daily use
73
+
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.
75
+
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. 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.com`; both can be changed under **Advanced: endpoints**.
77
+
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).
79
+
80
+ <details>
81
+ <summary>When a key is rotated</summary>
82
+
83
+ - **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.
84
+ - **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
+ - When every stored key is disabled, the request fails and says so. There is no fallback credential.
86
+
87
+ </details>
88
+
89
+ ## Models and groups
90
+
91
+ | Model | How it runs | Effort | Token group |
92
+ | --- | --- | --- | --- |
93
+ | `deepseek-flash` | HTTP, chat/completions | Can be off; `low` / `high` / `max` | `deepseek-officially`, `deepseek-sale` |
94
+ | `gpt-6.1-sol` | HTTP, Responses, images allowed | Cannot be off; `low` / `medium` / `high` / `xhigh` / `max` | `codex`, `codex-sale` |
95
+ | `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 |
96
+ | `claude-sonnet-5-5` | Same | Same | Same |
97
+ | `claude-haiku-5-5` | Same; session titles always use this model | Same | Same |
98
+
99
+ 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.
100
+
101
+ <details>
102
+ <summary>Protocol, group, and CLI constraints</summary>
103
+
104
+ - OpenAI / Codex traffic must use the Responses protocol. `gpt-6.1-sol` is pinned to `openai-responses` at `https://cf.api.fan/v1`. Do not put `/responses` in the base URL.
105
+ - Codex-group Responses requests must include `prompt_cache_key` or the gateway returns 403. The harness supplies the session id; the plugin adds a fallback when a caller does not.
106
+ - The Anthropic base URL has no `/v1` (the client appends `/v1/messages`). The three Claude models declare the Messages protocol in the catalog, but their requests are made by the local CLI.
107
+ - The `cc` group accepts only the official Claude CLI. A normal API client gets `403 This API endpoint is only accessible via the official Claude CLI`. This plugin runs the local `claude` binary for that group instead of switching the token to `cc-sale` or pretending to be the official client. If the token has no group the official CLI may use, the three Claude models still will not run.
108
+ - Selecting a Claude model when the CLI is missing fails that turn with an install hint. It does not surface as a missing binary name alone.
109
+ - The CLI receives this plugin's endpoint and current key (`ANTHROPIC_BASE_URL` / `ANTHROPIC_AUTH_TOKEN`) and an isolated config directory, `~/.dsh/packycode-claude-cli`. Without that isolation, `env` in `~/.claude/settings.json` would override the plugin key.
110
+ - CLI permission follows the session by default: `read-only` stays read-only, `workspace-write` may edit, and `danger-full-access` with approval disabled may use `full`. The CLI cannot ask the DSH user for approval, so a session that still asks never gets `full`. `claudeCliMode` can pin `read-only`, `edit`, or `full`. An empty `claudeCliPath` is looked up automatically; an empty `claudeCliCwd` uses the session workspace.
111
+ - Thinking and tool activity (`▸ Bash`, `▸ Read`, and `✗` on failure) appear in that turn's reasoning block. They are progress, not DSH tool calls. The CLI's own tools are outside the DSH sandbox.
112
+ - 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.
113
+ - The `claude_code` tool can delegate one task to the local CLI while the session model is `gpt-6.1-sol` or `deepseek-flash`. The requested `mode` cannot exceed what the session preset allows.
114
+ - An effort level the model does not support is rejected before the request is sent. Do not edit a model's effort map casually.
115
+
116
+ </details>
117
+
118
+ ## Update and uninstall
119
+
120
+ **Update:** on the desktop **Plugins** page, install `dsh-packycode-api` again (or `dsh-packycode-api@version`), then quit and reopen the desktop application. A page refresh does not reload the Host.
121
+
122
+ **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.
123
+
124
+ ## FAQ
125
+
126
+ **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.
127
+
128
+ **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.
129
+
130
+ **A rate limit disabled the key?** It should not. HTTP 429 does not rotate. Exhausted quota or HTTP 401 does; after a top-up, click **Re-enable**.
131
+
132
+ **The CLI is installed, but Claude still fails?** A host launched from Finder or the Start menu usually has a shorter `PATH` than a terminal. The plugin also checks the official install location, common package-manager directories, and `npm prefix -g`. If it still cannot find the binary, set `claudeCliPath` or click **Check again**. After a Host change or a reinstall, quit and reopen the desktop application.
133
+
134
+ ## Security and scope
135
+
136
+ This is a community plugin, not affiliated with or endorsed by DeepSeek or PackyCode. Model requests go to the PackyCode host you configured. Keeping credentials out of browser responses does not mean inference is local. Claude models also start the official CLI on this device.
137
+
138
+ Keys stay in the local DSH credential file (`~/.dsh/.credentials.yaml`, owner-only as enforced by DSH). They are not written into settings, logs, or the transcript. Balance requests run only on the host, with a timeout and redirects refused.
139
+
140
+ Use [Issues](https://github.com/BaronCyrus/dsh-packycode-api/issues) for ordinary bugs. Never include a key, token, or credential-file contents. See [SECURITY.md](SECURITY.md).
141
+
142
+ ## Local development
143
+
144
+ ```sh
145
+ git clone https://github.com/BaronCyrus/dsh-packycode-api.git
146
+ cd dsh-packycode-api
147
+ pnpm install --frozen-lockfile
148
+ pnpm run check
149
+ ```
150
+
151
+ `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.
152
+
153
+ `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.
154
+
155
+ 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.
156
+
157
+ ## License
158
+
159
+ [MIT](LICENSE)
package/README.md CHANGED
@@ -1,3 +1,159 @@
1
- # Temporary Holding Version
1
+ <div align="center">
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ <img src="docs/assets/dsh-packycode-mascot.png" width="220" height="220" alt="DSH PackyCode API 吉祥物">
4
+
5
+ # DSH PackyCode API
6
+
7
+ **简体中文** · [English](README.en.md)
8
+
9
+ **用 PackyCode 的 API Key,在 DSH 里选模型、轮换 Key、看余额。**
10
+
11
+ 安装后自带 `PackyCode` 模型分组,不用改 YAML,也不用另配 provider。
12
+
13
+ [![CI](https://github.com/BaronCyrus/dsh-packycode-api/actions/workflows/ci.yml/badge.svg)](https://github.com/BaronCyrus/dsh-packycode-api/actions/workflows/ci.yml)
14
+ [![npm](https://img.shields.io/npm/v/dsh-packycode-api?logo=npm&label=npm)](https://www.npmjs.com/package/dsh-packycode-api)
15
+ [![npm 总下载量](https://img.shields.io/npm/dt/dsh-packycode-api?logo=npm&label=%E6%80%BB%E4%B8%8B%E8%BD%BD%E9%87%8F)](https://www.npmjs.com/package/dsh-packycode-api)
16
+ [![MIT](https://img.shields.io/badge/license-MIT-111111.svg)](LICENSE)
17
+ [![Star](https://img.shields.io/github/stars/BaronCyrus/dsh-packycode-api?style=flat&logo=github&label=Star)](https://github.com/BaronCyrus/dsh-packycode-api/stargazers)
18
+
19
+ [功能](#功能) · [安装](#安装) · [日常使用](#日常使用) · [模型与分组](#模型与分组) · [更新与卸载](#更新与卸载)
20
+
21
+ </div>
22
+
23
+ ## 功能
24
+
25
+ | 能力 | 使用体验 |
26
+ | --- | --- |
27
+ | **自带模型路由** | 安装后模型选择器出现 `PackyCode` 分组,不需要配置 provider,也不用改 `cordis.patch.yml` |
28
+ | **多 Key 按顺序使用** | 额度用尽或 Key 被拒绝时自动切到下一个可用 Key;失败的那一步由重试接手 |
29
+ | **余额一眼可见** | 设置页列出每个 Key 的剩余、已用、额度和重置周期,并给出这些 Key 的合计;输入框右侧显示当前 Key 的剩余额度 |
30
+ | **Claude 走本机 CLI** | `claude-opus-5-5`、`claude-sonnet-5-5`、`claude-haiku-5-5` 由本机官方 Claude Code CLI 执行;设置页可以检测并一键安装 |
31
+ | **凭据不离开本机** | Key 只存在 DSH 凭据文件里。浏览器只能看到备注、状态和余额,拿不到 Key |
32
+
33
+ ## 安装
34
+
35
+ 先准备好 **DeepSeek Harness** 和 **PackyCode(PackyAPI)的 API Key**。尚未安装 DSH,可查看 [DSH 官方说明](https://github.com/deepseek-ai/deepseek-harness#run)。
36
+
37
+ ### 1. 安装插件
38
+
39
+ 打开桌面应用的**插件**页,点击**添加插件**,在**包名或地址**中填入:
40
+
41
+ ```text
42
+ dsh-packycode-api
43
+ ```
44
+
45
+ 在**安装源**中选择**官方源**,然后点击**安装**。需要固定版本时填入 `dsh-packycode-api@1.0.3`。
46
+
47
+ 完成后**手动重启桌面应用**。仅刷新页面不会重新加载 Host 里的插件。
48
+
49
+ ### 2. 添加 Key
50
+
51
+ 打开 **设置 → PackyCode**,在「添加 Key」里粘贴 API Key。备注可选,用来区分多个 Key。点「添加」。
52
+
53
+ > 不要把 Key 粘贴到公开 Issue、聊天记录或截图里。插件不会读取环境变量 `PACKY_CODE_API_KEY`,也不会在失败时偷偷改用别的 provider。
54
+
55
+ ### 3. 选择模型
56
+
57
+ 回到对话,在模型选择器里选 `PackyCode` 分组下的模型。`deepseek-flash` 和 `gpt-6.1-sol` 添加 Key 后即可用。三个 Claude 模型还需要本机装有 Claude Code CLI,见下文。
58
+
59
+ <details>
60
+ <summary>检查安装 / 其它 profile / 本地目录</summary>
61
+
62
+ 桌面应用的**插件**页可以看到已安装状态与版本,也可以在那里启用、停用或卸载。
63
+
64
+ **仅用于非 desktop profile:** `dsh plugin --profile <name> add dsh-packycode-api@1.0.3`。不要对 `desktop` 使用它 —— CLI 会直接拒绝(`profile "desktop" is managed exclusively by the Electron application`)。
65
+
66
+ 本地开发可以用绝对路径:`dsh plugin --profile <name> add link:/path/to/dsh-packycode-api`。
67
+
68
+ 没有浏览器的 profile 也能走模型路由,只是看不到设置页和输入框额度。
69
+
70
+ </details>
71
+
72
+ ## 日常使用
73
+
74
+ **换 Key。** Key 按添加顺序使用。点「切到此 Key」可以指定当前 Key;「重命名」改备注(最多 60 字,不能为空);充值或更换后点「重新启用」。
75
+
76
+ **看余额。** 设置页的合计是**已列出的这些 Key** 的剩余、已用与额度,不是 PackyCode 账号下全部令牌的账单。读失败的 Key 不计入合计,并会注明个数。输入框右侧只在选中 PackyCode 模型时显示当前 Key 的剩余额度。「刷新」会跳过约 60 秒的缓存。默认模型接口是 `https://cf.api.fan`,余额接口是 `https://www.packyapi.com`,可在「高级:接口地址」里改。
77
+
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)。
79
+
80
+ <details>
81
+ <summary>什么时候会自动切换 Key</summary>
82
+
83
+ - **会切换并停用当前 Key:** 额度用尽(网关报额度、余额、insufficient quota 之类),或 Key 被拒绝(HTTP 401 / `无效的令牌`)。失败的那一步由 harness 重试,从而用上新 Key。
84
+ - **不会切换:** 限速(429);网关把上游渠道故障误报成 400(`当前令牌未覆盖供应商 …`);以及「只允许官方客户端」的 403。后一种会直接说明原因,而不是显示成 API Key 无效。
85
+ - 全部 Key 都停用时,请求失败并说明没有可用 Key,不会回退到别的凭据。
86
+
87
+ </details>
88
+
89
+ ## 模型与分组
90
+
91
+ | 模型 | 怎么调用 | 推理档位 | 适合的令牌分组 |
92
+ | --- | --- | --- | --- |
93
+ | `deepseek-flash` | HTTP,chat/completions | 可关;`low` / `high` / `max` | `deepseek-officially`、`deepseek-sale` |
94
+ | `gpt-6.1-sol` | HTTP,Responses,可带图 | 不能关;`low` / `medium` / `high` / `xhigh` / `max` | `codex`、`codex-sale` |
95
+ | `claude-opus-5-5` | 本机 Claude Code CLI | 选择器档位同上,实际档位由 CLI 决定 | 要能给官方 CLI 用的分组,见下 |
96
+ | `claude-sonnet-5-5` | 同上 | 同上 | 同上 |
97
+ | `claude-haiku-5-5` | 同上;会话标题也固定走它 | 同上 | 同上 |
98
+
99
+ 列表里看得到模型,不代表当前令牌分组一定放行。换分组时不要把对应分组一起去掉。
100
+
101
+ <details>
102
+ <summary>协议、分组和 CLI 的具体约束</summary>
103
+
104
+ - OpenAI / Codex 必须走 Responses。`gpt-6.1-sol` 已固定为 `openai-responses`,base URL 是 `https://cf.api.fan/v1`,不要把 `/responses` 写进根地址。
105
+ - Codex 分组的 Responses 请求必须带 `prompt_cache_key`,否则 403。harness 会带会话 id;没有会话 id 时插件补一个兜底值。
106
+ - Anthropic 的 base URL 不带 `/v1`(客户端自己拼 `/v1/messages`)。三个 Claude 模型虽然目录里声明了 Messages 协议,实际请求由本机 CLI 发出。
107
+ - `cc`(Claude Code 专用)只接受官方 Claude CLI。用普通 API 客户端调 Claude 会 403:`This API endpoint is only accessible via the official Claude CLI`。本插件因此调用本机 `claude`,而不是改去 `cc-sale`,也不是伪造客户端身份。令牌如果没有开放给官方 CLI 的分组,这三个模型仍然不可用。
108
+ - 选了 Claude 模型但本机没有 CLI 时,这一回合会直接说明未安装,并指向设置页的安装按钮。
109
+ - CLI 使用插件配置的地址和当前 Key(`ANTHROPIC_BASE_URL` / `ANTHROPIC_AUTH_TOKEN`),以及独立配置目录 `~/.dsh/packycode-claude-cli`。不隔离的话,`~/.claude/settings.json` 里的 `env` 会盖掉插件 Key。
110
+ - CLI 权限默认跟随当前会话:`read-only` → 只读,`workspace-write` → 可编辑,`danger-full-access` 且不再审批 → `full`。CLI 不能向 DSH 弹审批,所以只要会话还要审批,就不会给它 `full`。也可把设置项 `claudeCliMode` 固定成 `read-only` / `edit` / `full`。`claudeCliPath` 留空则自动查找;`claudeCliCwd` 留空则用当前会话工作区。
111
+ - 思考过程和工具调用(`▸ Bash`、`▸ Read`,失败时带 `✗`)会出现在这一回合的思考区。它们是进度,不是 DSH 的工具调用。CLI 自己的工具不受 DSH 沙箱约束。
112
+ - 会话跑在 Claude 模型上时,生成标题改用 `claude-haiku-5-5`,只读、临时目录,避免为起标题跑一整轮 Opus。
113
+ - 另有 `claude_code` 工具:当前模型是 `gpt-6.1-sol` 或 `deepseek-flash` 时,可以把一个任务委派给本机 Claude Code。请求的 `mode` 不会超过会话权限允许的上限。
114
+ - 声明了模型不支持的推理档位会被 harness 在发请求前拒绝,不要随手改档位映射。
115
+
116
+ </details>
117
+
118
+ ## 更新与卸载
119
+
120
+ **更新:** 在桌面应用的**插件**页重新安装 `dsh-packycode-api`(或 `dsh-packycode-api@版本`),然后完全退出并重新打开桌面应用。只刷新页面不会重载 Host。
121
+
122
+ **卸载:** 在**插件**页卸载本插件,然后重启桌面应用。卸载不会删除其它插件、整个 profile,也不会撤销 PackyCode 控制台里的令牌。
123
+
124
+ ## 常见问题
125
+
126
+ **某个模型不可用,但 Key 是有效的?** 先看令牌分组是否覆盖这个模型。`cc` 分组不会把 Claude 交给普通 API 客户端;本插件的 Claude 模型要靠本机 CLI。分组拒绝不会停用 Key,也不会切到下一个 Key。
127
+
128
+ **余额对不上账号账单?** 设置页合计的是这里列出的 Key,不含同账号下没添加到本插件的其它令牌。个别 Key 读失败时会单独注明,不会被算成 0。
129
+
130
+ **限速之后 Key 被停用了?** 不该发生。429 不会切换 Key。额度用尽或 401 才会停用;充值后点「重新启用」。
131
+
132
+ **装了 CLI 还是不能用 Claude?** 从 Finder 或开始菜单启动时,`PATH` 往往比终端短。插件会再查官方安装位置、常见包管理器目录和 `npm prefix -g`。仍找不到就在设置里填 `claudeCliPath`,或点「重新检测」。改的是 Host 或重新安装了插件时,要完全退出并重启桌面应用。
133
+
134
+ ## 安全边界
135
+
136
+ 本项目是社区插件,与 DeepSeek、PackyCode 无隶属或背书关系。模型请求会发到你配置的 PackyCode 接口;凭据不返回浏览器,不代表推理在本地完成。Claude 模型还会在本机启动官方 CLI。
137
+
138
+ Key 存在本机 DSH 凭据文件(`~/.dsh/.credentials.yaml`,权限由 DSH 设为仅所有者可读),不进设置文档、日志或对话记录。余额请求只在宿主侧发出,带超时并禁止重定向。
139
+
140
+ 普通问题请提交 [Issue](https://github.com/BaronCyrus/dsh-packycode-api/issues)。不要附带 Key、令牌或凭据文件内容。更多说明见 [SECURITY.md](SECURITY.md)。
141
+
142
+ ## 本地开发
143
+
144
+ ```sh
145
+ git clone https://github.com/BaronCyrus/dsh-packycode-api.git
146
+ cd dsh-packycode-api
147
+ pnpm install --frozen-lockfile
148
+ pnpm run check
149
+ ```
150
+
151
+ `pnpm run check` 会跑全部测试、重新构建,并在 `.artifacts/` 打出 tarball。`lib/` 是已提交的构建产物,改 `src/` 后用 `pnpm run build` 生成,不要手改。
152
+
153
+ 真机网关用 `node scripts/live-check.mjs` 验证。它会读本机已保存的 Key,并消耗少量额度。
154
+
155
+ 使用 Agent 修改或发布时,先读 [AGENTS.md](AGENTS.md)。发布由推送 `v*` 标签触发 [Publish workflow](.github/workflows/publish.yml),不要在本机执行 `npm publish`。
156
+
157
+ ## License
158
+
159
+ [MIT](LICENSE)
package/SECURITY.md ADDED
@@ -0,0 +1,18 @@
1
+ # Security
2
+
3
+ ## Credentials
4
+
5
+ - API keys are stored through the DSH credential service as one `grant` record scoped to this plugin (`packycode-api/keys`), which lands in the local credentials file with the permissions DSH enforces (owner-only).
6
+ - Keys never enter the settings document, the session transcript, logs, or an error message. `src/key-ring.js` returns key material only through `secrets()`, which is callable from the host half alone.
7
+ - The browser receives key labels, states, and balances — never a key, and never a masked fragment of one. The RPC surface in `src/rpc.js` is the only way in.
8
+ - The plugin disables ambient discovery (`authContext.env` and `fileExists` resolve empty), so an exported `PACKY_CODE_API_KEY` cannot authenticate a request behind the user's back.
9
+ - There is no fallback to another provider, another credential, or an unauthenticated request. When every stored key is disabled, the request fails with a message that says so.
10
+
11
+ ## Network
12
+
13
+ - Balance requests carry a timeout (`AbortSignal.timeout`) and `redirect: 'error'`, and go only to the configured hosts plus the PackyAPI fallback host.
14
+ - Model traffic goes to the configured model host through the host's own pi-ai adapter, with the harness attribution headers, exactly like a configured provider route.
15
+
16
+ ## Reporting
17
+
18
+ Report a security issue through the repository's GitHub issues rather than a public discussion of a live credential. Never paste a key, token, or credential-store value into an issue, a log, or a chat.
@@ -0,0 +1,13 @@
1
+ {
2
+ "latestTested": "0.1.5-rc.2",
3
+ "acceptedRuntimeRange": ">=0.1.5-rc.1 <0.3.0-0",
4
+ "supported": [
5
+ "0.1.5-rc.2",
6
+ "0.2.0-rc.2"
7
+ ],
8
+ "piAi": "0.85.1",
9
+ "validation": {
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."
12
+ }
13
+ }
@@ -0,0 +1,23 @@
1
+ # Register the PackyCode API-key model route and its settings surface.
2
+ # The route is self-contained: the plugin builds its own adapter, model
3
+ # catalog, credential key ring, and balance reader, so the user configures
4
+ # nothing but their API keys.
5
+ - insert:
6
+ - id: packycode-api
7
+ name: 'dsh-packycode-api'
8
+
9
+ # Connection registers a plugin's RPC channel through `owner.webServer`, where
10
+ # the owner is the Connection row's own fiber — not the caller's. The shipped
11
+ # dsh-web-app row injects `webRuntime` alone, so that read is refused with
12
+ # `cannot get property "webServer" without inject`, the channel never reaches
13
+ # the web server, and the browser's calls answer HTTP 405 while the boot log
14
+ # stays clean.
15
+ #
16
+ # An entry patch replaces the whole inject list, so name every service that
17
+ # fiber actually touches: `credentials` is the module's own inject, `webRuntime`
18
+ # carries the row's `trustedHosts: !!js ctx.webRuntime.trustedHosts`, and
19
+ # `webServer` is what channel registration reads. The name guard leaves a
20
+ # replacement Connection implementation untouched.
21
+ - id: connection
22
+ name: '@deepseek-ai/dsh-client-connection'
23
+ inject: [credentials, webRuntime, webServer]
Binary file