@omdp/dsh-key-fallback 3.1.0 → 3.1.2
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/README.md +42 -43
- package/README.zh-CN.md +75 -0
- package/lib/client.js +13 -6
- package/lib/index.js +7 -4
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -1,41 +1,34 @@
|
|
|
1
1
|
# @omdp/dsh-key-fallback
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
-
|
|
21
|
-
|
|
22
|
-
- **明文揭示**: every key row has an eye toggle (`👁` / `🙈`) that fetches and shows the real value via `GET /keys/plain` (only for
|
|
23
|
-
|
|
24
|
-
-
|
|
25
|
-
|
|
26
|
-
- **Per-key controls**: rename/update value, set the "失败后→" next key (nextRef), lock the pool to a specific key ("设为当前"),
|
|
27
|
-
delete a key (the env key is not deletable).
|
|
28
|
-
- **Pool-level "锁定"**: lock the pool to a specific key or return to 自动轮换 (cursor-based).
|
|
3
|
+
[简体中文](README.zh-CN.md) | English
|
|
4
|
+
|
|
5
|
+
**Multi-key API key pool with automatic rotation for [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness)** — sits between the LLM adapter and the credential store. Before each request the plugin picks a key from the per-provider pool and pre-writes it into the provider's credential reference; when a configured trigger error occurs it marks the failed key cooling (fixed `cooldownMs`, no exponential backoff) and advances to the next key. **Re-sending is left entirely to DSH's own `dsh-llm-retry`** — this plugin never re-sends on its own; it only switches the key and lets the retry policy decide.
|
|
6
|
+
|
|
7
|
+
Current version: **v3.1.2** (`v6` UI generation).
|
|
8
|
+
|
|
9
|
+
## Requirements
|
|
10
|
+
|
|
11
|
+
- DeepSeek Harness with a `web`-profile GUI (`npx @deepseek-ai/dsh web`)
|
|
12
|
+
- Node.js `^22.19` or `>=24`
|
|
13
|
+
- Peer ranges for `@deepseek-ai/dsh-credentials` / `dsh-llm` / `dsh-settings` cover the full `0.1.x` pre-release line — **`0.1.0-rc.6`, `0.1.1-rc.2`, and `0.1.2-alpha.1` all satisfy them**. The plugin only uses the credential-reference half (`resolve`/`describe`/`set`/`unset`/`credentialRef` — stable since `0.1.0-rc.6`) and the `agent/request` + `agent/request-error` waterfall (payload unchanged across these versions); `isCredentialRefName` (added `rc.8`) is implemented locally for compatibility. The peer range deliberately enumerates each `0.1.x` series because npm semver only matches pre-releases within the same `[major,minor,patch]` triple.
|
|
14
|
+
|
|
15
|
+
## What v3.1.2 offers
|
|
16
|
+
|
|
17
|
+
**Settings → API Key 回退** — a top-level settings page with a redesigned UI (status dots, badges, gradient pool cards, per-key rows):
|
|
18
|
+
|
|
19
|
+
- **Configurable rotation triggers that are actually enforced** (`rotateOn`): click chips to select which error codes cause rotation. The preset chips cover the **full DSH `LlmError` standard code set** — `QUOTA` / `AUTH` / `RATE_LIMIT` / `TIMEOUT` / `TRANSPORT` / `SERVER` / `EMPTY_RESPONSE` / `INVALID_CREDENTIAL` — or add a non-standard/custom code (matched **exactly** against the provider's `failure.code`). What you save is what runs: there is no "magic upgrade" that rewrites three selected codes back into a six-code superset. `ABORTED` (user cancel) never triggers rotation.
|
|
20
|
+
- **真实当前使用 key 显示**: the page shows which key is actually being used right now (derived from the last value written to the provider's env) — not a truncated hash, not a guessed name.
|
|
21
|
+
- **短 ref 命名**: new keys are auto-named `key_fallback_<provider>_key1`, `key_fallback_<provider>_key2`, … so the UI shows clean short names (`key1`, `key2`, … or your custom `label`) instead of long ref strings. Existing legacy long refs are migrated **once, automatically and idempotently** (write new ref → persist config → best-effort unset old ref; any failure aborts and retries safely).
|
|
22
|
+
- **明文揭示**: every key row has an eye toggle (`👁` / `🙈`) that fetches and shows the real value via `GET /keys/plain` (only for keys owned by that pool, or the pool's env key). The env key row also reveals plaintext — it is not swallowed by the read-only note.
|
|
23
|
+
- **环境密钥可编辑**: the pool's env key (`AGNES_API_KEY` etc.) can be updated right in the page when it is backed by the writable credential file. If it is supplied by the launching environment (read-only), the UI says so and refuses to edit (HTTP 400).
|
|
24
|
+
- **Per-key controls**: update value, set the "失败后→" next key (`nextRef`), lock the pool to a specific key ("设为当前"), delete a key (the env key is not deletable).
|
|
25
|
+
- **Pool-level controls**: enable switch, cooldown display, "↺ 重置冷却", pool lock/auto-rotation, per-pool status (`live` / `cooling` / `recovered`).
|
|
29
26
|
|
|
30
27
|
## Rotation semantics
|
|
31
28
|
|
|
32
|
-
- **pick
|
|
33
|
-
|
|
34
|
-
- **
|
|
35
|
-
`rotateOn` (code / status mapping / message keyword). Marks the failed key with a fixed `cooldownMs` (no exponential backoff), then
|
|
36
|
-
switches to `nextRef` if configured, else the next live key.
|
|
37
|
-
- **re-send** is left entirely to DSH's `dsh-llm-retry` with the user's own retry policy; this plugin only switches the key and writes
|
|
38
|
-
config/env. `ABORTED` (user cancel) never triggers rotation.
|
|
29
|
+
- **pick** (`agent/request`, pre-write): respects `useKeyRef` lock first (but a cooling locked key is skipped so the pool never deadlocks on a bad key), otherwise cursor round-robin over live keys. The chosen key is written via `credentials.set` **and** `process.env` so the provider actually authenticates with it.
|
|
30
|
+
- **fail** (`agent/request-error`, registered `prepend` so it runs before `dsh-llm-retry`): only when the error matches the pool's `rotateOn` — by `failure.code` (exact), by HTTP status mapping (`429→RATE_LIMIT`, `401/403→AUTH`, `402→QUOTA`, `5xx→SERVER`), or by message keyword. Marks the failed key with a fixed `cooldownMs` (default 30 s), then switches to `nextRef` if configured, else the next live key.
|
|
31
|
+
- **re-send** is left entirely to DSH's `dsh-llm-retry` with the user's own per-provider retry policy. The two are independent and complementary: the retry plugin decides "retry the same key N times" (`retryPolicy.retryableCodes`, default `EMPTY_RESPONSE/RATE_LIMIT/SERVER/TIMEOUT/TRANSPORT` — which notably excludes `AUTH`/`QUOTA`); this plugin decides "switch to the next key". So e.g. an `AUTH` failure (which retry would not re-send anyway) still rotates to the next key — that next request will authenticate with the fresh key.
|
|
39
32
|
|
|
40
33
|
## Diagnostics
|
|
41
34
|
|
|
@@ -45,32 +38,38 @@ Current version is **v6** (`3.1.0`).
|
|
|
45
38
|
## Install
|
|
46
39
|
|
|
47
40
|
```sh
|
|
48
|
-
# from
|
|
41
|
+
# from npm (recommended)
|
|
42
|
+
cd ~/.dsh/profiles/web
|
|
43
|
+
pnpm add @omdp/dsh-key-fallback
|
|
44
|
+
|
|
45
|
+
# or from a local checkout
|
|
49
46
|
dsh plugin --profile web add link:D:/WorkSpace/omdp/dsh-key-fallback
|
|
50
|
-
# or from npm
|
|
51
|
-
dsh plugin --profile web add @omdp/dsh-key-fallback
|
|
52
47
|
|
|
53
48
|
# restart DSH, then open Settings → API Key 回退
|
|
54
49
|
```
|
|
55
50
|
|
|
56
|
-
> Note:
|
|
57
|
-
> `~/.dsh/profiles/web/node_modules/@omdp/dsh-key-fallback`; copying updated `lib/index.js` + `lib/client.js` there and restarting DSH
|
|
58
|
-
> picks up v6.
|
|
51
|
+
> Note: the profile currently installs the package from npm (`^3.x`) into `~/.dsh/profiles/web/node_modules/@omdp/dsh-key-fallback`; copying updated `lib/index.js` + `lib/client.js` there and restarting DSH picks up a newer version. The package declares a `dsh.bundle.patch`, so it activates automatically — no manual `cordis.patch.yml` editing.
|
|
59
52
|
|
|
60
53
|
## HTTP API (host)
|
|
61
54
|
|
|
62
55
|
| Method | Path | Purpose |
|
|
63
56
|
| --- | --- | --- |
|
|
64
|
-
| GET | `/dsh-key-fallback/pools` | list pools with live status, activeRef
|
|
65
|
-
| POST | `/dsh-key-fallback/pools` | create/update pool (enabled
|
|
57
|
+
| GET | `/dsh-key-fallback/pools` | list pools with live status, `activeRef`, env writability/source, per-key status |
|
|
58
|
+
| POST | `/dsh-key-fallback/pools` | create/update pool (`enabled`, `cooldownMs`, `rotateOn`, `useKeyRef`, …) |
|
|
66
59
|
| DELETE | `/dsh-key-fallback/pools?provider=` | delete pool + all its keys |
|
|
67
60
|
| POST | `/dsh-key-fallback/keys` | add key (auto short ref `key_fallback_<p>_keyN`) |
|
|
68
|
-
| PATCH | `/dsh-key-fallback/keys` | update value / label / nextRef / useKeyRef (env key value = edit env) |
|
|
61
|
+
| PATCH | `/dsh-key-fallback/keys` | update value / label / `nextRef` / `useKeyRef` (env key value = edit env) |
|
|
69
62
|
| DELETE | `/dsh-key-fallback/keys?provider=&ref=` | delete key (env key refused) |
|
|
70
63
|
| GET | `/dsh-key-fallback/keys/plain?provider=&ref=` | reveal real value (pool-owned keys / env key only) |
|
|
71
64
|
| POST | `/dsh-key-fallback/reset` | reset cooldown state for a pool |
|
|
72
65
|
| GET/POST | `/dsh-key-fallback/diag` | diagnostics buffer |
|
|
73
66
|
|
|
67
|
+
## Known limitations
|
|
68
|
+
|
|
69
|
+
- Cooldown is a fixed per-pool `cooldownMs` (no exponential backoff); a cooled key becomes live again when the timer expires.
|
|
70
|
+
- The plugin manages rotation/cooldown state in-memory plus persisted pool config (`keyFallback.providers` in `settings.yaml`); a DSH restart re-reads config and recomputes live status.
|
|
71
|
+
- The env key cannot be deleted through the UI (it is the pool's identity).
|
|
72
|
+
|
|
74
73
|
## License
|
|
75
74
|
|
|
76
|
-
MIT
|
|
75
|
+
MIT
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# @omdp/dsh-key-fallback
|
|
2
|
+
|
|
3
|
+
English | [简体中文](README.md)
|
|
4
|
+
|
|
5
|
+
**为 [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) 提供多 key 池 + 自动轮换**——插件位于 LLM 适配器与凭证存储之间:每次请求前从按 provider 分组的 key 池里选一把,预写入该 provider 的凭证引用;遇到配置的触发错误时,把失败 key 标记为冷却(固定 `cooldownMs`,无指数退避)并前进到下一把。**重发完全交给 DSH 自带的 `dsh-llm-retry`**——本插件从不自行重发,只负责换 key,重试策略由 retry policy 决定。
|
|
6
|
+
|
|
7
|
+
当前版本:**v3.1.2**(v6 UI 代)。
|
|
8
|
+
|
|
9
|
+
## 环境要求
|
|
10
|
+
|
|
11
|
+
- DeepSeek Harness 带 `web` profile GUI(`npx @deepseek-ai/dsh web`)
|
|
12
|
+
- Node.js `^22.19` 或 `>=24`
|
|
13
|
+
- `@deepseek-ai/dsh-credentials` / `dsh-llm` / `dsh-settings` 的 peer 范围覆盖完整 `0.1.x` 预发布线——**`0.1.0-rc.6`、`0.1.1-rc.2`、`0.1.2-alpha.1` 均满足**。插件只使用 credential-reference 半边(`resolve`/`describe`/`set`/`unset`/`credentialRef`,自 `0.1.0-rc.6` 起稳定)与 `agent/request` + `agent/request-error` waterfall(载荷跨这些版本未变);`isCredentialRefName`(rc.8 新增)本地实现兜底。peer 范围故意逐个枚举 `0.1.x` 系列,因为 npm semver 只匹配同 `[major,minor,patch]` 三元组内的预发布。
|
|
14
|
+
|
|
15
|
+
## v3.1.2 提供什么
|
|
16
|
+
|
|
17
|
+
**设置 → API Key 回退** —— 顶层设置页,全新 UI(状态点、徽章、渐变池卡片、逐 key 行):
|
|
18
|
+
|
|
19
|
+
- **可配置且真正生效的轮转触发码**(`rotateOn`):点选 chips 决定哪些错误码触发轮换。预设 chips 覆盖 **DSH `LlmError` 标准码全集**——`QUOTA` / `AUTH` / `RATE_LIMIT` / `TIMEOUT` / `TRANSPORT` / `SERVER` / `EMPTY_RESPONSE` / `INVALID_CREDENTIAL`——也可添加非标准/自定义错误码(与 provider 的 `failure.code` **精确匹配**)。你保存什么就执行什么:不存在"把选中的三个码偷偷变回六码超集"的魔法。`ABORTED`(用户取消)永不触发轮换。
|
|
20
|
+
- **真实当前使用 key 显示**:页面显示当前真正在用哪把 key(从最后一次写入 provider env 的值推导)——不是截断的哈希,不是猜的名字。
|
|
21
|
+
- **短 ref 命名**:新 key 自动命名为 `key_fallback_<provider>_key1`、`key_fallback_<provider>_key2`、…,UI 显示干净的短名(`key1`、`key2`、…或你的自定义 `label`)。已有旧长 ref **一次性、幂等地自动迁移**(写新 ref → 持久化配置 → best-effort 删旧 ref;任何一步失败即中止、安全可重试)。
|
|
22
|
+
- **明文揭示**:每行有眼睛开关(`👁` / `🙈`),经 `GET /keys/plain` 显示真实值(仅限本池的 key 或本池 env key)。env key 行也会显示明文——不会被只读说明吞掉。
|
|
23
|
+
- **环境密钥可编辑**:池的 env key(`AGNES_API_KEY` 等)在凭证文件可写时可直接在页面里改。若由启动环境提供(只读),UI 会说明并拒绝编辑(HTTP 400)。
|
|
24
|
+
- **逐 key 控制**:更新值、设置"失败后→"下一把(`nextRef`)、锁定到某把 key("设为当前")、删除 key(env key 不可删)。
|
|
25
|
+
- **池级控制**:启用开关、冷却显示、"↺ 重置冷却"、池锁定/自动轮换、逐池状态(`live` / `cooling` / `recovered`)。
|
|
26
|
+
|
|
27
|
+
## 轮换语义
|
|
28
|
+
|
|
29
|
+
- **pick**(`agent/request` 预写):先尊重 `useKeyRef` 锁定(但冷却中的锁定 key 会跳过,避免池在某把坏 key 上死锁),否则按游标在 live key 上轮询。选中的 key 同时写 `credentials.set` 和 `process.env`,保证 provider 真的用这把 key 鉴权。
|
|
30
|
+
- **fail**(`agent/request-error`,注册 `prepend` 保证先于 `dsh-llm-retry` 看到错误):仅当错误匹配本池 `rotateOn` 时才处理——按 `failure.code`(精确)、按 HTTP 状态映射(`429→RATE_LIMIT`、`401/403→AUTH`、`402→QUOTA`、`5xx→SERVER`)、或按消息关键字。把失败 key 标记为固定 `cooldownMs`(默认 30 s),然后切到 `nextRef`(若配置)否则下一把 live key。
|
|
31
|
+
- **re-send** 完全交给 DSH 的 `dsh-llm-retry` 与用户自己的 per-provider 重试策略。两者独立互补:重试插件决定"同一把 key 重发几次"(`retryPolicy.retryableCodes` 默认 `EMPTY_RESPONSE/RATE_LIMIT/SERVER/TIMEOUT/TRANSPORT`——注意**不含 AUTH/QUOTA**);本插件决定"换下一把 key"。例如 `AUTH` 失败(重试本来也不会重发,重发也白搭)仍会轮换到下一把——下一次请求就会用新 key 鉴权。
|
|
32
|
+
|
|
33
|
+
## 诊断
|
|
34
|
+
|
|
35
|
+
- client 会把运行时异常 POST 到 `POST /dsh-key-fallback/diag`。
|
|
36
|
+
- `GET /dsh-key-fallback/diag` 返回内存缓冲(最近 200 条)JSON。
|
|
37
|
+
|
|
38
|
+
## 安装
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
# 从 npm(推荐)
|
|
42
|
+
cd ~/.dsh/profiles/web
|
|
43
|
+
pnpm add @omdp/dsh-key-fallback
|
|
44
|
+
|
|
45
|
+
# 或从本地 checkout
|
|
46
|
+
dsh plugin --profile web add link:D:/WorkSpace/omdp/dsh-key-fallback
|
|
47
|
+
|
|
48
|
+
# 重启 DSH,然后打开 设置 → API Key 回退
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
> 注意:该 profile 目前从 npm(`^3.x`)安装到 `~/.dsh/profiles/web/node_modules/@omdp/dsh-key-fallback`;把新版 `lib/index.js` + `lib/client.js` 复制过去并重启 DSH 即可升级。包声明了 `dsh.bundle.patch`,自动激活——无需手动改 `cordis.patch.yml`。
|
|
52
|
+
|
|
53
|
+
## HTTP API(host)
|
|
54
|
+
|
|
55
|
+
| 方法 | 路径 | 用途 |
|
|
56
|
+
| --- | --- | --- |
|
|
57
|
+
| GET | `/dsh-key-fallback/pools` | 列池:live 状态、`activeRef`、env 可写性/来源、逐 key 状态 |
|
|
58
|
+
| POST | `/dsh-key-fallback/pools` | 创建/更新池(`enabled`、`cooldownMs`、`rotateOn`、`useKeyRef`、…) |
|
|
59
|
+
| DELETE | `/dsh-key-fallback/pools?provider=` | 删除池 + 其全部 key |
|
|
60
|
+
| POST | `/dsh-key-fallback/keys` | 加 key(自动短 ref `key_fallback_<p>_keyN`) |
|
|
61
|
+
| PATCH | `/dsh-key-fallback/keys` | 更新值 / label / `nextRef` / `useKeyRef`(env key 的值=改 env) |
|
|
62
|
+
| DELETE | `/dsh-key-fallback/keys?provider=&ref=` | 删 key(env key 拒绝) |
|
|
63
|
+
| GET | `/dsh-key-fallback/keys/plain?provider=&ref=` | 揭示真实值(仅本池 key / env key) |
|
|
64
|
+
| POST | `/dsh-key-fallback/reset` | 重置某池冷却状态 |
|
|
65
|
+
| GET/POST | `/dsh-key-fallback/diag` | 诊断缓冲 |
|
|
66
|
+
|
|
67
|
+
## 已知限制
|
|
68
|
+
|
|
69
|
+
- 冷却为每池固定 `cooldownMs`(无指数退避);冷却到期 key 自动恢复 live。
|
|
70
|
+
- 轮换/冷却状态为内存态 + 持久化池配置(`settings.yaml` 的 `keyFallback.providers`);DSH 重启会重读配置并重算 live 状态。
|
|
71
|
+
- env key 不能通过 UI 删除(它是池的身份标识)。
|
|
72
|
+
|
|
73
|
+
## License
|
|
74
|
+
|
|
75
|
+
MIT
|
package/lib/client.js
CHANGED
|
@@ -34,9 +34,11 @@ window.__ModuleLoader__.load({
|
|
|
34
34
|
var ce = React.createElement
|
|
35
35
|
|
|
36
36
|
// ── 常用代码 / 名称 ──
|
|
37
|
-
|
|
37
|
+
// 预设 chips = DSH LlmError 标准码(换 key 有意义的全集)。这些码决定「失败后是否换 key」,
|
|
38
|
+
// 与 DSH 的重试机制(provider 的 retryPolicy.retryableCodes,决定同一把 key 是否重发)独立互补。
|
|
39
|
+
var ROTATE_CODES = ['QUOTA', 'AUTH', 'RATE_LIMIT', 'TIMEOUT', 'TRANSPORT', 'SERVER', 'EMPTY_RESPONSE', 'INVALID_CREDENTIAL']
|
|
38
40
|
function codeLabel(c) {
|
|
39
|
-
return { QUOTA: '配额用尽', AUTH: '认证失败', RATE_LIMIT: '限流', TIMEOUT: '超时', TRANSPORT: '传输错误', SERVER: '服务端' }[c] || c
|
|
41
|
+
return { QUOTA: '配额用尽', AUTH: '认证失败', RATE_LIMIT: '限流', TIMEOUT: '超时', TRANSPORT: '传输错误', SERVER: '服务端', EMPTY_RESPONSE: '空响应', INVALID_CREDENTIAL: '凭据无效' }[c] || c
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
// ── CSS(一次注入)──
|
|
@@ -94,7 +96,7 @@ window.__ModuleLoader__.load({
|
|
|
94
96
|
'.kf_statusDot.recovered{background:#38bdf8}',
|
|
95
97
|
'.kf_keyName{font-weight:600;font-size:12.5px;color:var(--dsw-alias-label-primary,#e8e8e8);min-width:56px;display:flex;align-items:center;gap:6px}',
|
|
96
98
|
'.kf_keyRef{font-size:10.5px;color:var(--dsw-alias-label-secondary,#9aa);font-family:ui-monospace,Menlo,Consolas,monospace;opacity:.75;max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
|
|
97
|
-
'.kf_keyVal{flex:1;min-width:170px;font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12px;padding:6px 10px;border-radius:8px;border:1px dashed var(--dsw-alias-border-l2,rgba(128,128,128,.3));background:var(--dsw-alias-bg-base,rgba(0,0,0,.22));color:inherit}',
|
|
99
|
+
'.kf_keyVal{flex:1;min-width:170px;max-width:100%;font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12px;padding:6px 10px;border-radius:8px;border:1px dashed var(--dsw-alias-border-l2,rgba(128,128,128,.3));background:var(--dsw-alias-bg-base,rgba(0,0,0,.22));color:inherit;overflow-wrap:anywhere;word-break:break-all;line-height:1.5}',
|
|
98
100
|
'.kf_keyActions{display:flex;gap:6px;align-items:center;flex-wrap:wrap}',
|
|
99
101
|
'.kf_err{color:var(--dsw-alias-state-error-primary,#f87171);font-size:12px;line-height:1.5}',
|
|
100
102
|
'.kf_ok{color:var(--dsw-alias-state-success-primary,#4ade80);font-size:12px;line-height:1.5}',
|
|
@@ -106,7 +108,7 @@ window.__ModuleLoader__.load({
|
|
|
106
108
|
'.kf_iconBtn{cursor:pointer;font:inherit;font-size:12px;width:28px;height:28px;border-radius:8px;border:1px solid var(--dsw-alias-border-l2,rgba(128,128,128,.3));background:transparent;color:var(--dsw-alias-label-secondary,#9aa);display:inline-flex;align-items:center;justify-content:center;transition:all .12s;flex:none;line-height:1}',
|
|
107
109
|
'.kf_iconBtn:hover:not(:disabled){color:var(--dsw-alias-label-primary,#eee);border-color:var(--dsw-alias-brand-primary,#5b8cff);background:var(--dsw-alias-bg-layer-2,rgba(128,128,128,.12))}',
|
|
108
110
|
'.kf_iconBtn:disabled{opacity:.4;cursor:default}',
|
|
109
|
-
'.kf_envNote{font-size:11.5px;color:var(--dsw-alias-label-secondary,#9aa);border:1px dashed var(--dsw-alias-border-l2,rgba(128,128,128,.28));border-radius:8px;padding:6px 10px;flex:1;min-width:150px}',
|
|
111
|
+
'.kf_envNote{font-size:11.5px;color:var(--dsw-alias-label-secondary,#9aa);border:1px dashed var(--dsw-alias-border-l2,rgba(128,128,128,.28));border-radius:8px;padding:6px 10px;flex:1;min-width:150px;overflow-wrap:anywhere}',
|
|
110
112
|
].join('')
|
|
111
113
|
if (typeof document !== 'undefined' && !document.querySelector('style[data-plugin-css="@omdp/dsh-key-fallback"]')) {
|
|
112
114
|
var tag = document.createElement('style')
|
|
@@ -201,7 +203,7 @@ window.__ModuleLoader__.load({
|
|
|
201
203
|
k.active ? ce('span', { className: 'kf_badge ok', style: { padding: '1px 7px', fontSize: 10.5 } }, '当前') : null,
|
|
202
204
|
),
|
|
203
205
|
ce('span', { className: 'kf_keyRef', title: k.ref }, k.ref),
|
|
204
|
-
isEnv
|
|
206
|
+
isEnv && !isRevealed
|
|
205
207
|
? ce('span', { className: 'kf_envNote' },
|
|
206
208
|
props.pool.envSource === 'env' && props.pool.envWritable === false
|
|
207
209
|
? '由启动环境提供(只读)'
|
|
@@ -389,7 +391,12 @@ window.__ModuleLoader__.load({
|
|
|
389
391
|
(pool.rotateOn || []).filter(function (c) { return ROTATE_CODES.indexOf(c) < 0 }).map(function (c) {
|
|
390
392
|
return ce('span', { key: c, className: 'kf_chip on' }, c)
|
|
391
393
|
}),
|
|
392
|
-
ce('span', { style: { fontSize: 11, color: 'var(--dsw-alias-label-secondary,#9aa)' } }, '
|
|
394
|
+
ce('span', { style: { fontSize: 11, color: 'var(--dsw-alias-label-secondary,#9aa)' } }, '勾选=该错误发生后换下一把 key;换 key 与 DSH 的重试再发相互独立'),
|
|
395
|
+
),
|
|
396
|
+
ce('div', { className: 'kf_customRow', style: { fontSize: 11, color: 'var(--dsw-alias-label-tertiary,#778)', lineHeight: 1.6 } },
|
|
397
|
+
'补充:DSH 的重试插件负责「同一把 key 重发几次」,由各模型 provider 的 retryPolicy 决定;',
|
|
398
|
+
ce('br'),
|
|
399
|
+
'这里只决定「失败后是否切换到下一把 key」。预设 chips 已含 LlmError 标准码,下方输入框用于添加非标准/自定义错误码(与 provider 返回的 failure.code 精确匹配)。',
|
|
393
400
|
),
|
|
394
401
|
ce('div', { className: 'kf_section' }, '密钥链'),
|
|
395
402
|
keys.length === 0
|
package/lib/index.js
CHANGED
|
@@ -95,10 +95,12 @@ function sendJson(res, code, data) {
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
// ── 轮转触发语义(v6:rotateOn 真正生效)──
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
|
|
98
|
+
// 注意:rotateOn 决定「失败后是否换下一把 key」,与 DSH 的重试机制(dsh-llm-retry 按
|
|
99
|
+
// provider 的 retryPolicy.retryableCodes 决定是否用同一把 key 重发、重试几次)相互独立、互补:
|
|
100
|
+
// 同一 failure.code,重试插件决定重发,本插件决定换 key。所以这是两套开关,不存在谁覆盖谁。
|
|
101
|
+
// 预设 = LlmError 标准码(换 key 有意义的全集):QUOTA/AUTH/RATE_LIMIT/TIMEOUT/TRANSPORT/SERVER
|
|
102
|
+
// + EMPTY_RESPONSE/INVALID_CREDENTIAL。ABORTED(用户取消)永远不轮换。
|
|
103
|
+
const DEFAULT_ROTATE_ON = ['QUOTA', 'AUTH', 'RATE_LIMIT', 'TIMEOUT', 'TRANSPORT', 'SERVER', 'EMPTY_RESPONSE', 'INVALID_CREDENTIAL']
|
|
102
104
|
function canonicalCode(c) {
|
|
103
105
|
const s = String(c || '').toUpperCase()
|
|
104
106
|
if (s === 'QUOTA_EXCEEDED' || s === 'QUOTA') return 'QUOTA'
|
|
@@ -118,6 +120,7 @@ const CODE_KEYWORDS = {
|
|
|
118
120
|
TRANSPORT: /transport|network|econn|socket|eai|dns|fetch failed|connection|closed|reset|unreachable/i,
|
|
119
121
|
SERVER: /5\d\d|server error|internal|unavailable|502|503|504|bad gateway|overload/i,
|
|
120
122
|
EMPTY_RESPONSE: /empty response|no content|empty reply/i,
|
|
123
|
+
INVALID_CREDENTIAL: /invalid credential|malformed|bad credential|invalid api key/i,
|
|
121
124
|
}
|
|
122
125
|
function shouldRotate(pool, code, rawMsg, status) {
|
|
123
126
|
const triggers = effectiveRotateOn(pool.cfg.rotateOn)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omdp/dsh-key-fallback",
|
|
3
|
-
"description": "API Key 回退轮换插件:多 key 池 +
|
|
4
|
-
"version": "3.1.
|
|
3
|
+
"description": "API Key 回退轮换插件:多 key 池 + 自动轮换(触发码可配置,含 LlmError 标准码全集)+ 环境密钥编辑 + 明文揭示 + 短 ref 迁移。Settings → API Key 回退 UI. v6.1.",
|
|
4
|
+
"version": "3.1.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
42
|
-
"@deepseek-ai/dsh-credentials": ">=0.1.0-rc.
|
|
43
|
-
"@deepseek-ai/dsh-llm": "
|
|
44
|
-
"@deepseek-ai/dsh-settings": "
|
|
42
|
+
"@deepseek-ai/dsh-credentials": ">=0.1.0-rc.6 <0.2.0 || >=0.1.1-0 <0.2.0-0 || >=0.1.2-0 <0.2.0-0",
|
|
43
|
+
"@deepseek-ai/dsh-llm": ">=0.1.0-rc.6 <0.2.0 || >=0.1.1-0 <0.2.0-0 || >=0.1.2-0 <0.2.0-0",
|
|
44
|
+
"@deepseek-ai/dsh-settings": ">=0.1.0-rc.6 <0.2.0 || >=0.1.1-0 <0.2.0-0 || >=0.1.2-0 <0.2.0-0",
|
|
45
45
|
"@deepseek-ai/schemastery": "^3.18.1"
|
|
46
46
|
}
|
|
47
47
|
}
|