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.
- package/CONTRIBUTING.md +133 -0
- package/README.en.md +217 -0
- package/README.md +149 -122
- package/docs/screenshot-add-channel.png +0 -0
- package/docs/screenshot-channels.png +0 -0
- package/docs/screenshot-usage.png +0 -0
- package/lib/client.js +332 -186
- package/package.json +6 -1
package/CONTRIBUTING.md
ADDED
|
@@ -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
|
+

|
|
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
|
+

|
|
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
|
+

|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
provider
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
```
|
|
3
|
+
**在 DeepSeek Harness 里,用多家渠道的 DeepSeek 额度,而模型列表里只多出一行。**
|
|
4
|
+
|
|
5
|
+
你在好几处买过 DeepSeek 的额度:官方、某家中转、某个云厂商、朋友推荐的便宜渠道。
|
|
6
|
+
这个插件把它们收进**一个** provider,你粘几个 key、点一下就能换 —— 同时按真实渠道分开记账,
|
|
7
|
+
你知道这个月每一家各花了多少 token。
|
|
8
|
+
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
[English](README.en.md) · 中文(本页)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 装之前先看一眼:它适合你吗
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
**适合**,如果你符合下面任意一条:
|
|
19
18
|
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
|
|
23
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
38
|
+
然后**重启 DSH**,打开 设置 → **Any DeepSeek**。
|
|
35
39
|
|
|
36
|
-
>
|
|
37
|
-
>
|
|
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
|
-
##
|
|
43
|
+
## 第一次用:三步
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
id** from `cordis.patch.yml`, and it is what the panel binds to:
|
|
45
|
+
### 第 1 步:加一个渠道
|
|
47
46
|
|
|
48
|
-
|
|
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
|
-
|
|
49
|
+

|
|
61
50
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
51
|
+
| 填什么 | 说明 |
|
|
52
|
+
|---|---|
|
|
53
|
+
| **规格** | 选你用的那家。它决定地址、模型名、协议,所以这三样你不用管。 |
|
|
54
|
+
| **密钥** | 那家给你的 key,直接粘。不需要先起名字。 |
|
|
66
55
|
|
|
67
|
-
|
|
56
|
+
**渠道 id** 会自动填好(可以改,只是配置里的键名)。**显示名**留空的话用规格的默认名 ——
|
|
57
|
+
同一家开两个号时,填上「主号」「备号」之类会好认。
|
|
68
58
|
|
|
69
|
-
|
|
70
|
-
|
|
59
|
+
内置规格:DeepSeek Official、OpenCode Go、Ollama Cloud、Open Design、CommandCode
|
|
60
|
+
(这些名字与下拉框里显示的完全一致)。
|
|
61
|
+
要接别家,见 [接一个新渠道](#接一个新渠道)。
|
|
71
62
|
|
|
72
|
-
|
|
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
|
-
|
|
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
|
-
|
|
83
|
-
through it fail with `MISSING_CREDENTIAL`, and the row shows an unconfigured dot.
|
|
67
|
+
以后想换:点那一行左边的圆点即可。
|
|
84
68
|
|
|
85
|
-
###
|
|
69
|
+
### 第 3 步:开始用
|
|
86
70
|
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
93
|
-
`anthropic-messages`). The picker never changes per channel behind your back.
|
|
74
|
+
## 日常会用到的三件事
|
|
94
75
|
|
|
95
|
-
###
|
|
76
|
+
### 换一个渠道
|
|
96
77
|
|
|
97
|
-
|
|
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
|
-
|
|
102
|
-
rather than by a small badge — a single-choice list has to be readable at a glance.
|
|
80
|
+
> ⚠️ **换渠道会让当前对话的 prompt 缓存失效**,所以尽量在话题切换时换,或者干脆新开一个会话。
|
|
103
81
|
|
|
104
|
-
###
|
|
82
|
+
### 看还剩多少钱
|
|
105
83
|
|
|
106
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
88
|
+
| 你看到的 | 意思 |
|
|
89
|
+
|---|---|
|
|
90
|
+
| `642.44 CNY`、`55.48 USD` | 这家能报金额 |
|
|
91
|
+
| `rolling 100% · weekly 81% · monthly 75%` | 这家只给**订阅用量百分比**,不给钱 |
|
|
92
|
+
| **余额未尽**(实测可用:…) | 这家**没有余额接口**,但我发了一次最小请求,**它答了** ⇒ 账户没停 |
|
|
93
|
+
| 不支持查询 / 读取失败 / 未配置密钥 | 三种不同的「没拿到」 |
|
|
116
94
|
|
|
117
|
-
|
|
95
|
+
> **「余额未尽」不等于「还剩多少」。** 一次成功的请求只证明三件事:地址通、key 对、账户没因为欠费被拒
|
|
96
|
+
> (欠费的账户会回 402,不会回 200)。所以这里只写「未尽」,**不会编一个金额给你**。
|
|
118
97
|
|
|
119
|
-
|
|
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
|
-
|
|
100
|
+
切到 **用量** 分页。
|
|
124
101
|
|
|
125
|
-
|
|
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
|
+

|
|
129
103
|
|
|
130
|
-
|
|
104
|
+
可以按渠道看,也可以按天看。「输入」只算没命中缓存的部分,缓存读/写单独列出 ——
|
|
105
|
+
三者相加才是计费输入。速度一栏的 `—` 表示没测到,**不是 0**。
|
|
131
106
|
|
|
132
|
-
|
|
107
|
+
## 我的 key 安全吗
|
|
133
108
|
|
|
134
|
-
|
|
135
|
-
~/.dsh/any-deepseek/usage-YYYY-MM.jsonl
|
|
136
|
-
```
|
|
109
|
+
**它存在你本机的配置文件里,明文。**
|
|
137
110
|
|
|
138
|
-
|
|
111
|
+
位置是 profile 的 `channels.<渠道id>.apiKey`。它不会被这个插件发到任何第三方,
|
|
112
|
+
但是:
|
|
139
113
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
```
|
|
114
|
+
> ⚠️ `dsh --dump-config` 会把配置原样打印出来、**不做脱敏**。所以:
|
|
115
|
+
> **别把 profile 配置文件发给别人**,也别把它的 `git diff` 贴到公开的地方。
|
|
143
116
|
|
|
144
|
-
|
|
145
|
-
because a zero would read as "this request was free".
|
|
117
|
+
如果你需要分享配置做排查,先把 `apiKey` 那几行删掉。
|
|
146
118
|
|
|
147
|
-
|
|
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
|
-
|
|
121
|
+
**模型列表里没有 DeepSeek V4.1 Flash。**
|
|
122
|
+
→ 装完还没重启。重启 DSH。
|
|
152
123
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
189
|
+
- 不修改 Harness 本体,也不替代自带的 `deepseek-official` 路由。
|
|
190
|
+
- 只暴露一个模型 id(`deepseek-v4.1-flash`);各家的上游模型名由规格映射。
|
|
191
|
+
- **不做费用估算** —— 没有价目表,只有 token 数。
|
|
192
|
+
- **不做自动故障转移**(v1)。
|
|
193
|
+
- **不做多模型聚合**。
|
|
165
194
|
|
|
166
|
-
|
|
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
|
-
|
|
197
|
+
MIT。这是个实用小工具,欢迎 fork、提 issue、发 PR —— 见 [CONTRIBUTING.md](CONTRIBUTING.md)。
|
|
171
198
|
|
|
172
|
-
|
|
199
|
+
<https://github.com/tellmewhattodo/dsh-any-deepseek>
|