dsh-any-deepseek 0.0.15 → 0.0.17

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.
@@ -0,0 +1,133 @@
1
+ # 参与改进 dsh-any-deepseek
2
+
3
+ 这是个实用小工具,改动从「一个字段的文案」到「接一家新渠道」都欢迎。
4
+
5
+ **先读 [README.md](README.md)** —— 那里讲的是怎么用;这一份讲怎么改。
6
+
7
+ ---
8
+
9
+ ## 最有价值的三种贡献
10
+
11
+ ### 1. 接一个新渠道
12
+
13
+ 内置 5 家,接别家通常只改 [`src/presets.ts`](src/presets.ts) 一个文件。
14
+
15
+ ```ts
16
+ 'your-relay': {
17
+ displayName: 'Your Relay',
18
+ api: 'openai-completions',
19
+ baseURL: 'https://your-relay.example/v1',
20
+ // 🔴 用**这家自己的**拼写。各家模型名不同:
21
+ // deepseek-v4.1-flash / deepseek/deepseek-v4.1-flash / deepseek-flash
22
+ // 抄错不会在本地报错 —— 会在第一次真请求时被上游拒。
23
+ model: 'deepseek-v4.1-flash',
24
+ ...V41_FLASH_CAPS,
25
+ // 可选:这家有没有余额接口、是哪种形状(见 src/balance.ts 的四种 shape)
26
+ // balance: { shape: 'openai-billing' },
27
+ },
28
+ ```
29
+
30
+ 加完这一条,面板的规格下拉里就有它了 —— 不需要改别处。
31
+
32
+ **两条硬规矩**(都是付过代价的):
33
+
34
+ 1. **注释里不要写「我探过了」,除非真的探过。**
35
+ 这个仓里曾经有一条 `Probed live: the billing pair answers` 的注释,而那个路由**从没被调用过**,
36
+ 第一次真调用就 404。**假证据比没有注释更坏** —— 它让下一个读者不再去查。
37
+
38
+ 2. **`model` 填上游的拼写,不是我们的 id。**
39
+ 我们对外只暴露 `deepseek-v4.1-flash` 一个 id,各家的拼写由预设吸收。
40
+ **这正是这个插件存在的理由** —— 所以别在预设里图省事写我们的 id。
41
+
42
+ ### 2. 修一处文案
43
+
44
+ 面板上的每一句都应该是一个**能在页面上验证的事实**。读到含糊、过时、或替宿主猜原因的,直接改。
45
+
46
+ 判断标准(本项目一直在用):
47
+
48
+ - ❌ 「已安全保存」—— 读者无法验证。
49
+ - ✅ 「存在 `channels.<id>.apiKey`,会出现在 `--dump-config` 输出里」—— 可验证。
50
+ - ❌ 「只读部署,或设置服务未启用」—— 替宿主猜了两个**没查过**的原因。
51
+ - ✅ 「宿主拒绝了这个修改」+ 宿主自己的错误信息 —— 只说知道的事。
52
+
53
+ ### 3. 报一个「装了但没反应」
54
+
55
+ **静默失效是本项目最贵的一类缺陷** —— 装了不生效、写了没保存、功能存在但不可达,
56
+ 全都零报错。复现步骤比任何猜测都值钱。
57
+
58
+ 这类问题通常有这几个藏身处,如果你能指出是哪一处,修起来会快很多:
59
+
60
+ | 现象 | 可能的位置 |
61
+ |---|---|
62
+ | 面板整块不出现,控制台零错误 | `SETTINGS_NS` 与 `cordis.patch.yml` 的条目 id 不一致 |
63
+ | 面板在,但每次请求都失败、表格显示「还没有渠道」 | `inject` 里少了 `webServer`,路由被静默跳过 |
64
+ | 设置写不进去,但看不出原因 | schema 少了 `.volatile()`,或写入被 `.catch` 吞了 |
65
+ | provider 装了但模型选择器里没有 | 注册元数据(`inputModalities` / `reasoning`)没声明 |
66
+
67
+ ## 加一种余额形状(少见)
68
+
69
+ 如果某家有余额接口但形状是新的,改 [`src/balance.ts`](src/balance.ts):
70
+
71
+ 1. 给 `BalanceProbe.shape` 加一个值;
72
+ 2. 写一个 `parseXxx()`;
73
+ 3. 在 `balanceUrls()` 里给它路径;
74
+ 4. 在 `readBalance()` 里分发。
75
+
76
+ `balanceUrls()` 是**唯一**知道路径的地方 —— 这样预设就不可能和传输层意见相左。
77
+
78
+ 还有一条底线:**缺数字就报缺,绝不填 0。**
79
+ 「读不到」和「是空的」导致相反的行动,而只有一个是真的。
80
+
81
+ ## 开发
82
+
83
+ ```bash
84
+ pnpm install
85
+ pnpm run typecheck # 两个 tsconfig:node 半 + client 半
86
+ pnpm run test # 纯函数层,不需要装上 harness
87
+ pnpm run build # lib/index.js + lib/client.js
88
+ ```
89
+
90
+ 纯函数层(`presets` / `channels` / `route` / `usage-ledger` / `balance`)不 import harness,
91
+ 所以单测跑得很快。适配器与面板只通过宿主的公开接缝和它说话。
92
+
93
+ ## 判据必须承重
94
+
95
+ **每条新判据都要附一次「把它弄红」的实验结果。**
96
+
97
+ 把修复回滚,那条用例必须红;否则它只是装饰。这个仓的 CHANGELOG 里每个结论都带着它的负控制读数
98
+ (例如「去掉 `percent` 取反 ⇒ 精确红 3 条」)。
99
+
100
+ 三种特别容易写成**假判据**的形态,本项目都踩过:
101
+
102
+ 1. **判据与被判对象同源**。例:`bump` 改的是 CHANGELOG 首个标题,而 `version` 只读首个标题 ⇒
103
+ 永远读绿。要抓这类缺陷,判据必须碰**别的不变式**(现在是「集合包含」:原有每个标题都在,且恰好多一个)。
104
+ 2. **假传输层掩盖走错协议**。测试里传输层是假的,走错协议照样「通」。
105
+ 分发决策要用**结构判据**钉住(断言 `api: apiImplementations()` 存在、旧形态不回来)。
106
+ 3. **自写抽取器出错时不喊**。手写的 YAML 正则抽空了,用例照样绿。
107
+ ⇒ 抽取要**如实落盘原文**再看,不要在抽取器里顺手「清洗」。
108
+
109
+ ## 截图怎么更新
110
+
111
+ README 里的三张图来自 `scripts/screenshot-harness.tsx`,它渲染**真实面板组件**
112
+ (假数据、真 CSS/JSX/文案)—— 面板本身在 token 保护的 Web UI 后面,干净 profile 打开是空白页,
113
+ 所以直拍拿不到。
114
+
115
+ ```bash
116
+ msm("any-deepseek-develop", ["shot"]) # 打包成单文件页面
117
+ ```
118
+
119
+ 生成的页面在 `_tmp/shot/screenshot-harness.html`,用浏览器打开后截图即可。
120
+ 加 `?variant=add` 可以拍「添加渠道」表单那一种状态。
121
+
122
+ > ⚠️ 打包时有两个坑,都会让页面**空白且零报错**:产物里残留 `import`(内联模块无法解析)、
123
+ > 或外部 `<script src>` 在 `file://` 下被 CORS 拒。`shot` 已自带「不许有残留 import」的判据。
124
+
125
+ ## 发布
126
+
127
+ 维护者用的开发通道是 CCC 的 MSM `any-deepseek-develop`(safe-mode 下没有 bash)。
128
+ `stage check` 是「能不能装」的唯一判据 —— **`npm publish` 的退出码 0 不代表可安装**,
129
+ 新版本要一两分钟后才在 registry 上出现。
130
+
131
+ ## 许可
132
+
133
+ MIT
package/README.en.md ADDED
@@ -0,0 +1,217 @@
1
+ # dsh-any-deepseek
2
+
3
+ **Use DeepSeek credit from several providers inside DeepSeek Harness — with one extra row in the
4
+ model list, not N.**
5
+
6
+ You bought DeepSeek credit in several places: the official API, a relay, a cloud vendor, a cheap
7
+ one a friend recommended. This plugin collects them into **one** provider: paste a few keys, click
8
+ to switch — and usage is booked per real upstream, so you can see what each one cost this month.
9
+
10
+ ![Channel list](docs/screenshot-channels.png)
11
+
12
+ [中文](README.md) · English (this page)
13
+
14
+ ---
15
+
16
+ ## Before you install: is this for you?
17
+
18
+ **Yes**, if any of these is true:
19
+
20
+ - You have **more than one** DeepSeek endpoint and want to switch between them without editing
21
+ configuration.
22
+ - You want **per-channel usage accounting** instead of one blended total.
23
+ - You use relays whose model ids differ (`deepseek-v4.1-flash` / `deepseek/deepseek-v4.1-flash` /
24
+ `deepseek-flash`) and don't want to keep track yourself.
25
+
26
+ **No**, if:
27
+
28
+ - You have a single endpoint and it is the official one — the built-in `deepseek-official` already
29
+ covers you; this plugin is unnecessary.
30
+ - You want **automatic failover** (one endpoint dies, requests move to the next). v1 does not do
31
+ that — see [Why no failover](#why-no-failover).
32
+ - You want to **switch between models**. This is "one model, many sources", not a model router.
33
+
34
+ ## Install
35
+
36
+ Requires DeepSeek Harness to be installed already. In a terminal:
37
+
38
+ ```bash
39
+ dsh plugin --profile web add dsh-any-deepseek
40
+ ```
41
+
42
+ Then **restart DSH** and open Settings → **Any DeepSeek**.
43
+
44
+ > **Why the restart**: the plugin is added to the profile's load list, which is read only at
45
+ > startup. Afterwards, changing this plugin's UI half needs only a browser refresh — but the
46
+ > **first install** needs the restart.
47
+
48
+ ## First run: three steps
49
+
50
+ ### 1. Add a channel
51
+
52
+ Click **添加渠道** (Add channel). Two fields are required:
53
+
54
+ ![Add channel](docs/screenshot-add-channel.png)
55
+
56
+ | Field | What it means |
57
+ |---|---|
58
+ | **规格** (preset) | Pick your provider. It determines the endpoint, model id, and protocol — so you don't type them. |
59
+ | **密钥** (key) | The key that provider gave you. Paste it. There is no name to invent. |
60
+
61
+ **渠道 id** (channel id) is filled in for you — it is just the configuration key, and you can
62
+ change it. **显示名** (display name) defaults to the preset name; when you run two accounts on the
63
+ same provider, set something like "main" / "backup".
64
+
65
+ Bundled presets: DeepSeek Official, OpenCode Go, Ollama Cloud, Open Design, CommandCode.
66
+ For another provider, see [Adding a provider](#adding-a-provider).
67
+
68
+ ### 2. Make sure it is selected
69
+
70
+ The first channel you add **becomes the current channel automatically** — its row gets a radio
71
+ dot, a colour bar down the left edge, and a 「当前」 badge.
72
+
73
+ To switch later: click the dot at the left of a row.
74
+
75
+ ### 3. Start using it
76
+
77
+ In the model picker, choose **DeepSeek V4.1 Flash** (the provider is `any-deepseek`).
78
+ Every request then goes to whichever channel you selected.
79
+
80
+ ## Three things you will actually do
81
+
82
+ ### Switch channels
83
+
84
+ Click the dot at the left of a row.
85
+
86
+ > ⚠️ **Switching invalidates the prompt cache for the running conversation**, so switch at a topic
87
+ > boundary, or just start a new session.
88
+
89
+ ### See how much credit is left
90
+
91
+ Click **查询余额** (Check balance). It does not run when the panel opens — each check calls every
92
+ provider's server.
93
+
94
+ Different providers can report different things, and the panel keeps them apart:
95
+
96
+ | What you see | Meaning |
97
+ |---|---|
98
+ | `642.44 CNY`, `55.48 USD` | This provider reports an amount |
99
+ | `rolling 100% · weekly 81% · monthly 75%` | This provider reports **subscription usage percentages**, not money |
100
+ | **余额未尽** (verified live: …) | This provider has **no balance API**, but a minimal live request **succeeded** ⇒ the account is not blocked |
101
+ | 不支持查询 / 读取失败 / 未配置密钥 | Three different kinds of "nothing" |
102
+
103
+ > **"Not exhausted" is not "how much is left".** One successful request proves exactly three
104
+ > things: the endpoint is reachable, the key is valid, and the account is not blocked for lack of
105
+ > credit (an exhausted account answers 402, not 200). So the panel says only 「未尽」 and **never
106
+ > invents an amount**.
107
+
108
+ ### See what this month cost
109
+
110
+ Switch to the **用量** (Usage) tab.
111
+
112
+ ![Usage](docs/screenshot-usage.png)
113
+
114
+ Group by channel or by day. "Input" counts only cache misses; cache reads and writes are separate
115
+ columns, and the three together are the billable input. In the speed column, `—` means
116
+ **unmeasured — not zero**.
117
+
118
+ ## Is my key safe
119
+
120
+ **It is stored in your local configuration file, in plaintext.**
121
+
122
+ The path is `channels.<channel-id>.apiKey` inside the profile. The plugin never sends it to any
123
+ third party, but:
124
+
125
+ > ⚠️ `dsh --dump-config` prints the configuration verbatim and does **not** redact it. So:
126
+ > **do not share your profile configuration**, and do not paste its `git diff` anywhere public.
127
+
128
+ If you need to share config for troubleshooting, delete the `apiKey` lines first.
129
+
130
+ ## Troubleshooting
131
+
132
+ **The model list has no DeepSeek V4.1 Flash.**
133
+ → You have not restarted since installing. Restart DSH.
134
+
135
+ **Settings has no "Any DeepSeek".**
136
+ → Same; or check you installed into the `web` profile: `dsh plugin --profile web list`.
137
+
138
+ **`MISSING_CREDENTIAL`.**
139
+ → That channel has no key yet. Its status dot is hollow; click 「填写密钥」 to add one.
140
+
141
+ **`NO_CHANNEL_SELECTED`.**
142
+ → No channel is selected. The panel shows a hint at the bottom; click any row's dot.
143
+
144
+ **「查询余额」 shows 「读取失败」.**
145
+ → That provider did not answer properly just now; retry. If it never works, your network may not
146
+ reach it.
147
+
148
+ **401 / 403 / out of credit.**
149
+ → The key is wrong or expired, or that account really is out of money.
150
+
151
+ **Answers got slower or worse after switching.**
152
+ → Providers do not behave identically. Use the 测试 (Test) button per channel, and the 用量 tab to
153
+ compare speeds.
154
+
155
+ ## Why no failover
156
+
157
+ A failing channel reports its own error; it never silently moves to another. Three reasons:
158
+
159
+ - Silent failover makes "who answered this?" unknowable, while the usage table honestly records
160
+ the other provider — the two parts would disagree.
161
+ - Switching mid-conversation invalidates the prompt cache, costing more than the failure saved.
162
+ - Failure is information. Hide it and you never learn a provider is degrading.
163
+
164
+ ## Adding a provider
165
+
166
+ Five are bundled; adding another usually touches one file. See
167
+ **[CONTRIBUTING.md](CONTRIBUTING.md)**.
168
+
169
+ ## Configuration reference (advanced)
170
+
171
+ Everything lives under the `dsh-any-deepseek` namespace:
172
+
173
+ ```yaml
174
+ dsh-any-deepseek:
175
+ current: opencode-go # the current channel
176
+ channels:
177
+ opencode-go:
178
+ apiKey: sk-… # the key itself
179
+ open-design:
180
+ preset: open-design # use a bundled preset
181
+ label: Open Design (main) # optional display name
182
+ my-relay:
183
+ baseURL: https://relay.example/v1 # or declare it fully
184
+ model: deepseek-v4.1-flash
185
+ apiKey: sk-…
186
+ usage:
187
+ enabled: true
188
+ ```
189
+
190
+ **Protocol**: defaults to `openai-completions` (almost every relay speaks it). Set `api:
191
+ openai-responses` or `api: anthropic-messages` on **that one channel** only when it really speaks
192
+ something else.
193
+
194
+ **Thinking level**: each channel declares which levels it supports, and the level you pick in the
195
+ UI is the one sent upstream. Unsupported levels are not offered in the picker.
196
+
197
+ **Images**: sending and receiving images is supported.
198
+
199
+ **Legacy config**: early versions stored the key in `apiKeyEnv`. That field is still read — a value
200
+ that looks like an environment-variable name is resolved as one, otherwise it is used as the key
201
+ itself. `apiKey` always wins, so re-entering the key once in the panel migrates the entry.
202
+
203
+ ## Boundaries
204
+
205
+ - Does not modify Harness itself, and does not replace the built-in `deepseek-official` route.
206
+ - Exposes exactly one model id (`deepseek-v4.1-flash`); per-provider upstream ids are mapped by
207
+ presets.
208
+ - **No cost estimation** — there is no price table, only token counts.
209
+ - **No automatic failover** (v1).
210
+ - **No multi-model aggregation**.
211
+
212
+ ## License
213
+
214
+ MIT. This is a small practical tool — forks, issues and PRs are welcome. See
215
+ [CONTRIBUTING.md](CONTRIBUTING.md).
216
+
217
+ <https://github.com/tellmewhattodo/dsh-any-deepseek>
package/README.md CHANGED
@@ -1,172 +1,199 @@
1
1
  # dsh-any-deepseek
2
2
 
3
- One **DeepSeek V4.1 Flash** provider for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness).
4
-
5
- Instead of configuring every third-party DeepSeek relay as its own provider, install one
6
- provider with **exactly one model**, paste a key per channel in the settings panel, and pick
7
- which channel is live. Token usage is booked **per real upstream**, so you can see what each
8
- relay actually cost you in tokens.
9
-
10
- ```text
11
- composer → any-deepseek / deepseek-v4.1-flash
12
- │
13
- ├─ channel selected in the panel (manual switch, v1)
14
- ├─ key resolved from ctx.credentials per request
15
- └─ forwarded as openai-completions to the channel's endpoint
16
- ```
3
+ **在 DeepSeek Harness 里,用多家渠道的 DeepSeek 额度,而模型列表里只多出一行。**
4
+
5
+ 你在好几处买过 DeepSeek 的额度:官方、某家中转、某个云厂商、朋友推荐的便宜渠道。
6
+ 这个插件把它们收进**一个** provider,你粘几个 key、点一下就能换 —— 同时按真实渠道分开记账,
7
+ 你知道这个月每一家各花了多少 token。
8
+
9
+ ![渠道列表](docs/screenshot-channels.png)
10
+
11
+ [English](README.en.md) · 中文(本页)
12
+
13
+ ---
14
+
15
+ ## 装之前先看一眼:它适合你吗
17
16
 
18
- ## Why
17
+ **适合**,如果你符合下面任意一条:
19
18
 
20
- - **One row in the model picker.** Your relay list stops being N provider groups.
21
- - **Only a key is user input.** Endpoint, upstream model id, context window, output cap and
22
- compatibility flags ship in the plugin's own preset table.
23
- - **Per-channel accounting.** Input / cache-read / cache-write / output are recorded per
24
- request and grouped by the channel that actually served it.
25
- - **Keys never enter configuration.** They live in the harness credential store; the config
26
- only carries the reference name, exactly like the official Models page.
19
+ - 你有**不止一个** DeepSeek 渠道,并且想在 Harness 里随意切换而不用改配置。
20
+ - 你想要**按渠道分开的用量统计**,而不是所有请求混成一个总数。
21
+ - 你用中转站,而它们的模型名各不相同(`deepseek-v4.1-flash` / `deepseek/deepseek-v4.1-flash` /
22
+ `deepseek-flash`),懒得每次自己对应。
27
23
 
28
- ## Install
24
+ **不适合**,如果:
25
+
26
+ - 你只有一个渠道,且它就是官方 —— Harness 自带的 `deepseek-official` 已经够用,不必装。
27
+ - 你想要**自动故障转移**(一家挂了自动换下一家)。本插件 v1 不做,见 [为什么不做自动切换](#为什么不做自动切换)。
28
+ - 你想要**多模型切换**。这个插件是「一个模型、多个来源」,不是模型路由器。
29
+
30
+ ## 安装
31
+
32
+ 需要已经装好 DeepSeek Harness。在终端执行:
29
33
 
30
34
  ```bash
31
35
  dsh plugin --profile web add dsh-any-deepseek
32
36
  ```
33
37
 
34
- Then restart DSH and open **Settings → Any DeepSeek**.
38
+ 然后**重启 DSH**,打开 设置 → **Any DeepSeek**。
35
39
 
36
- > **Restart is required for a first install** — the profile's layer list changed, and the
37
- > composing graph is built at boot. Editing an already-installed plugin's client half reloads
38
- > in the browser without a restart; its node half does not.
39
- >
40
- > `dsh plugin` forwards its arguments to pnpm, so `add <spec>` accepts a registry name, a
41
- > tarball path, or a local directory (which installs as a symlink).
40
+ > **为什么必须重启**:插件被加进了 profile 的加载列表,而这份列表只在 DSH 启动时读取。
41
+ > 之后改这个插件的界面部分只需刷新浏览器,但**第一次装**一定要重启。
42
42
 
43
- ## Configuration
43
+ ## 第一次用:三步
44
44
 
45
- Everything lives under the `dsh-any-deepseek` settings namespace — that is the **profile entry
46
- id** from `cordis.patch.yml`, and it is what the panel binds to:
45
+ ### 第 1 步:加一个渠道
47
46
 
48
- ```yaml
49
- dsh-any-deepseek:
50
- current: opencode-go # the live channel (manual)
51
- channels:
52
- opencode-go:
53
- apiKey: sk-… # the key itself
54
- commandcode:
55
- apiKey: sk-… # the key itself
56
- usage:
57
- enabled: true
58
- ```
47
+ 点 **添加渠道**,填两个必填项就能用:
59
48
 
60
- `channels.<id>` may be either:
49
+ ![添加渠道](docs/screenshot-add-channel.png)
61
50
 
62
- - **preset-backed** — `preset: opencode-go` plus optional overrides; or
63
- - **fully declared** — `baseURL` + `model` are required; `api` defaults to
64
- `openai-completions`, and `contextWindow` / `maxTokens` fall back to documented defaults
65
- (the panel shows a warning next to the row when a default was applied rather than declared).
51
+ | 填什么 | 说明 |
52
+ |---|---|
53
+ | **规格** | 选你用的那家。它决定地址、模型名、协议,所以这三样你不用管。 |
54
+ | **密钥** | 那家给你的 key,直接粘。不需要先起名字。 |
66
55
 
67
- ### Where the key lives
56
+ **渠道 id** 会自动填好(可以改,只是配置里的键名)。**显示名**留空的话用规格的默认名 ——
57
+ 同一家开两个号时,填上「主号」「备号」之类会好认。
68
58
 
69
- `apiKey` holds the key **itself**, on the channel. The panel never asks you to invent a name for
70
- it — you paste the key, and it is written here.
59
+ 内置规格:DeepSeek Official、OpenCode Go、Ollama Cloud、Open Design、CommandCode
60
+ (这些名字与下拉框里显示的完全一致)。
61
+ 要接别家,见 [接一个新渠道](#接一个新渠道)。
71
62
 
72
- ⚠️ **That means the key is in your profile configuration.** `dsh --dump-config` prints patch
73
- layers verbatim and does not redact, so a diagnostic dump, a `git diff` of the profile, or a
74
- pasted config includes it. Do not share the profile document. Environment-variable injection
75
- (`-e SOME_KEY=…`) is not consulted — nothing resolves a name.
63
+ ### 第 2 步:确认它选中了
76
64
 
77
- `apiKeyEnv` is still **read** for configurations written before this change: when its value
78
- matches the credential-reference grammar (`^[A-Za-z_][A-Za-z0-9_]*$`) it is resolved as a name;
79
- when it does not, it cannot be a name, so it is adopted as the key itself. `apiKey` always wins
80
- when both are present, so re-keying through the panel is enough to migrate an entry.
65
+ 第一个加进去的渠道会**自动成为当前渠道**,行首有圆点、左侧有色条、右侧有「当前」标记。
81
66
 
82
- A channel with no key still **appears** in the panel — that is how you find and fix it. Requests
83
- through it fail with `MISSING_CREDENTIAL`, and the row shows an unconfigured dot.
67
+ 以后想换:点那一行左边的圆点即可。
84
68
 
85
- ### Channels vs. protocols
69
+ ### 第 3 步:开始用
86
70
 
87
- `openai-completions` is the default and the recommended protocol: it is what almost every
88
- relay speaks, it exposes the largest compatibility surface (~20 switches), and its usage
89
- normalization already reconciles the three cache-token spellings relays use
90
- (`prompt_tokens_details.cached_tokens`, `prompt_cache_hit_tokens`, top-level `cached_tokens`).
71
+ 在模型的对话界面里选模型,选 **DeepSeek V4.1 Flash**(provider 名是 `any-deepseek`)。
72
+ 之后所有请求都会发到你选中的那个渠道。
91
73
 
92
- Set `api` on one channel only when that relay speaks something else (`openai-responses`,
93
- `anthropic-messages`). The picker never changes per channel behind your back.
74
+ ## 日常会用到的三件事
94
75
 
95
- ### Switching channels
76
+ ### 换一个渠道
96
77
 
97
- v1 is deliberately manual. A channel that fails reports its error; it does not silently fail
98
- over to another one. Switching invalidates the prompt cache for the running conversation, so
99
- switch at a natural boundary (or start a new session).
78
+ 点行首的圆点。
100
79
 
101
- The current channel is marked on its row (left colour bar, stronger border, tinted background)
102
- rather than by a small badge — a single-choice list has to be readable at a glance.
80
+ > ⚠️ **换渠道会让当前对话的 prompt 缓存失效**,所以尽量在话题切换时换,或者干脆新开一个会话。
103
81
 
104
- ### Thinking level
82
+ ### 看还剩多少钱
105
83
 
106
- Each channel declares its selectable thinking levels, and the level picked in the harness is
107
- passed through to the relay:
84
+ 点 **查询余额**。它不会在打开面板时自动跑 —— 因为每次都要请求每一家的服务器。
108
85
 
109
- ```
110
- harness reasoningEffort → pi-ai ThinkingLevel → model.thinkingLevelMap → wire value
111
- ```
86
+ 不同家能给出的东西不一样,面板会如实区分:
112
87
 
113
- There is no mapping layer in this plugin: the `thinkingLevelMap` the transport applies **is**
114
- the mapping, and a second one here would drift from it. A level declared `null` is unsupported
115
- and is not offered in the picker.
88
+ | 你看到的 | 意思 |
89
+ |---|---|
90
+ | `642.44 CNY`、`55.48 USD` | 这家能报金额 |
91
+ | `rolling 100% · weekly 81% · monthly 75%` | 这家只给**订阅用量百分比**,不给钱 |
92
+ | **余额未尽**(实测可用:…) | 这家**没有余额接口**,但我发了一次最小请求,**它答了** ⇒ 账户没停 |
93
+ | 不支持查询 / 读取失败 / 未配置密钥 | 三种不同的「没拿到」 |
116
94
 
117
- ### Display names
95
+ > **「余额未尽」不等于「还剩多少」。** 一次成功的请求只证明三件事:地址通、key 对、账户没因为欠费被拒
96
+ > (欠费的账户会回 402,不会回 200)。所以这里只写「未尽」,**不会编一个金额给你**。
118
97
 
119
- `label` is a free-form display name, and it is what tells two accounts on the same relay apart.
120
- Rename a channel from the panel; the channel **id** never changes, because the id is the
121
- channel's identity (its key and its `current` target hang off it).
98
+ ### 看这个月花了多少
122
99
 
123
- ### Image input
100
+ 切到 **用量** 分页。
124
101
 
125
- Not supported yet, and declared as such. A harness `ImageBlock` carries only an
126
- `attachmentId`; turning that into request bytes requires the durable attachment service, which
127
- this adapter does not consult. Declaring image support anyway would make the harness stop
128
- substituting its placeholder text and drop the image silently — worse than declaring text-only.
102
+ ![用量统计](docs/screenshot-usage.png)
129
103
 
130
- ## Usage accounting
104
+ 可以按渠道看,也可以按天看。「输入」只算没命中缓存的部分,缓存读/写单独列出 ——
105
+ 三者相加才是计费输入。速度一栏的 `—` 表示没测到,**不是 0**。
131
106
 
132
- The ledger is append-only JSONL, split by month, under the harness home:
107
+ ## 我的 key 安全吗
133
108
 
134
- ```text
135
- ~/.dsh/any-deepseek/usage-YYYY-MM.jsonl
136
- ```
109
+ **它存在你本机的配置文件里,明文。**
137
110
 
138
- One line per request:
111
+ 位置是 profile 的 `channels.<渠道id>.apiKey`。它不会被这个插件发到任何第三方,
112
+ 但是:
139
113
 
140
- ```json
141
- {"at":"2026-10-19T10:12:03+08:00","channel":"opencode-go","model":"deepseek-v4.1-flash","ok":true,"ms":4210,"firstTokenMs":810,"usage":{"input":812,"output":96,"cacheRead":40960,"cacheWrite":0,"reasoning":0,"total":41868}}
142
- ```
114
+ > ⚠️ `dsh --dump-config` 会把配置原样打印出来、**不做脱敏**。所以:
115
+ > **别把 profile 配置文件发给别人**,也别把它的 `git diff` 贴到公开的地方。
143
116
 
144
- `usage` is `null` when a relay does not report token counts — the panel shows `—`, never `0`,
145
- because a zero would read as "this request was free".
117
+ 如果你需要分享配置做排查,先把 `apiKey` 那几行删掉。
146
118
 
147
- **Counting convention.** `TokenUsage` counts are *disjoint*: `input` is uncached input only,
148
- cache reads and writes are reported separately, and the three together are the billable input.
149
- The stats table states this next to the numbers.
119
+ ## 出问题了
150
120
 
151
- ## Development
121
+ **模型列表里没有 DeepSeek V4.1 Flash。**
122
+ → 装完还没重启。重启 DSH。
152
123
 
153
- ```bash
154
- pnpm install
155
- pnpm run typecheck # host types come from pinned devDependencies
156
- pnpm run test # pure layers only; no harness needed
157
- pnpm run build # lib/index.js + lib/client.js
124
+ **设置里找不到「Any DeepSeek」。**
125
+ → 同上;或确认装的是 `web` 这个 profile:`dsh plugin --profile web list`。
126
+
127
+ **报 `MISSING_CREDENTIAL`。**
128
+ → 这个渠道还没填 key。面板里那一行的状态点是空心的,点「填写密钥」补上。
129
+
130
+ **报 `NO_CHANNEL_SELECTED`。**
131
+ → 一个渠道都没选中。面板底部会有提示,点任意一行的圆点即可。
132
+
133
+ **「查询余额」显示「读取失败」。**
134
+ → 这家服务器这会儿没答好,稍后再试。如果一直这样,可能是网络到不了它。
135
+
136
+ **报 401 / 403 / 余额不足。**
137
+ → key 不对或过期,或者那家账户真的没钱了。
138
+
139
+ **换渠道以后回答变慢/变差。**
140
+ → 不同家的模型行为不完全一样。用「测试」按钮逐家试,用「用量」分页看各家的速度。
141
+
142
+ ## 为什么不做自动切换
143
+
144
+ 一个渠道失败就**报它自己的错**,不会静默换到另一家。三个理由:
145
+
146
+ - 静默切换会让「这次答案是谁给的」变得不可知,而用量表会照实记另一家 —— 两处对不上。
147
+ - 半路换渠道会让 prompt 缓存失效,成本比你省下的那次失败更高。
148
+ - 失败是信息。藏起来,你就不知道某家正在坏掉。
149
+
150
+ ## 接一个新渠道
151
+
152
+ 内置了 5 家,接别家通常只需要改一个文件。见 **[CONTRIBUTING.md](CONTRIBUTING.md)**。
153
+
154
+ ## 配置参考(进阶)
155
+
156
+ 设置都在 `dsh-any-deepseek` 这个命名空间下:
157
+
158
+ ```yaml
159
+ dsh-any-deepseek:
160
+ current: opencode-go # 当前渠道
161
+ channels:
162
+ opencode-go:
163
+ apiKey: sk-… # key 本体
164
+ open-design:
165
+ preset: open-design # 用内置规格
166
+ label: Open Design(主号) # 可选,显示名
167
+ my-relay:
168
+ baseURL: https://relay.example/v1 # 或者完全手写
169
+ model: deepseek-v4.1-flash
170
+ apiKey: sk-…
171
+ usage:
172
+ enabled: true
158
173
  ```
159
174
 
160
- The pure layers (`presets`, `channels`, `route`, `usage-ledger`) do not import the harness and
161
- are covered by the unit tests. The adapter and panel talk to the host through public seams
162
- only.
175
+ **协议**:默认 `openai-completions`(几乎所有中转都说它)。只有当某家真的说别的协议时,
176
+ 在**那一个**渠道上设 `api: openai-responses` 或 `api: anthropic-messages`。
177
+
178
+ **思考强度**:每个渠道声明自己支持哪些档位,你在界面上选哪档就传到哪档。
179
+ 声明成不支持的档位不会出现在选择器里。
180
+
181
+ **图片**:支持收发图片。
182
+
183
+ **兼容旧配置**:早期版本把 key 存在 `apiKeyEnv` 里。这个字段仍会被读:
184
+ 值像环境变量名就当名字解析,不像就直接当 key 用。`apiKey` 存在时永远优先 ——
185
+ 所以在面板里重填一次 key 就迁移完了。
186
+
187
+ ## 边界
163
188
 
164
- ## Boundaries
189
+ - 不修改 Harness 本体,也不替代自带的 `deepseek-official` 路由。
190
+ - 只暴露一个模型 id(`deepseek-v4.1-flash`);各家的上游模型名由规格映射。
191
+ - **不做费用估算** —— 没有价目表,只有 token 数。
192
+ - **不做自动故障转移**(v1)。
193
+ - **不做多模型聚合**。
165
194
 
166
- - Does not modify the harness, and does not replace `deepseek-official` or `llm-pi-ai` routes.
167
- - Exposes exactly one model id. Upstream model ids differ per relay and are mapped in presets.
168
- - No cost estimation (no price table), no automatic failover (v1), no multi-model aggregation.
195
+ ## 许可
169
196
 
170
- ## License
197
+ MIT。这是个实用小工具,欢迎 fork、提 issue、发 PR —— 见 [CONTRIBUTING.md](CONTRIBUTING.md)。
171
198
 
172
- MIT
199
+ <https://github.com/tellmewhattodo/dsh-any-deepseek>