@leaf233/dsh-llm-rate-limiter 0.2.0 → 0.3.0
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/CHANGELOG.md +211 -1
- package/README.md +80 -17
- package/README.zh.md +284 -0
- package/lib/client.js +182 -140
- package/lib/index.js +160 -54
- package/lib/status-rpc.js +227 -9
- package/lib/types/config.js +48 -16
- package/lib/types/index.js +1 -1
- package/package.json +13 -11
- package/COMPATIBILITY.md +0 -150
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,216 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/).
|
|
6
6
|
|
|
7
|
+
## [0.3.0] - 2026-09-28
|
|
8
|
+
|
|
9
|
+
**Requires DSH 0.1.7-rc.1 or newer.** For DSH 0.1.5-rc.3, stay on `0.2.x`.
|
|
10
|
+
|
|
11
|
+
### Changed (breaking)
|
|
12
|
+
|
|
13
|
+
DSH 0.1.7 replaced both client services this plugin used for its settings UI, and
|
|
14
|
+
removed the host namespace API. All four breakpoints are source-verified against
|
|
15
|
+
the installed 0.1.7-rc.1 tree:
|
|
16
|
+
|
|
17
|
+
- **Client: `settingsScope` → `configForms`.** `SettingsScopeController` is gone;
|
|
18
|
+
the page now hands each `plugins.item` entry a `ConfigForm` as
|
|
19
|
+
`props.form` (`dsh-client-ui-plugin-manager/lib/client.js:2627-2634`, `:2832`).
|
|
20
|
+
The snapshot shape is unchanged (`{status, value, base, user, revision, writable,
|
|
21
|
+
mode}`), so the card reads `props.form.state.value` exactly as it read
|
|
22
|
+
`settingsScope.getSnapshot().value`.
|
|
23
|
+
- **Client slot: `settings.plugin.item` → `plugins.item`**, declared by
|
|
24
|
+
`dsh-client-ui-plugin-manager` (`slot-contract.d.ts:90`). The page draws the
|
|
25
|
+
card chrome itself and asks the entry for `view: "summary"` and
|
|
26
|
+
`view: "page"`; the plugin renders the one-liner and the body respectively.
|
|
27
|
+
`id` replaces `key` (a list slot requires `id`, `dsh-client-ui-slots/lib/index.js:182`),
|
|
28
|
+
and `order: 900` parks the card after the official pages (10/20/30/40).
|
|
29
|
+
- **Client inject: `["slots", "locale", "configForms"]`**, and the plugin now
|
|
30
|
+
registers its own locale dictionary — an entry that `declares locale namespace`
|
|
31
|
+
with no dictionary installed throws `SlotAssemblyError`
|
|
32
|
+
(`dsh-client-ui-renderer/lib/client.js:723`). Previously the plugin declared
|
|
33
|
+
`locale: name` without ever registering it.
|
|
34
|
+
- **Host: `ctx.settings.register()` / `scope.watch()` removed.** Configuration is
|
|
35
|
+
now the profile entry's own `Config`, which the plugin exports
|
|
36
|
+
(`export { apply, name, SETTINGS_NS, Config }`) and reads through volatile
|
|
37
|
+
references. Every editable leaf is marked `.volatile()`.
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- **The plugin no longer hangs in `pending` on DSH 0.1.7.** It previously waited
|
|
42
|
+
for a `settingsScope` service the framework no longer provides.
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
- **`models` is volatile as ONE BLOCK** — `z.dict(ModelOverride).volatile()`,
|
|
47
|
+
never `z.dict(ModelOverride.volatile())`. The latter is rejected by
|
|
48
|
+
schemastery ("volatile fields require a fixed object path without an enclosing
|
|
49
|
+
volatile field", `schemastery/lib/index.mjs:246`). Because
|
|
50
|
+
`isVolatilePath` returns true on reaching ANY volatile node without inspecting
|
|
51
|
+
its descendants (`dsh-settings/lib/index.js:153-158`), nested writes such as
|
|
52
|
+
`["models", "provider/model", "maxRpm"]` still work — the plugin's per-model
|
|
53
|
+
editor is unaffected.
|
|
54
|
+
- Configuration is now read **at each use** (`config.enabled.get()`) rather than
|
|
55
|
+
cached. A volatile-only settings write updates the references in place via the
|
|
56
|
+
loader's `_commitVolatile()` (`cordis-plugin-loader/lib/index.js:393`)
|
|
57
|
+
**without re-running `apply()`**, so a cached snapshot would go stale.
|
|
58
|
+
- The plugin opts out of schema-generated pages with
|
|
59
|
+
`settings.configure({ auto: false }, ctx.fiber)` from an optional
|
|
60
|
+
`ctx.inject(["settings"], ...)` child, following the shipped idiom
|
|
61
|
+
(`dsh-settings/README.md:39`; `dsh-client-locale/lib/index.js:24`). It is not
|
|
62
|
+
currently required — nothing shipped consumes `autoGenerate` — but it is cheap
|
|
63
|
+
and matches the official pages.
|
|
64
|
+
- `dependencies`: `@deepseek-ai/schemastery` `^3.18.0` → `^3.18.4`.
|
|
65
|
+
`.volatile()` does not exist in 3.18.2 (0 occurrences; 28 in 3.18.4).
|
|
66
|
+
- `peerDependencies`: added `"@deepseek-ai/dsh-settings": ">=0.1.7-rc.1"` and
|
|
67
|
+
raised schemastery to `>=3.18.4`.
|
|
68
|
+
- The DSH gate is a **dsh-* subpackage** peer, not the `@deepseek-ai/dsh`
|
|
69
|
+
umbrella. `evaluatePluginCompatibility()` treats both identically — verified by
|
|
70
|
+
importing the real exported function and comparing its verdict for
|
|
71
|
+
`@deepseek-ai/dsh` against `@deepseek-ai/dsh-settings` across
|
|
72
|
+
`0.1.2-rc.1 / 0.1.5-rc.3 / 0.1.6-alpha.2 / 0.1.7-rc.1 / 0.1.7-rc.2` (identical
|
|
73
|
+
every time: blocked on 0.1.5, allowed from 0.1.7-rc.1). The umbrella form costs
|
|
74
|
+
far more: a `pnpm install --lockfile-only` comparison measured **777,850 bytes**
|
|
75
|
+
for `@deepseek-ai/dsh` versus **27,725 bytes** with the subpackage — the umbrella
|
|
76
|
+
resolves the entire DSH dependency graph (~600 packages) into the lockfile.
|
|
77
|
+
Marking it `optional` in `peerDependenciesMeta` does NOT help (measured:
|
|
78
|
+
777,588 bytes, effectively identical), because the gate reads
|
|
79
|
+
`peerDependencies` and never consults that field
|
|
80
|
+
(`dsh-app-boot/lib/index.js:289-301`) while pnpm's `autoInstallPeers` still
|
|
81
|
+
materializes it. This also matches the shipped convention: `dsh-free-search`,
|
|
82
|
+
`dsh-context`, and `@leaf233/dsh-model-relay` all gate on dsh-* subpackages.
|
|
83
|
+
Lockfile growth for this release is **+464/-8 lines (~26 KB)**, down from 8,015
|
|
84
|
+
lines with the umbrella peer.
|
|
85
|
+
- `devDependencies` holds only `@deepseek-ai/dsh-settings` — the one package the
|
|
86
|
+
schema test needs in order to exercise the real helpers. The previously listed
|
|
87
|
+
`dsh-client-store` / `dsh-client-ui-primitives` / `dsh-client-ui-slots`
|
|
88
|
+
entries were imported by nothing and were dropped.
|
|
89
|
+
- `dsh.client.inject` now also declares `dsh-client-locale` and
|
|
90
|
+
`dsh-client-ui-plugin-manager`.
|
|
91
|
+
|
|
92
|
+
### Removed
|
|
93
|
+
|
|
94
|
+
- **`dsh.compatibility.dshReleases`** — a dead field. It has zero occurrences in
|
|
95
|
+
both the 0.1.5 and 0.1.7 trees; the real compatibility gate is
|
|
96
|
+
`evaluatePluginCompatibility()` (`dsh-app-boot/lib/index.js:286-313`), which
|
|
97
|
+
reads only `peerDependencies` matching `@deepseek-ai/dsh` / `dsh-*`.
|
|
98
|
+
- `lib/client.js`'s `RateLimiterConfig` export is now `Config` (also re-exported
|
|
99
|
+
from `lib/types/index.js`).
|
|
100
|
+
|
|
101
|
+
### Added
|
|
102
|
+
|
|
103
|
+
- **`test-config-schema.mjs` (36 assertions)** — pins the contract the settings
|
|
104
|
+
page depends on: `Config` yields a non-empty `volatileForm`; `models` is
|
|
105
|
+
whole-block volatile and nested paths stay writable; `dict(inner.volatile())`
|
|
106
|
+
is refused; and a Config with no volatile field is dropped — the regression
|
|
107
|
+
guard against deleting a `.volatile()` mark.
|
|
108
|
+
- `test-host-integration.mjs` §13 proves volatile references are read in place:
|
|
109
|
+
it mutates the reference the plugin already holds and asserts the change
|
|
110
|
+
reaches the snapshot with no remount.
|
|
111
|
+
- `test-client-bundle.mjs` now covers the page protocol: both views, six write
|
|
112
|
+
paths through `props.form.mutate` (including the nested per-model case),
|
|
113
|
+
read-only/unavailable/missing-form degradation, and the `whileServed` gate.
|
|
114
|
+
- **`README.zh.md`** — full Simplified Chinese README, following the DSH house
|
|
115
|
+
convention of a `README.md` + `README.zh.md` pair (every official package
|
|
116
|
+
ships both). Both files carry a language switcher line under the badges, and
|
|
117
|
+
the Chinese file is listed in `package.json`'s `files` so it ships to npm.
|
|
118
|
+
Code blocks, tables, and section structure are 1:1 with the English original
|
|
119
|
+
(verified: 16 fence lines, 48 table rows, 1 YAML block each).
|
|
120
|
+
- The "Via file" section in both languages now describes 0.1.7 accurately:
|
|
121
|
+
configuration persists as the entry's own `config` in the profile's Cordis
|
|
122
|
+
patch, and a legacy `settings.yaml` is imported by DSH once at first start
|
|
123
|
+
(then renamed) rather than being the live store.
|
|
124
|
+
|
|
125
|
+
### Fixed (tooling and docs)
|
|
126
|
+
|
|
127
|
+
- **`test-config-schema.mjs` now exercises the REAL framework helpers.** Its
|
|
128
|
+
resolution previously asked for `@deepseek-ai/dsh-settings/lib/types/schema.js`
|
|
129
|
+
directly, which can never resolve — that path is not in the package's
|
|
130
|
+
`exports` map (only `.`, `./types`, `./src/*`), so the suite silently fell back to a
|
|
131
|
+
local re-implementation on every run. It now resolves the package ROOT and
|
|
132
|
+
appends the file path, converts it with `pathToFileURL()` (a native Windows
|
|
133
|
+
path makes `import()` fail with `ERR_UNSUPPORTED_ESM_URL_SCHEME`), and
|
|
134
|
+
`@deepseek-ai/dsh-settings` is a devDependency so the real path is reachable.
|
|
135
|
+
Confirmed by the run header switching from "\u25cb not resolvable — using
|
|
136
|
+
equivalent local helpers" to "\u25cf using the real @deepseek-ai/dsh-settings
|
|
137
|
+
schema helpers" (36/36 pass). The fallback branch now prints a
|
|
138
|
+
`DSH_REAL_PACKAGES` hint, so a silent fallback is never mistaken for
|
|
139
|
+
framework-level proof.
|
|
140
|
+
- **CI now runs `test-config-schema.mjs`** and `node --check`s **all seven**
|
|
141
|
+
shipped lib modules (previously 5 of 6 suites and 3 files). The contract that
|
|
142
|
+
decides whether the settings card exists at all — an entry with no volatile
|
|
143
|
+
field is dropped (`dsh-settings/lib/index.js:419`) — had no regression
|
|
144
|
+
protection in CI, while being the one failure mode that leaves the rate
|
|
145
|
+
limiter working and the card silently missing.
|
|
146
|
+
- **CI Node matrix is now `[20, 22, 24]`** (was `[18, 20, 22]`): 18 is EOL and 24
|
|
147
|
+
is the version the plugin is developed and run on.
|
|
148
|
+
- **`tools/verify-install.mjs` skips its install half instead of failing** when
|
|
149
|
+
the plugin is not present in the profile, and falls back to importing the
|
|
150
|
+
source tree. Exit codes are now 0 = fully verified, 1 = a check failed,
|
|
151
|
+
2 = source-tree checks passed but the install half was skipped (the same
|
|
152
|
+
contract as `tools/verify-live-route.mjs`). A local run without an installed
|
|
153
|
+
profile goes from 2 misleading failures to **31 passed / 0 failed / exit 2**.
|
|
154
|
+
- Removed the dangling `COMPATIBILITY.md` references left when that file was
|
|
155
|
+
deleted: the `package.json` `files` entry, the README badge link in both
|
|
156
|
+
languages, and the closing "see COMPATIBILITY.md" links (now pointing at
|
|
157
|
+
`CHANGELOG.md` and the inline source comments). The historical `CHANGELOG.md`
|
|
158
|
+
mention under `[0.1.0]` is intentionally kept, and the `lib/index.js` header
|
|
159
|
+
comment no longer points at the deleted file.
|
|
160
|
+
|
|
161
|
+
### Notes
|
|
162
|
+
|
|
163
|
+
- `test-3rpm.mjs` is deliberately **left out of the `test` script**. It prints
|
|
164
|
+
conclusions rather than making assertions, and it re-implements
|
|
165
|
+
`resolveModelConfig` instead of importing it (`test-3rpm.mjs:15-28`), so it
|
|
166
|
+
cannot detect a drift between itself and `lib/index.js`. It still runs
|
|
167
|
+
cleanly against 0.3.0. Converting it into a real assertion suite would mean
|
|
168
|
+
exporting the resolver or driving the interceptor — out of scope for this
|
|
169
|
+
migration.
|
|
170
|
+
|
|
171
|
+
### Verified
|
|
172
|
+
|
|
173
|
+
- All six suites green on DSH `0.1.7-rc.1` packages with Cordis 4.0.4,
|
|
174
|
+
schemastery 3.18.4, Node 24.21.0: **19 + 61 + 106 + 80 + 73 + 36 = 375
|
|
175
|
+
assertions, 0 failures**.
|
|
176
|
+
- `node tools/verify-install.mjs`: 37 passed, 0 failed.
|
|
177
|
+
- Against the **real** `@deepseek-ai/dsh-settings` service: `describe()` returns
|
|
178
|
+
the entry with all six volatile fields, `autoGenerate: false`, and
|
|
179
|
+
`models` unwrapped to a plain object; a nested
|
|
180
|
+
`["models", "c/d", "maxRpm"]` mutation passes `write()`'s volatile gate while
|
|
181
|
+
a non-volatile path is still refused (12/12).
|
|
182
|
+
- The status channel needs no change: the `dsh-client-connection` `inject`
|
|
183
|
+
mismatch that forces the fallback is **still unfixed in 0.1.7-rc.1**
|
|
184
|
+
(`dsh-client-connection/lib/index.js:798` keeps `inject = ["credentials"]`),
|
|
185
|
+
so the v0.2.1 dual-path carrier continues to work as designed.
|
|
186
|
+
- Producer: **deepseek-v4.1-flash**.
|
|
187
|
+
|
|
188
|
+
### Migration
|
|
189
|
+
|
|
190
|
+
- On DSH 0.1.7+: install `0.3.0`. Your existing `llm-rate-limiter` configuration
|
|
191
|
+
in the profile's settings/cordis patch is read as the entry's own `config`;
|
|
192
|
+
no manual migration was performed by the plugin.
|
|
193
|
+
- On DSH 0.1.5-rc.3: keep `0.2.x`.
|
|
194
|
+
|
|
195
|
+
## [0.2.1] - 2026-09-25
|
|
196
|
+
|
|
197
|
+
### Fixed
|
|
198
|
+
- **Live status panel stopped working on DSH 0.1.5-rc.3** — the channel silently failed to mount and the panel sat on "○ 重试中" forever. Root cause is upstream: DSH 0.1.5-rc.3 changed `@deepseek-ai/dsh-client-connection`'s own `inject` from `["webServer", "credentials"]` to `["credentials"]`, while `HostConnectionService.register()` still dereferences `owner.webServer`. Cordis rebinds a cross-fiber service's `ctx` to the *reader's* fiber, so `connection.rpc.handle()` threw `cannot get property "webServer" without inject` — which the plugin's own try/catch swallowed.
|
|
199
|
+
- The plugin now falls back to a **self-registered `kind: "prefix"` route** on its own fiber (which can see `webServer`) and reuses `connection.requestRejection()` for the 403/401 fence. Both carriers speak the identical wire protocol, so **`lib/client.js` is unchanged**.
|
|
200
|
+
|
|
201
|
+
### Added
|
|
202
|
+
- `createChannelRoute({ channel, handler, reject })` and `endpointFromPath()` in `lib/status-rpc.js` — a Connection-RPC-compatible webserver prefix route, mirroring the framework's `rpcFetchHandler` request-shape decisions (404 / 415 / 413 / 400 / `gateway/bad-request` / 500 / 200)
|
|
203
|
+
- `test-status-route.mjs` (73 assertions) in four layers: route grammar, request-shape decisions, a simulated 0.1.5 regression (proves both the fallback *and* the preference for `rpc.handle` when it works), and the real 0.1.5 package (auto-skips when DSH is absent)
|
|
204
|
+
- `tools/verify-live-route.mjs` (19 assertions) — boots the real `dsh-host-webserver` on a real socket plus the real `dsh-client-connection`, then issues real HTTP requests; exits 2 (skipped) without a DSH install
|
|
205
|
+
- `pnpm run verify:live` script
|
|
206
|
+
|
|
207
|
+
### Changed
|
|
208
|
+
- `dsh.compatibility.dshReleases` now declares `0.1.5-rc.3` as compatible alongside `0.1.2-rc.1`
|
|
209
|
+
- The status channel's mount log now names the active carrier (`connection.rpc` vs `self-registered route`), and a failed path-1 registration is reported as a warning instead of being swallowed
|
|
210
|
+
- If `connection.requestRejection()` is unavailable or throws, the plugin **refuses to mount** rather than publishing an unauthenticated route
|
|
211
|
+
|
|
212
|
+
### Notes
|
|
213
|
+
- Path 1 (`connection.rpc.handle`) is still preferred: it keeps route ownership, request validation, and withdrawal with the framework. The fallback exists only for hosts where path 1 is broken.
|
|
214
|
+
- When DSH fixes the upstream `inject` mismatch, path 1 succeeds again and the fallback goes unused — no plugin change required.
|
|
215
|
+
- Verified on: DSH `0.1.5-rc.3` (client-connection 0.1.5-rc.3, host-webserver 0.1.5-rc.3), Cordis 4.0.2, Node 24.21.0. Test totals: 19 + 61 + 70 + 77 + 73 = **300 assertions**, plus 31 in `tools/verify-install.mjs` and 19 in `tools/verify-live-route.mjs`. Producer: **deepseek-v4.1-flash**.
|
|
216
|
+
|
|
7
217
|
## [0.2.0] - 2026-09-13
|
|
8
218
|
|
|
9
219
|
### Added
|
|
@@ -49,4 +259,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
|
|
|
49
259
|
- `maxQueueWaitMs` timeout for queue mode
|
|
50
260
|
- 19 unit tests covering concurrency, burst, queue, abort, and hot-reload
|
|
51
261
|
- E2E test (`test-3rpm.mjs`) verifying 3 rpm limit
|
|
52
|
-
- DSH version compatibility analysis (COMPATIBILITY.md)
|
|
262
|
+
- DSH version compatibility analysis (COMPATIBILITY.md)
|
package/README.md
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@leaf233/dsh-llm-rate-limiter)
|
|
4
4
|
[](LICENSE)
|
|
5
|
-
|
|
6
|
-
[
|
|
6
|
+
[](package.json)
|
|
7
|
+
[](test-config-schema.mjs)
|
|
8
|
+
|
|
9
|
+
English | [中文](README.zh.md)
|
|
8
10
|
|
|
9
11
|
Per-model LLM call rate limiter for [DeepSeek Harness](https://github.com/deepseek-ai/dsh) with queue/reject support and interactive GUI configuration.
|
|
10
12
|
|
|
@@ -39,7 +41,8 @@ Expanding the card shows a live panel at the top of its body:
|
|
|
39
41
|
|
|
40
42
|
| Aspect | Behaviour |
|
|
41
43
|
|--------|-----------|
|
|
42
|
-
| Data channel |
|
|
44
|
+
| Data channel | Channel `/llm-rate-limiter` — authenticated (401/403 fence), POST+JSON, auto-cleaned with the plugin fiber |
|
|
45
|
+
| Carrier | Prefers the framework's `connection.rpc.handle()`; falls back to a self-registered prefix route that reuses `connection.requestRejection()` when the framework path is broken (see below) |
|
|
43
46
|
| Cadence | 1 s polling while the card is expanded; backs off 2 s → 4 s → 8 s after failures |
|
|
44
47
|
| Collapsed card | The panel unmounts, so **no polling runs at all** |
|
|
45
48
|
| Endpoints | `snapshot` (live counters) and `reset` (zero the statistics) |
|
|
@@ -47,7 +50,29 @@ Expanding the card shows a live panel at the top of its body:
|
|
|
47
50
|
| Counters | `requests` / `granted` / `rejected` / `timeouts` / `aborted` / `totalWaitMs`, plus the last 8 events (ring buffer of 64) |
|
|
48
51
|
| Progress bars | Token bucket shows `tokens/burstSize`; sliding window shows `countInWindow/maxRpm`; both turn amber as the limit approaches |
|
|
49
52
|
|
|
50
|
-
|
|
53
|
+
### Carrier fallback (DSH 0.1.5-rc.3 onward)
|
|
54
|
+
|
|
55
|
+
DSH 0.1.5-rc.3 changed `@deepseek-ai/dsh-client-connection`'s own `inject` from
|
|
56
|
+
`["webServer", "credentials"]` to `["credentials"]`, but its
|
|
57
|
+
`HostConnectionService.register()` still dereferences `owner.webServer`.
|
|
58
|
+
Cordis rebinds a cross-fiber service's `ctx` to the *reader's* fiber, so
|
|
59
|
+
`connection.rpc.handle()` throws: **still present in 0.1.7-rc.1** — see below.
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
cannot get property "webServer" without inject
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The plugin now detects that and mounts the same channel itself:
|
|
66
|
+
|
|
67
|
+
| Path | When | How |
|
|
68
|
+
|------|------|-----|
|
|
69
|
+
| 1 (preferred) | `connection.rpc.handle()` works | The framework owns the route, request validation, and fiber-scoped withdrawal |
|
|
70
|
+
| 2 (fallback) | Path 1 throws | The plugin registers a `kind: "prefix"` route on its own fiber (which *can* see `webServer`) and reuses `connection.requestRejection()` for the 403/401 fence |
|
|
71
|
+
|
|
72
|
+
Both carriers speak the identical wire protocol, so **the browser half is
|
|
73
|
+
unchanged** — the panel cannot tell which one is live. If
|
|
74
|
+
`connection.requestRejection()` is unavailable, the plugin refuses to mount
|
|
75
|
+
rather than publishing an unauthenticated route.
|
|
51
76
|
|
|
52
77
|
|
|
53
78
|
---
|
|
@@ -72,6 +97,9 @@ dsh plugin add ./path/to/dsh-llm-rate-limiter # default profile
|
|
|
72
97
|
|
|
73
98
|
> The plugin must be added as a dependency in the profile's `package.json`.
|
|
74
99
|
> The bundle entry (`cordis.patch.yml`) is auto-detected by `reconcilePlugins`.
|
|
100
|
+
>
|
|
101
|
+
> **0.3.0 requires DSH 0.1.7-rc.1 or newer.** Pin `@leaf233/dsh-llm-rate-limiter@0.2.x`
|
|
102
|
+
> for DSH 0.1.5. See the [compatibility table](#compatibility).
|
|
75
103
|
|
|
76
104
|
### Option 3: from GitHub
|
|
77
105
|
|
|
@@ -96,13 +124,20 @@ dsh plugin add <your-profile> github:Leafyezi233/dsh-llm-rate-limiter
|
|
|
96
124
|
### Via GUI
|
|
97
125
|
|
|
98
126
|
1. Open DSH Web UI (`dsh web`)
|
|
99
|
-
2. Go to **Settings → Plugins**
|
|
100
|
-
3. Find
|
|
101
|
-
4. Configure defaults, per-model overrides, and throttle behavior
|
|
127
|
+
2. Go to **Settings → Plugins** and open the **Official** group
|
|
128
|
+
3. Find the **LLM 调用限速** card (`data-plugin-item="llm-rate-limiter"`) and open it
|
|
129
|
+
4. Configure defaults, per-model overrides, and throttle behavior — changes save
|
|
130
|
+
through the page's own form, so the live status panel is right there
|
|
131
|
+
|
|
132
|
+
> The card lives in the **Official** group because DSH lists every `plugins.item`
|
|
133
|
+
> registrant there; the plugin's order is `900`, so it follows the five official
|
|
134
|
+
> settings pages.
|
|
102
135
|
|
|
103
136
|
### Via file
|
|
104
137
|
|
|
105
|
-
|
|
138
|
+
Changes persist into the profile's Cordis patch, in the entry's own `config`
|
|
139
|
+
(0.1.7 reads configuration from the entry's exported `Config`; a legacy
|
|
140
|
+
`settings.yaml` is imported by DSH once at first start and then renamed). The shape is:
|
|
106
141
|
|
|
107
142
|
```yaml
|
|
108
143
|
llm-rate-limiter:
|
|
@@ -189,8 +224,19 @@ cd dsh-llm-rate-limiter
|
|
|
189
224
|
# Install deps
|
|
190
225
|
pnpm install
|
|
191
226
|
|
|
192
|
-
# Run
|
|
193
|
-
|
|
227
|
+
# Run the full suite (375 assertions across 6 files)
|
|
228
|
+
pnpm test
|
|
229
|
+
|
|
230
|
+
# Individual suites
|
|
231
|
+
node test-strategies.mjs # 19 — rate-limit algorithms
|
|
232
|
+
node test-status-rpc.mjs # 61 — channel handler + counters
|
|
233
|
+
node test-client-bundle.mjs # 106 — real client bundle on a miniature React runtime
|
|
234
|
+
node test-host-integration.mjs # 80 — real apply(ctx, config) wiring + volatile refs
|
|
235
|
+
node test-status-route.mjs # 73 — self-registered route + the 0.1.5 regression
|
|
236
|
+
node test-config-schema.mjs # 36 — Config volatile contract the settings page needs
|
|
237
|
+
|
|
238
|
+
# Live HTTP proof against a real DSH install (real socket, real webserver)
|
|
239
|
+
pnpm run verify:live # 19 — exits 2 (skipped) when DSH is absent
|
|
194
240
|
|
|
195
241
|
# Run E2E rate-limit test
|
|
196
242
|
node test-3rpm.mjs
|
|
@@ -205,13 +251,30 @@ The plugin uses a live symlink when installed via `link:` — edits to `lib/` ta
|
|
|
205
251
|
|
|
206
252
|
## Compatibility
|
|
207
253
|
|
|
208
|
-
| DSH Version | Status | Notes |
|
|
209
|
-
|
|
210
|
-
| 0.1.x
|
|
211
|
-
| 0.2.x |
|
|
212
|
-
|
|
|
254
|
+
| DSH Version | Plugin version | Status | Notes |
|
|
255
|
+
|-------------|----------------|--------|-------|
|
|
256
|
+
| 0.1.2-rc.1 | 0.2.x | ✅ Tested | Original target; `connection.rpc` carrier |
|
|
257
|
+
| 0.1.5-rc.3 | 0.2.x | ✅ Tested | Requires the carrier fallback; verified over real HTTP |
|
|
258
|
+
| **0.1.7-rc.1+** | **0.3.x** | ✅ **Tested** | **`settingsScope` → `configForms`; `settings.plugin.item` → `plugins.item`; Config-based settings** |
|
|
259
|
+
| 0.2.x (DSH) | — | ⚠️ Untested | May need API adjustments |
|
|
260
|
+
| Cordis 5+ | — | ⚠️ Untested | Major version change likely requires rewrite |
|
|
261
|
+
|
|
262
|
+
### Breaking change in 0.3.0
|
|
263
|
+
|
|
264
|
+
DSH 0.1.7 removed the two client services this plugin used for its settings UI:
|
|
265
|
+
|
|
266
|
+
| 0.1.5 | 0.1.7 |
|
|
267
|
+
|-------|-------|
|
|
268
|
+
| `settingsScope` service | `configForms` service |
|
|
269
|
+
| `settings.plugin.item` slot | `plugins.item` slot |
|
|
270
|
+
| host `ctx.settings.register(ns, schema, { base })` | the entry's own exported `Config` schema |
|
|
271
|
+
| host `scope.get()` / `scope.watch()` | `.volatile()` references, read with `.get()` |
|
|
272
|
+
|
|
273
|
+
**0.3.0 only supports DSH 0.1.7-rc.1+. Use `0.2.x` for 0.1.5.**
|
|
213
274
|
|
|
214
|
-
|
|
275
|
+
The four breakpoints this migration rests on are documented inline in
|
|
276
|
+
`lib/index.js` and `lib/types/config.js`, and the reasoning is recorded in
|
|
277
|
+
[CHANGELOG.md](CHANGELOG.md) under `[0.3.0]`.
|
|
215
278
|
|
|
216
279
|
---
|
|
217
280
|
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
# dsh-llm-rate-limiter
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@leaf233/dsh-llm-rate-limiter)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+

|
|
6
|
+
[](package.json)
|
|
7
|
+
[](test-config-schema.mjs)
|
|
8
|
+
|
|
9
|
+
[English](README.md) | 中文
|
|
10
|
+
|
|
11
|
+
[DeepSeek Harness](https://github.com/deepseek-ai/dsh) 的**按模型 LLM 调用限速插件**,支持排队/拒绝两种节流策略,并带交互式图形配置界面。
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 功能
|
|
16
|
+
|
|
17
|
+
- **按模型限速** —— 每个 `provider/model` 独立的并发数、RPM 与突发容量限制
|
|
18
|
+
- **两种算法** —— 令牌桶(允许突发)或滑动窗口(平滑、严格 RPM)
|
|
19
|
+
- **排队模式** —— 被限流的请求排队等待,有槽位空出时放行
|
|
20
|
+
- **拒绝模式** —— 被限流的请求立即失败(可与 `dsh-llm-retry` 配合自动退避重试)
|
|
21
|
+
- **交互式界面** —— DSH 设置 → 插件页内的可展开配置卡片
|
|
22
|
+
- **实时状态面板** —— 实时计数器、按模型的进度条与事件日志(v0.2.0 起)
|
|
23
|
+
- **热更新** —— 配置改动立即生效,无需重启
|
|
24
|
+
- **全量检查** —— 拦截 `llm/stream` waterfall,覆盖每个 agent 轮次里的每一次 LLM 调用
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 状态面板(v0.2.0)
|
|
29
|
+
|
|
30
|
+
打开卡片后,其正文顶部会显示实时面板:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
📊 实时状态 ● 实时 [清零]
|
|
34
|
+
请求 42 · 通过 38 · 拒绝 2 · 超时 1 · 中止 1 · 平均等待 214ms
|
|
35
|
+
deepseek/deepseek-chat [令牌桶] ▓▓▓▓▓▓▓░░░ 7.5/10 并发 2/5 排队 1
|
|
36
|
+
openai/gpt-4o [滑动窗口] ▓▓▓▓▓▓▓▓▓▓ 3/3 rpm 并发 1/5
|
|
37
|
+
12:00:03 timeout openai/gpt-4o 等待 1m
|
|
38
|
+
12:00:01 rejected openai/gpt-4o
|
|
39
|
+
12:00:00 granted deepseek/deepseek-chat 等待 4.2s
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
| 方面 | 行为 |
|
|
43
|
+
|------|------|
|
|
44
|
+
| 数据通道 | 通道 `/llm-rate-limiter` —— 带认证(401/403 栅栏),POST+JSON,随插件 fiber 自动清理 |
|
|
45
|
+
| 载体 | 优先用框架的 `connection.rpc.handle()`;框架路径失效时,退回到插件自注册的前缀路由并复用 `connection.requestRejection()`(见下文) |
|
|
46
|
+
| 轮询节奏 | 卡片展开时 1 秒轮询;连续失败后退避 2 秒 → 4 秒 → 8 秒 |
|
|
47
|
+
| 折叠状态 | 面板卸载,因此**完全不轮询** |
|
|
48
|
+
| 端点 | `snapshot`(实时计数)与 `reset`(清零统计) |
|
|
49
|
+
| 通道不可用时 | 显示「状态通道不可用」,卡片其余部分功能不受影响 |
|
|
50
|
+
| 计数器 | `requests` / `granted` / `rejected` / `timeouts` / `aborted` / `totalWaitMs`,外加最近 8 条事件(环形缓冲为 64) |
|
|
51
|
+
| 进度条 | 令牌桶显示 `tokens/burstSize`;滑动窗口显示 `countInWindow/maxRpm`;接近上限时变琥珀色 |
|
|
52
|
+
|
|
53
|
+
### 载体兜底(DSH 0.1.5-rc.3 起)
|
|
54
|
+
|
|
55
|
+
DSH 0.1.5-rc.3 把 `@deepseek-ai/dsh-client-connection` **这个框架插件自身**的 `inject` 从
|
|
56
|
+
`["webServer", "credentials"]` 改成了 `["credentials"]`,但其
|
|
57
|
+
`HostConnectionService.register()` 仍然解引用 `owner.webServer`。
|
|
58
|
+
Cordis 会把跨 fiber 读取的 service 的 `ctx` 重绑定到**读取者**的 fiber,因此
|
|
59
|
+
`connection.rpc.handle()` 会抛错:**在 0.1.7-rc.1 中依然存在** —— 见下文。
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
cannot get property "webServer" without inject
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
插件会检测到该错误,并自行挂载同一条通道:
|
|
66
|
+
|
|
67
|
+
| 路径 | 触发条件 | 做法 |
|
|
68
|
+
|------|---------|------|
|
|
69
|
+
| 1(首选) | `connection.rpc.handle()` 正常 | 由框架持有路由、请求校验与 fiber 级撤销 |
|
|
70
|
+
| 2(兜底) | 路径 1 抛错 | 插件在**自己的 fiber**(能看到 `webServer`)注册 `kind: "prefix"` 路由,并复用 `connection.requestRejection()` 作为 403/401 栅栏 |
|
|
71
|
+
|
|
72
|
+
两条载体使用逐字相同的 wire 协议,因此**浏览器端无需改动** —— 面板也分辨不出
|
|
73
|
+
当前是哪一条。若 `connection.requestRejection()` 不可用,插件会**拒绝挂载**,
|
|
74
|
+
而不是发布一条未鉴权的路由。
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 安装
|
|
79
|
+
|
|
80
|
+
### 方式 1:npm(推荐)
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
dsh plugin add <your-profile> @leaf233/dsh-llm-rate-limiter
|
|
84
|
+
# 或在该 profile 目录内:
|
|
85
|
+
pnpm add @leaf233/dsh-llm-rate-limiter
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### 方式 2:本地路径(开发)
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
dsh plugin add <your-profile> ./path/to/dsh-llm-rate-limiter
|
|
92
|
+
# 或
|
|
93
|
+
dsh plugin add ./path/to/dsh-llm-rate-limiter # 默认 profile
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
> 插件必须被加入该 profile 的 `package.json` 依赖中。
|
|
97
|
+
> bundle 入口(`cordis.patch.yml`)由 `reconcilePlugins` 自动识别。
|
|
98
|
+
>
|
|
99
|
+
> **0.3.0 要求 DSH 0.1.7-rc.1 或更新版本。** 若使用 DSH 0.1.5,请锁定
|
|
100
|
+
> `@leaf233/dsh-llm-rate-limiter@0.2.x`。见[兼容性表](#兼容性)。
|
|
101
|
+
|
|
102
|
+
### 方式 3:从 GitHub 安装
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
dsh plugin add <your-profile> github:Leafyezi233/dsh-llm-rate-limiter
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
> ⚠️ **重要**:从 Git 安装的插件在首次安装时会被 pnpm 的 `allowBuilds` 限制拦下。
|
|
109
|
+
> 若安装失败,请查看报错信息中 pnpm 给出的确切键名,然后加入该 profile 的
|
|
110
|
+
> `pnpm-workspace.yaml`:
|
|
111
|
+
>
|
|
112
|
+
> ```yaml
|
|
113
|
+
> pnpm:
|
|
114
|
+
> allowBuilds:
|
|
115
|
+
> - '@leaf233/dsh-llm-rate-limiter'
|
|
116
|
+
> ```
|
|
117
|
+
>
|
|
118
|
+
> 然后重新执行安装命令。
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## 配置
|
|
123
|
+
|
|
124
|
+
### 通过图形界面
|
|
125
|
+
|
|
126
|
+
1. 打开 DSH Web UI(`dsh web`)
|
|
127
|
+
2. 进入**设置 → 插件**,展开 **Official** 分组
|
|
128
|
+
3. 找到 **LLM 调用限速** 卡片(`data-plugin-item="llm-rate-limiter"`)并打开
|
|
129
|
+
4. 配置全局默认值、按模型覆盖与节流行为 —— 改动经页面自带的表单保存,
|
|
130
|
+
实时状态面板就在同一页
|
|
131
|
+
|
|
132
|
+
> 卡片出现在 **Official** 分组里,是因为 DSH 把所有 `plugins.item` 的注册者
|
|
133
|
+
> 都列在那里;本插件的 `order` 为 `900`,因此排在五个官方设置页之后。
|
|
134
|
+
|
|
135
|
+
### 通过配置文件
|
|
136
|
+
|
|
137
|
+
改动会被持久化到该 profile 的 Cordis patch 中该条目的 `config` 里
|
|
138
|
+
(0.1.7 起,插件以自己的 `Config` 作为配置来源;旧版 `settings.yaml` 由
|
|
139
|
+
DSH 在首次启动时导入一次并改名)。形状如下:
|
|
140
|
+
|
|
141
|
+
```yaml
|
|
142
|
+
llm-rate-limiter:
|
|
143
|
+
enabled: true
|
|
144
|
+
strategy: token-bucket # "token-bucket" | "sliding-window"
|
|
145
|
+
defaults:
|
|
146
|
+
maxConcurrent: 5
|
|
147
|
+
maxRpm: 60
|
|
148
|
+
burstSize: 10 # 仅令牌桶
|
|
149
|
+
refillRate: 1 # 仅令牌桶(每秒补充的令牌数)
|
|
150
|
+
models:
|
|
151
|
+
"deepseek/deepseek-chat":
|
|
152
|
+
maxConcurrent: 8
|
|
153
|
+
maxRpm: 120
|
|
154
|
+
"openai/gpt-4o":
|
|
155
|
+
maxConcurrent: 2
|
|
156
|
+
maxRpm: 10
|
|
157
|
+
burstSize: 3
|
|
158
|
+
"anthropic/claude-3-5-sonnet":
|
|
159
|
+
enabled: false # 对该模型跳过限速
|
|
160
|
+
onThrottled: queue # "queue" | "reject"
|
|
161
|
+
maxQueueWaitMs: 60000
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## 设置项参考
|
|
167
|
+
|
|
168
|
+
| 字段 | 默认值 | 说明 |
|
|
169
|
+
|------|--------|------|
|
|
170
|
+
| `enabled` | `true` | 全局总开关。关闭时零开销直接放行。 |
|
|
171
|
+
| `strategy` | `"token-bucket"` | `"token-bucket"`(允许突发)或 `"sliding-window"`(平滑、严格 RPM) |
|
|
172
|
+
| `defaults.maxConcurrent` | `5` | 每个模型的最大同时请求数 |
|
|
173
|
+
| `defaults.maxRpm` | `60` | 每个模型每分钟最大请求数 |
|
|
174
|
+
| `defaults.burstSize` | `10` | 令牌桶容量 —— 一次最多可突发多少个请求 |
|
|
175
|
+
| `defaults.refillRate` | `1` | 每秒补充的令牌数(令牌桶)。未设置时由 `maxRpm / 60` 自动推导。 |
|
|
176
|
+
| `models.<key>.maxConcurrent` | — | 该模型的并发数覆盖 |
|
|
177
|
+
| `models.<key>.maxRpm` | — | 该模型的 RPM 覆盖 |
|
|
178
|
+
| `models.<key>.burstSize` | — | 该模型的突发容量覆盖 |
|
|
179
|
+
| `models.<key>.refillRate` | — | 该模型的补充速率覆盖 |
|
|
180
|
+
| `models.<key>.enabled` | — | 设为 `false` 可对该模型跳过限速 |
|
|
181
|
+
| `onThrottled` | `"queue"` | 请求触限时的行为:`"queue"`(等待)或 `"reject"`(立即失败) |
|
|
182
|
+
| `maxQueueWaitMs` | `60000` | 请求在队列中等待多久(毫秒)后放弃并被拒绝 |
|
|
183
|
+
|
|
184
|
+
> **注意**:当模型覆盖了 `maxRpm` 但未显式设置 `refillRate` 时,补充速率会
|
|
185
|
+
> 自动按 `maxRpm / 60`(每秒令牌数)推导。这样才能保证「设 maxRpm=3」真的限制到
|
|
186
|
+
> 每分钟 3 个请求。
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## 算法对比
|
|
191
|
+
|
|
192
|
+
| | 令牌桶 | 滑动窗口 |
|
|
193
|
+
|---|--------|----------|
|
|
194
|
+
| **突发** | 允许(由 `burstSize` 控制) | 不允许 —— 严格平滑 |
|
|
195
|
+
| **恢复** | 令牌按 `refillRate`/秒补充 | 窗口持续滑动 |
|
|
196
|
+
| **最适合** | 容忍请求尖峰 | 有硬性每分钟上限的 API |
|
|
197
|
+
| **界面名称** | 令牌桶 (Token Bucket) | 滑动窗口 (Sliding Window) |
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## 工作原理
|
|
202
|
+
|
|
203
|
+
```
|
|
204
|
+
Agent 轮次
|
|
205
|
+
→ LLM 调用(例如 deepseek/deepseek-chat)
|
|
206
|
+
→ ctx.on("llm/stream") 拦截器
|
|
207
|
+
→ 为该 provider/model 解析对应的限速器
|
|
208
|
+
→ 令牌桶:还有令牌且并发未满?
|
|
209
|
+
→ 是:消耗令牌、占用槽位、转发到 API
|
|
210
|
+
→ 否(拒绝模式):立即返回 RATE_LIMIT 错误
|
|
211
|
+
→ 否(排队模式):进入 waiters[] 等待令牌补充
|
|
212
|
+
→ 请求完成 → 释放槽位 → 唤醒等待中的请求
|
|
213
|
+
→ dsh-llm-retry 捕获 RATE_LIMIT → 指数退避 → 重试
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## 开发
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
# 克隆
|
|
222
|
+
git clone https://github.com/Leafyezi233/dsh-llm-rate-limiter.git
|
|
223
|
+
cd dsh-llm-rate-limiter
|
|
224
|
+
|
|
225
|
+
# 安装依赖
|
|
226
|
+
pnpm install
|
|
227
|
+
|
|
228
|
+
# 跑完整测试套件(6 个文件,375 条断言)
|
|
229
|
+
pnpm test
|
|
230
|
+
|
|
231
|
+
# 单独运行各套件
|
|
232
|
+
node test-strategies.mjs # 19 —— 限速算法
|
|
233
|
+
node test-status-rpc.mjs # 61 —— 通道处理器与计数器
|
|
234
|
+
node test-client-bundle.mjs # 106 —— 真实客户端 bundle 跑在迷你 React 运行时上
|
|
235
|
+
node test-host-integration.mjs # 80 —— 真实 apply(ctx, config) 接线与 volatile 引用
|
|
236
|
+
node test-status-route.mjs # 73 —— 自注册路由与 0.1.5 回归
|
|
237
|
+
node test-config-schema.mjs # 36 —— 设置页所依赖的 Config volatile 契约
|
|
238
|
+
|
|
239
|
+
# 针对真实 DSH 安装的实机 HTTP 验证(真实 socket、真实 webserver)
|
|
240
|
+
pnpm run verify:live # 19 —— 无 DSH 时以退出码 2 跳过
|
|
241
|
+
|
|
242
|
+
# 运行端到端限速测试
|
|
243
|
+
node test-3rpm.mjs
|
|
244
|
+
|
|
245
|
+
# 安装到某个 DSH profile 里测试
|
|
246
|
+
dsh plugin add <your-profile> .
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
通过 `link:` 安装时,插件走实时符号链接 —— 对 `lib/` 的改动在浏览器硬刷新
|
|
250
|
+
(`Ctrl+Shift+R`)后即生效,无需重新安装。
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## 兼容性
|
|
255
|
+
|
|
256
|
+
| DSH 版本 | 插件版本 | 状态 | 说明 |
|
|
257
|
+
|----------|---------|------|------|
|
|
258
|
+
| 0.1.2-rc.1 | 0.2.x | ✅ 已测 | 最初目标;`connection.rpc` 载体 |
|
|
259
|
+
| 0.1.5-rc.3 | 0.2.x | ✅ 已测 | 需要载体兜底;已通过实机 HTTP 验证 |
|
|
260
|
+
| **0.1.7-rc.1+** | **0.3.x** | ✅ **已测** | **`settingsScope` → `configForms`;`settings.plugin.item` → `plugins.item`;基于 Config 的设置** |
|
|
261
|
+
| 0.2.x(DSH) | — | ⚠️ 未测 | 可能需要调整 API |
|
|
262
|
+
| Cordis 5+ | — | ⚠️ 未测 | 大版本升级多半需要重写 |
|
|
263
|
+
|
|
264
|
+
### 0.3.0 的破坏性变更
|
|
265
|
+
|
|
266
|
+
DSH 0.1.7 移除了本插件设置界面所用的两个客户端服务:
|
|
267
|
+
|
|
268
|
+
| 0.1.5 | 0.1.7 |
|
|
269
|
+
|-------|-------|
|
|
270
|
+
| `settingsScope` 服务 | `configForms` 服务 |
|
|
271
|
+
| `settings.plugin.item` 槽位 | `plugins.item` 槽位 |
|
|
272
|
+
| 宿主 `ctx.settings.register(ns, schema, { base })` | 条目自己导出的 `Config` schema |
|
|
273
|
+
| 宿主 `scope.get()` / `scope.watch()` | `.volatile()` 引用,用 `.get()` 读取 |
|
|
274
|
+
|
|
275
|
+
**0.3.0 只支持 DSH 0.1.7-rc.1+。使用 0.1.5 请安装 `0.2.x`。**
|
|
276
|
+
|
|
277
|
+
本次迁移所依赖的四个断裂点,已就地写在 `lib/index.js` 与 `lib/types/config.js`
|
|
278
|
+
的注释里;完整推理记录在 [CHANGELOG.md](CHANGELOG.md) 的 `[0.3.0]` 段落。
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## 许可证
|
|
283
|
+
|
|
284
|
+
[MIT](LICENSE)
|