dsh-packycode-api 0.0.0-stage → 1.0.1
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 +49 -0
- package/CHANGELOG.md +35 -0
- package/LICENSE +21 -0
- package/README.md +100 -2
- package/SECURITY.md +18 -0
- package/compatibility.json +13 -0
- package/cordis.patch.yml +23 -0
- package/lib/client.js +853 -0
- package/lib/index.js +2796 -0
- package/package.json +95 -4
package/AGENTS.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
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 and confirms the version. Never reuse or move a published version or tag; publish a new patch version instead.
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.0.1
|
|
4
|
+
|
|
5
|
+
- 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.
|
|
6
|
+
- With no keys stored, the balance card showed the "no keys yet" note twice; it now shows it once.
|
|
7
|
+
|
|
8
|
+
## 1.0.0
|
|
9
|
+
|
|
10
|
+
First release.
|
|
11
|
+
|
|
12
|
+
- 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.
|
|
13
|
+
- 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.
|
|
14
|
+
- 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.
|
|
15
|
+
- 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.
|
|
16
|
+
- 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.
|
|
17
|
+
- 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.
|
|
18
|
+
- 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`.
|
|
19
|
+
- 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`.
|
|
20
|
+
- 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").
|
|
21
|
+
- 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.
|
|
22
|
+
- 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.
|
|
23
|
+
- `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.
|
|
24
|
+
- 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`.
|
|
25
|
+
- 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.
|
|
26
|
+
- 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.
|
|
27
|
+
- 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.
|
|
28
|
+
- 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.
|
|
29
|
+
- `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.
|
|
30
|
+
- 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.
|
|
31
|
+
- 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.
|
|
32
|
+
- 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`.
|
|
33
|
+
- 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.
|
|
34
|
+
- 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.
|
|
35
|
+
- 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.md
CHANGED
|
@@ -1,3 +1,101 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-packycode-api
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
DeepSeek Harness 的 PackyCode(PackyAPI)插件:**填一个 API Key 就能用**,插件自带模型路由,不需要你改任何 YAML。
|
|
4
|
+
|
|
5
|
+
- **自带路由与模型目录**:安装后模型选择器里直接出现 `PackyCode` 分组,不需要配置 provider。
|
|
6
|
+
- **多个 Key 按顺序使用**:某个 Key 额度用尽或被网关拒绝时,自动切到下一个可用 Key,本轮请求由重试接手,不需要你手动改配置。
|
|
7
|
+
- **看得到余额**:设置页列出每个 Key 的剩余额度、已用量、重置周期与状态;对话输入框右侧常驻显示当前 Key 的剩余额度。
|
|
8
|
+
- **三种协议各就各位**:`deepseek-flash`(chat/completions)、`gpt-6.1-sol`(Responses)、`claude-opus-5-5`(Anthropic Messages),每个模型的档位按厂商官方文档声明。
|
|
9
|
+
|
|
10
|
+
## 安装
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
dsh plugin --profile <profile> add dsh-packycode-api
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
安装后重启 DSH。插件通过自带的 `cordis.patch.yml` 注册自己,profile 的 `dsh.profile.bundles` 会自动包含它。模型路由本身不依赖浏览器:没有 `connection` 服务的 profile(例如 headless)也能用,只是看不到设置面板与输入框额度。
|
|
17
|
+
|
|
18
|
+
## 使用
|
|
19
|
+
|
|
20
|
+
1. 打开 **设置 → PackyCode**。
|
|
21
|
+
2. 在「添加 Key」里粘贴 PackyCode 的 API Key(可填备注,便于区分多个 Key),点「添加」。
|
|
22
|
+
3. 回到对话,在模型选择器里选 `PackyCode` 分组下的模型,例如 `claude-opus-5-5`。
|
|
23
|
+
|
|
24
|
+
余额会在设置页与输入框右侧显示;「刷新」会强制重新读取(默认 60 秒缓存)。
|
|
25
|
+
|
|
26
|
+
## 多 Key 轮换
|
|
27
|
+
|
|
28
|
+
- Key 按添加顺序排列,插件只使用「当前 Key」;点击「切到此 Key」可以手动指定。
|
|
29
|
+
- 同一个 Key 出现**额度用尽**(网关会报 `额度/余额/insufficient quota` 之类)或**被拒绝**(HTTP 401 / `无效的令牌`)时,插件会把该 Key 标记为「已停用」并自动切到下一个可用 Key;本轮失败的那一步由 harness 的重试重新执行,从而真正用上新 Key。
|
|
30
|
+
- 限速(429)不会切换 Key——那是瞬时问题,由重试吸收即可。
|
|
31
|
+
- 网关把上游渠道故障误报成 400(`当前令牌未覆盖供应商 …`)时也不会切换 Key,插件把它当作可重试的瞬时故障。
|
|
32
|
+
- Key 充值或更换后,点「重新启用」即可让它重新参与。
|
|
33
|
+
|
|
34
|
+
## 余额从哪里来
|
|
35
|
+
|
|
36
|
+
| 数据 | 接口 | 说明 |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| 每个 Key 的额度 | `GET {余额接口}/api/usage/token/` | `total_available` / `quota_limit` / `quota_reset_period`;`quota_limit = -1` 表示无周期上限,`unlimited_quota = true` 表示不限额度 |
|
|
39
|
+
| 账号额度 | `GET {模型接口}/v1/dashboard/billing/subscription` 与 `/usage` | `hard_limit_usd` 与 `total_usage`,剩余即两者之差 |
|
|
40
|
+
|
|
41
|
+
额度单位按 new-api 约定换算:`500000 quota = 1 USD`。默认模型接口是 `https://cf.api.fan`,余额接口是 `https://www.packyapi.com`(`cf.api.fan` 不提供 `/api/*` 系列,插件会自动回退到另一个 host)。两者都可以在设置页「高级」里改。
|
|
42
|
+
|
|
43
|
+
## PackyCode 官方的注意事项(插件已内置)
|
|
44
|
+
|
|
45
|
+
- **OpenAI / Codex 系模型必须走 Responses 协议**,用 chat/completions 会协议不匹配、流式提前中断 —— `gpt-6.1-sol` 已固定为 `openai-responses`。
|
|
46
|
+
- **OpenAI / Codex 的 base_url 要带 `/v1`**,且不要把 `/responses` 这类端点路径写进根地址(客户端自己拼)。插件里是 `https://cf.api.fan/v1`。
|
|
47
|
+
- **Anthropic 的 base_url 不能带 `/v1`**(客户端自己拼 `/v1/messages`)—— `claude-opus-5-5` 用的是 `https://cf.api.fan`。
|
|
48
|
+
- Codex 分组的 Responses 请求**必须带 `prompt_cache_key`**(否则 403 反代校验)。harness 会带上会话 id;插件在调用方没给 session id 时补一个兜底值。
|
|
49
|
+
- 各模型的推理档位按厂商文档声明:`deepseek-flash` = `off/low/high/max`,`gpt-6.1-sol` 与 `claude-opus-5-5` = `low/medium/high/xhigh/max`(后两者无法关闭推理)。请求里声明了模型不支持的档位会被 harness 在发请求前拒绝,所以档位不要随手改。
|
|
50
|
+
- **分组决定"能不能用 API 调用"**(PackyCode 控制台里给令牌选的分组):
|
|
51
|
+
- `cc`(claude code 专用)**只接受官方 Claude CLI**,用 API 调 Claude 会 403 `This API endpoint is only accessible via the official Claude CLI`。要让本插件调 Claude,请选 `cc-sale` 或 `claude-officially`。
|
|
52
|
+
- `codex` / `codex-sale` 要求标准 Codex 客户端形态(插件已内置 `prompt_cache_key` 等必要条件),对应用 `gpt-6.1-sol`。
|
|
53
|
+
- `deepseek-officially` / `deepseek-sale` 对应用 `deepseek-flash`。
|
|
54
|
+
- 换分组时别把这三个分组一起去掉,否则对应模型会失效。
|
|
55
|
+
- 插件识别这类"只允许官方客户端"的拒绝:**不重试、不切换也不停用 Key**,并直接给出中文说明(而不是让你看到"API Key 无效")。
|
|
56
|
+
|
|
57
|
+
## Claude 模型走本机 Claude Code CLI
|
|
58
|
+
|
|
59
|
+
PackyCode 的 `cc` 分组只把 Claude 模型提供给**官方 Claude CLI**,API 客户端会被 403 挡住。所以插件的 `claude-opus-5-5`、`claude-sonnet-5-5`、`claude-haiku-5-5` **不是**普通的 HTTP 模型:**在模型选择器里选中它,底层就由本机真正的 `claude` 二进制执行**(其余模型照旧走 pi-ai / HTTP)。
|
|
60
|
+
|
|
61
|
+
- **检测与一键安装**:**设置 → PackyCode** 顶部的「Claude Code CLI」卡片会检测本机是否装了 CLI,并显示版本号(`claude --version`)与位置。没装时会提示「不安装将无法正常使用 Claude Opus 模型」,并给出「安装 Claude Code CLI」按钮:macOS 与 Windows 统一执行 `npm install -g @anthropic-ai/claude-code`(需要 Node.js 18+),安装期间卡片每 2 秒刷新一次,装好后不用重启 DSH 就能用。
|
|
62
|
+
- 查找顺序:设置项 `claudeCliPath` → 官方安装器位置(`~/.local/bin/claude`,Windows 为 `%USERPROFILE%\.local\bin\claude.exe`)→ npm / Homebrew / Volta / nvm 的常见位置(Windows 为 `%APPDATA%\npm\claude.cmd`)→ `PATH` → `npm prefix -g` 给出的全局目录(从 Finder / 开始菜单启动的 DSH 拿到的 `PATH` 往往比终端短,所以要问 npm)。
|
|
63
|
+
- Windows 上 npm 装出来的是 `claude.cmd` 启动器。插件**不经过** `cmd.exe` 启动它,而是读出启动器指向的程序(`node …\cli.js` 或包内的 `.exe`)直接运行,所以提示词不会被 `cmd.exe` 解析。只有 `npm install` 本身经 `cmd.exe` 运行,它的参数都是固定的。Windows 上 Claude Code 另外需要 Git for Windows。
|
|
64
|
+
- 常见失败会给出对应提示:找不到 npm(先装 Node.js)、全局目录没有写权限(`EACCES`/`EPERM`,提示用 `sudo` 或管理员终端)、网络错误、超时;同时显示 npm 输出的最后几行。
|
|
65
|
+
- 没装 CLI 时选了 Claude 模型,该回合会直接报「Claude Code CLI is not installed on this device…」,并提示去设置页安装。
|
|
66
|
+
- **凭据**:子进程用**插件配置的 baseURL 与当前 Key**(`ANTHROPIC_BASE_URL` / `ANTHROPIC_AUTH_TOKEN`),并使用独立的 `CLAUDE_CONFIG_DIR`(`~/.dsh/packycode-claude-cli`)。隔离是必须的:`~/.claude/settings.json` 里的 `env` 会覆盖进程环境,不隔离就等于用你 CLI 自己配置里的 token,插件 Key 完全不生效。
|
|
67
|
+
- **设置项**:
|
|
68
|
+
- `claudeCliPath` — CLI 路径,空则按上面「查找顺序」自动查找。
|
|
69
|
+
- `claudeCliMode` — 默认 `auto`:**跟随当前会话的 DSH 权限预设**,不需要额外配置。`read-only` 预设 → `read-only`(Read/Grep/Glob);`workspace-write` 预设 → `edit`(另加 Edit/Write);`danger-full-access` 且审批为 `never` → `full`(`--dangerously-skip-permissions`,含 shell 命令)。CLI 无法向 DSH 用户发起审批,所以只要会话还需要审批,就不会给它 `full`。也可以显式填 `read-only` / `edit` / `full` 固定下来。
|
|
70
|
+
- `claudeCliCwd` — 工作目录,空则用**当前会话的工作区**(取不到时退回进程目录)。
|
|
71
|
+
- **语义**:一个 CLI 回合 = 一次委派。CLI 用**它自己的**工具与权限系统完成任务并给出最终答案;它无法调用 harness 的工具,所以插件**不会**把工具清单转发给它;系统提示也用 CLI 自己的(只把最近的对话作为上下文,过长按尾部截断)。因此对 `claude-opus-5-5` 而言:DSH 的沙箱/审批不约束子进程内部动作。
|
|
72
|
+
- **实时输出**:CLI 的思考过程与每一次工具调用(`▸ Bash · git status`、`▸ Read · …`,失败时追加 `✗ …`)会实时出现在该回合的「思考」区,回答正文实时流式输出。它们只是进度展示,不是 DSH 的工具调用,也不会带进下一回合的委派提示。
|
|
73
|
+
- **用量**:上下文占用与缓存命中取自 CLI 本回合**最后一次**请求(未缓存输入 / 缓存读 / 缓存写),输出 token 为整回合合计。CLI 几乎全部输入都命中缓存,所以缓存命中率通常很高。
|
|
74
|
+
- **会话标题**:会话跑在 Claude 模型上时,生成标题固定改用 `claude-haiku-5-5`,只读、在临时目录下运行(不加载项目记忆),只把标题请求本身作为提示词,不再为起个标题跑一整轮 Opus 委派。
|
|
75
|
+
- **代价**:每次数秒到数十秒,按 PackyCode 计费(实测单次约 **$0.19**,因 CLI 自带大量系统提示与工具说明)。
|
|
76
|
+
- 另有 `claude_code` **工具**:当会话由 `gpt-6.1-sol` / `deepseek-flash` 驱动时,可以让模型显式把某个任务委派给 Claude Code(参数含 `prompt` / `mode` / `model` / `cwd`),与上面共用同一个 runner。模型请求的 `mode` 不会超过会话权限预设允许的上限。
|
|
77
|
+
|
|
78
|
+
## 安全
|
|
79
|
+
|
|
80
|
+
- API Key 只存在本机 DSH 凭据文件(`~/.dsh/.credentials.yaml`,权限 0600),由 DSH 的 credential record 管理;不会写进设置文档、日志或对话记录。
|
|
81
|
+
- 浏览器只能拿到 Key 的**备注、状态和余额**,拿不到 Key 本身。
|
|
82
|
+
- 余额请求只在宿主侧发出,带超时并禁止重定向。
|
|
83
|
+
- 插件**不会**回退到环境变量里的 `PACKY_CODE_API_KEY`,也不会回退到其它 provider:只用你显式添加的 Key。
|
|
84
|
+
|
|
85
|
+
## 开发与验证
|
|
86
|
+
|
|
87
|
+
```sh
|
|
88
|
+
pnpm install
|
|
89
|
+
pnpm run check # 全部测试 + 构建 + 打包
|
|
90
|
+
node scripts/live-check.mjs # 真机验证(需要本机已存 PackyCode Key,会消耗少量额度)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`scripts/live-check.mjs` 会读取本机已存的 Key,验证:余额读取、三个模型各跑通一次(三种协议各一条)、以及「无效 Key + 真 Key」的轮换演练,并报告 harness 侧的失败分类。
|
|
94
|
+
|
|
95
|
+
本地迭代时可以把插件 link 进 profile:
|
|
96
|
+
|
|
97
|
+
```sh
|
|
98
|
+
dsh plugin --profile <profile> add link:/path/to/dsh-packycode-api
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
改了 Host 代码必须重启 DSH 才会生效;只改客户端代码刷新浏览器即可。
|
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
|
+
}
|
package/cordis.patch.yml
ADDED
|
@@ -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]
|