@naced/llm-multi-rate-limiter 0.0.0-stage → 0.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/CHANGELOG.md ADDED
@@ -0,0 +1,78 @@
1
+ # Changelog
2
+
3
+ ## [0.1.2] - 2026-10-28
4
+
5
+ Concurrency accounting fix, provider-wide tiers, wildcard keys, adaptive 429 backoff, and a tested toolchain.
6
+
7
+ ### Fixed
8
+
9
+ - **Queue drain over-granted concurrency**: a waiter released by `releaseSlot()` counted against neither `#concurrent` nor any reservation, so one release could admit the entire queue at once. Both strategies now reserve a slot when they grant one, and `#drain()` compares `#concurrent + #reserved` against `maxConcurrent`.
10
+ - **Timeout and drain could settle the same waiter twice**: `acquire()` now guards its settlement with a `settled` flag.
11
+ - **Retry storm after a queue timeout**: a queue timeout now fails with `RATE_LIMIT_QUEUE_TIMEOUT` instead of the retryable `RATE_LIMIT`, so `dsh-llm-retry` no longer replays the same saturated model six times. Reject mode uses `RATE_LIMIT_REJECTED`.
12
+ - **Limiter instances leaked**: idle limiters with no override are swept every minute (10-minute idle window) once they have no concurrency, reservations or waiters.
13
+
14
+ ### Added
15
+
16
+ - **Provider tier** (`providers.<provider>`): one cap for every model of a provider, below per-model overrides and above `defaults`.
17
+ - **Wildcard model keys** (`openai/*`, `*/*`): `*` compiles to `[^/]*`, so it stays inside one path segment and cannot silently cover another provider. The most specific pattern wins.
18
+ - **Adaptive 429 backoff** (`cooldown`): when the provider answers 429, that model's limits are scaled by `factor` for `cooldownMs` (or the response's `Retry-After`, if longer) and recover after `recoverAfter` consecutive successes. Counted in the status snapshot as `effectiveMaxRpm` / `cooling`.
19
+ - **Settings page**: model search filter, delete confirmation, and whole-config JSON import/export.
20
+ - **Tests**: `node --test` suites for tier resolution, wildcard matching, cooldown bookkeeping and both strategies — including a regression test that one release admits exactly one waiter.
21
+ - **Type declarations** (`lib/index.d.ts`) and an **ESLint** flat config; `npm run check` runs syntax, lint and tests.
22
+ - CI now runs the full check on both Ubuntu and Windows.
23
+
24
+ ### Changed
25
+
26
+ - `lib/config-resolve.js` holds tier resolution and the cooldown tracker as pure functions, shared by the host entry and the tests.
27
+ - The status snapshot reports `reserved` slots per model and `throttledByProvider` in totals.
28
+
29
+ ## [0.1.1] - 2026-10-28
30
+
31
+ Repository and release plumbing; no behavioural change to the limiter.
32
+
33
+ ### Added
34
+
35
+ - `repository`, `homepage`, `bugs` and `author` metadata, so the npm page links back to GitHub.
36
+ - `publishConfig.access: "public"` — a plain `npm publish` on this scoped package no longer needs `--access public`.
37
+ - GitHub Actions: [`ci.yml`](.github/workflows/ci.yml) runs the syntax check and `npm pack --dry-run` on every push and PR; [`publish.yml`](.github/workflows/publish.yml) publishes on `v*` tags through npm Trusted Publishing (OIDC) with provenance, and creates the GitHub release. No npm token is stored in the repository.
38
+ - `prepublishOnly` runs the same syntax check, so a broken `lib/` can never be published.
39
+
40
+ ### Changed
41
+
42
+ - README (both languages) gained npm/CI badges, install-from-registry instructions, and development/release sections.
43
+
44
+ ## [0.1.0] - 2026-10-26
45
+
46
+ Fork of `@leaf233/dsh-llm-rate-limiter` 0.3.0, retargeted at the DSH 0.2.1 client contract.
47
+
48
+ ### Changed
49
+
50
+ - **Settings UI**: the configuration form is now a `settings.section` entry (Settings → 多模型限速) instead of a `plugins.item` card. The old card never rendered on 0.2.1 shells whose sidebar comes from a third-party bundle, because `.dsh/market/*` page ownership — not this plugin — decides which page hosts `plugins.item`.
51
+ - The page owns its own `ConfigFormController` through `ctx.configForms.get("llm-multi-rate-limiter")` and binds it with `hooks: { snapshot: form.store }`, so it no longer depends on a host page passing `props.form`.
52
+ - Package renamed to `@naced/llm-multi-rate-limiter`; client no longer injects `dsh-client-ui-plugin-manager`.
53
+ - `plugins.row.config` entry point removed.
54
+
55
+ ### Added
56
+
57
+ - Model picker with a text field (`provider/model`) plus per-row RPM / concurrency / burst inputs and enable-remove actions.
58
+ - Effective-limit readout per model (override or inherited default).
59
+ - Bilingual dictionaries (zh/en) registered for the section, so labels follow the UI locale instead of hard-coded Chinese.
60
+ - Sidebar entry: `sidebar.panellist` registration (`order: 40`) plus the matching `main` panel, so the page is reachable from the app sidebar on shells that draw navigation there. Both surfaces share one `inject` factory.
61
+
62
+ ### Fixed
63
+
64
+ - **Fail-open interceptor**: if config parsing or a strategy throws, the call is now forwarded unthrottled with a warning instead of failing. Previously an exception escaped the `llm/stream` generator and every model call failed.
65
+ - Duplicate-limiter warning when `@leaf233/dsh-llm-rate-limiter` is also installed (both hook `llm/stream`, so calls would be limited twice).
66
+
67
+ ### Security
68
+
69
+ - README documents exactly what the `llm/stream` position touches: `options.provider`, `options.model`, `options.signal` only; no prompt, response or credential access; no outbound network calls; no `fs` / `child_process` / `eval`; no install scripts.
70
+ - `engines.dsh` (`>=0.2.1-rc.2`) now states the tested runtime, so DSH's compatibility check has a real range to evaluate instead of relying on an unbounded peer range.
71
+ - `exports` gained `./package.json` (consistent with other DSH plugins; the loader's directory walk no longer has to carry it).
72
+ - Internal provider names removed from the README samples and the model input placeholder.
73
+ - LICENSE carries both copyright lines (upstream MIT + this fork).
74
+
75
+ ### Unchanged
76
+
77
+ - Host half: `llm/stream` waterfall interception, token-bucket and sliding-window strategies, `/llm-multi-rate-limiter` status channel with its self-registered fallback route, queueing with `maxQueueWaitMs`.
78
+ - Settings storage: the profile entry's volatile `Config` references, written through `settings.mutate` into `cordis.patch.yml`.
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dsh-llm-rate-limiter contributors (https://github.com/Leafyezi233/dsh-llm-rate-limiter)
4
+ Copyright (c) 2026 @naced/llm-multi-rate-limiter contributors
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,113 @@
1
- # Temporary Holding Version
1
+ # @naced/llm-multi-rate-limiter
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![npm version](https://img.shields.io/npm/v/@naced/llm-multi-rate-limiter.svg)](https://www.npmjs.com/package/@naced/llm-multi-rate-limiter)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@naced/llm-multi-rate-limiter.svg)](https://www.npmjs.com/package/@naced/llm-multi-rate-limiter)
5
+ [![license](https://img.shields.io/npm/l/@naced/llm-multi-rate-limiter.svg)](LICENSE)
6
+ [![ci](https://github.com/NacedWang/dsh-llm-multi-rate-limiter/actions/workflows/ci.yml/badge.svg)](https://github.com/NacedWang/dsh-llm-multi-rate-limiter/actions/workflows/ci.yml)
7
+
8
+ Per-`provider/model` LLM rate limiting for DeepSeek Harness, with an editable **Settings** page (Settings → Multi-model limits), a sidebar entry and a live status panel.
9
+
10
+ Forked from [`@leaf233/dsh-llm-rate-limiter`](https://github.com/Leafyezi233/dsh-llm-rate-limiter) 0.3.0 (MIT, © the upstream contributors). The configuration surface moved from the "built-in plugin config" card to a `settings.section` page and a sidebar entry; the card entry is gone.
11
+
12
+ ## Features
13
+
14
+ - **Two strategies**: token bucket (bursts allowed) or sliding window (strict N per minute).
15
+ - **Three configuration tiers**: `models` (exact or wildcard key) > `providers` (one cap per provider) > `defaults`.
16
+ - **Wildcard keys**: `openai/*` covers every model of that provider without its own limit. `*` stays inside one path segment and never crosses `/`.
17
+ - **Per-model overrides**, keyed `provider/model`, e.g. `openai/gpt-5.1`.
18
+ - **Adaptive 429 backoff**: when the provider answers 429, that model slows down by `factor` for `cooldownMs` and recovers after `recoverAfter` consecutive successes.
19
+ - **Queue or reject** throttled calls (`onThrottled`, bounded by `maxQueueWaitMs`).
20
+ - **Live panel**: polls the `/llm-multi-rate-limiter` status channel once per second — per-model token/window usage, granted calls, average wait, recent events.
21
+ - **Settings page**: model search, delete confirmation, and whole-config JSON import/export.
22
+
23
+ ## Configuration
24
+
25
+ `~/.dsh/profiles/desktop/cordis.patch.yml`:
26
+
27
+ ```yaml
28
+ - id: llm-multi-rate-limiter
29
+ name: "@naced/llm-multi-rate-limiter"
30
+ config:
31
+ enabled: true
32
+ strategy: sliding-window
33
+ defaults:
34
+ maxConcurrent: 5
35
+ maxRpm: 60
36
+ burstSize: 10
37
+ refillRate: 1
38
+ providers:
39
+ openai:
40
+ maxRpm: 30
41
+ maxConcurrent: 3
42
+ models:
43
+ openai/gpt-5.1:
44
+ maxRpm: 20
45
+ maxConcurrent: 2
46
+ "azure/*":
47
+ maxRpm: 40
48
+ cooldown:
49
+ enabled: true
50
+ cooldownMs: 30000
51
+ factor: 0.5
52
+ recoverAfter: 3
53
+ onThrottled: queue
54
+ maxQueueWaitMs: 240000
55
+ ```
56
+
57
+ - The row `id` is also the settings namespace (`dsh-settings` keys forms by `entry.options.id`), so keep it in step with the row.
58
+ - Precedence: exact `models` key > most specific wildcard in `models` > `providers[<provider>]` > `defaults`.
59
+ - `models[<key>]` only needs the fields you override; the rest is inherited tier by tier.
60
+ - Under the token bucket, an omitted `refillRate` (tokens/second) is derived from `maxRpm / 60`.
61
+ - A wildcard `*` matches within one path segment only, so `openai/*` cannot accidentally cover another provider; use `*/*` to cover everything.
62
+
63
+ Edits made in the Settings page are written back to `cordis.patch.yml` immediately and hot-reload through `dsh-hmr`.
64
+
65
+ ## Install
66
+
67
+ From the npm registry (recommended):
68
+
69
+ 1. Add the dependency to your DSH profile's `package.json` (`~/.dsh/profiles/desktop/package.json`):
70
+ ```json
71
+ "@naced/llm-multi-rate-limiter": "^0.1.2"
72
+ ```
73
+ then install it in the profile directory (`pnpm install`, or the DSH plugin manager's install action).
74
+ 2. Add `"@naced/llm-multi-rate-limiter"` to `dsh.profile.bundles`.
75
+ 3. Append the `cordis.patch.yml` row above, then restart DSH.
76
+
77
+ From a local checkout (development): point the dependency at the folder instead of a version —
78
+ `"@naced/llm-multi-rate-limiter": "file:./packages/llm-multi-rate-limiter"` — and keep the same `bundles` entry.
79
+
80
+ ## Security
81
+
82
+ This plugin intercepts the `llm/stream` waterfall, so it sits in front of every model call. What it actually does with that position:
83
+
84
+ - Reads only `options.provider`, `options.model` and `options.signal`. Prompts, messages, responses and credentials are never inspected, logged or transmitted.
85
+ - Makes no outbound network calls. The only HTTP surface is its own status channel (`/llm-multi-rate-limiter`), which is fenced by `connection.requestRejection()`, accepts `POST` + `application/json` only, and returns counters plus `provider/model` keys — no conversation content.
86
+ - Makes no filesystem or process calls (`fs`, `child_process`, `eval` are not imported); configuration is written through the host's settings service.
87
+ - Ships no install scripts.
88
+
89
+ If the limiter itself throws, the call is passed through untouched rather than failing, so a bug here cannot take the assistant offline.
90
+
91
+ ## Development
92
+
93
+ ```bash
94
+ git clone https://github.com/NacedWang/dsh-llm-multi-rate-limiter.git
95
+ cd dsh-llm-multi-rate-limiter
96
+ npm install
97
+ npm run check # syntax check + ESLint + node:test
98
+ npm pack --dry-run
99
+ ```
100
+
101
+ There is no build step: `lib/` is hand-written ESM plus a browser bundle, shipped as-is so the published artifact matches what runs.
102
+
103
+ ## Releasing
104
+
105
+ 1. Bump `version` and add a `CHANGELOG.md` entry.
106
+ 2. `git commit -am "chore: release vX.Y.Z" && git tag vX.Y.Z && git push --follow-tags`
107
+ 3. The [`publish`](.github/workflows/publish.yml) workflow publishes to npm via **Trusted Publishing (OIDC)** — no `NPM_TOKEN` secret exists — and creates the matching GitHub release.
108
+
109
+ Because publishing is tied to this repository, the workflow filename in the npm Trusted Publisher settings must stay `publish.yml`.
110
+
111
+ ## License
112
+
113
+ MIT. Based on `@leaf233/dsh-llm-rate-limiter`; see [LICENSE](LICENSE) for both copyright lines.
package/README.zh.md ADDED
@@ -0,0 +1,115 @@
1
+ # @naced/llm-multi-rate-limiter
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@naced/llm-multi-rate-limiter.svg)](https://www.npmjs.com/package/@naced/llm-multi-rate-limiter)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@naced/llm-multi-rate-limiter.svg)](https://www.npmjs.com/package/@naced/llm-multi-rate-limiter)
5
+ [![license](https://img.shields.io/npm/l/@naced/llm-multi-rate-limiter.svg)](LICENSE)
6
+ [![ci](https://github.com/NacedWang/dsh-llm-multi-rate-limiter/actions/workflows/ci.yml/badge.svg)](https://github.com/NacedWang/dsh-llm-multi-rate-limiter/actions/workflows/ci.yml)
7
+
8
+ 按 **provider/model** 限制 LLM 请求频率的 DeepSeek Harness 插件,带一个可编辑的「设置」页(Settings → 多模型限速)、侧边栏入口,以及实时状态面板。
9
+
10
+ 本包 fork 自 [`@leaf233/dsh-llm-rate-limiter`](https://github.com/Leafyezi233/dsh-llm-rate-limiter) 0.3.0(MIT,版权归上游作者),主要改动是把配置界面从「内置插件配置」卡片改为 `settings.section` 设置页 + 侧边栏入口,并移除了卡片入口。
11
+
12
+ ## 功能
13
+
14
+ - **两种限速策略**:令牌桶 `token-bucket`(允许突发)/滑动窗口 `sliding-window`(严格每分钟 N 次)。
15
+ - **三层配置**:`models`(精确或通配符 key)> `providers`(整个 provider 一个上限)> `defaults`。
16
+ - **通配符 key**:`openai/*` 覆盖该 provider 下所有没有单独配置的模型,`*` 只在单段内匹配、不跨 `/`。
17
+ - **按模型独立配置**:key 为 `provider/model`,例如 `openai/gpt-5.1`。
18
+ - **429 自适应降速**:provider 返回 429 时,该模型自动按 `factor` 降速并保持 `cooldownMs`,连续 `recoverAfter` 次成功后恢复。
19
+ - **排队或拒绝**:`onThrottled: queue` 时请求等待空位(受 `maxQueueWaitMs` 约束),`reject` 时立即返回错误。
20
+ - **实时面板**:每秒拉取 `/llm-multi-rate-limiter` 状态通道,显示各模型当前令牌/窗口占用、累计放行、平均等待,以及最近事件。
21
+ - **设置页**:模型搜索过滤、删除二次确认、整份配置的 JSON 导入/导出。
22
+
23
+ ## 配置
24
+
25
+ 配置写在 profile 的 `cordis.patch.yml`(`~/.dsh/profiles/desktop/cordis.patch.yml`):
26
+
27
+ ```yaml
28
+ - id: llm-multi-rate-limiter
29
+ name: "@naced/llm-multi-rate-limiter"
30
+ config:
31
+ enabled: true
32
+ strategy: sliding-window
33
+ defaults:
34
+ maxConcurrent: 5
35
+ maxRpm: 60
36
+ burstSize: 10
37
+ refillRate: 1
38
+ providers:
39
+ openai:
40
+ maxRpm: 30
41
+ maxConcurrent: 3
42
+ models:
43
+ openai/gpt-5.1:
44
+ maxRpm: 20
45
+ maxConcurrent: 2
46
+ "azure/*":
47
+ maxRpm: 40
48
+ cooldown:
49
+ enabled: true
50
+ cooldownMs: 30000
51
+ factor: 0.5
52
+ recoverAfter: 3
53
+ onThrottled: queue
54
+ maxQueueWaitMs: 240000
55
+ ```
56
+
57
+ - 行 `id` 同时是设置命名空间(`dsh-settings` 按 `entry.options.id` 建表单),改名需与 `cordis.patch.yml` 一致。
58
+ - 优先级:`models` 精确 key > `models` 里最具体的通配符 > `providers[<provider>]` > `defaults`。
59
+ - `models[<key>]` 只写需要覆盖的字段,其余逐层继承。
60
+ - 令牌桶下 `refillRate`(个/秒)若省略,会按 `maxRpm / 60` 推导。
61
+
62
+ 设置页里改任何字段都会立即写回 `cordis.patch.yml` 并热生效(`dsh-hmr` 监听 profile patch)。
63
+
64
+ ## 安装
65
+
66
+ 走 npm registry(推荐):
67
+
68
+ 1. 在 DSH profile 的 `package.json`(`~/.dsh/profiles/desktop/package.json`)里加依赖:
69
+ ```json
70
+ "@naced/llm-multi-rate-limiter": "^0.1.2"
71
+ ```
72
+ 然后在 profile 目录执行 `pnpm install`(或用 DSH 插件管理器的安装动作)。
73
+ 2. `dsh.profile.bundles` 列表中加入 `"@naced/llm-multi-rate-limiter"`。
74
+ 3. 在 `cordis.patch.yml` 加上面那段配置,重启 DSH。
75
+
76
+ 本地开发时把依赖改成指向目录:`"@naced/llm-multi-rate-limiter": "file:./packages/llm-multi-rate-limiter"`,`bundles` 一项保持不变。
77
+
78
+ ## 卸载
79
+
80
+ 删除 `cordis.patch.yml` 中该行与 `bundles` 里的包名,再删 `node_modules/@naced/llm-multi-rate-limiter` 即可。
81
+
82
+ ## 安全说明
83
+
84
+ 本插件拦截 `llm/stream` 瀑布,位于每一次模型调用之前。它到底用这个位置做了什么:
85
+
86
+ - 只读 `options.provider`、`options.model`、`options.signal`;不检查、不记录、不外发 prompt、messages、响应或凭据。
87
+ - 不发起任何对外网络请求。唯一的 HTTP 面是自带状态通道 `/llm-multi-rate-limiter`,由 `connection.requestRejection()` 加鉴权栅栏,只接受 `POST` + `application/json`,返回的是计数器与 `provider/model` 键,不含对话内容。
88
+ - 不使用 `fs`、`child_process`、`eval`;配置写入走宿主 settings 服务。
89
+ - 没有安装脚本。
90
+
91
+ 限速器自身抛错时会原样放行请求,而不是让调用失败——这里的 bug 不会把助手整体弄挂。
92
+
93
+ ## 开发
94
+
95
+ ```bash
96
+ git clone https://github.com/NacedWang/dsh-llm-multi-rate-limiter.git
97
+ cd dsh-llm-multi-rate-limiter
98
+ npm install
99
+ npm run check # 语法检查 + ESLint + node:test
100
+ npm pack --dry-run
101
+ ```
102
+
103
+ 没有编译步骤:`lib/` 是手写的 ESM 加一份浏览器 bundle,原样发布,所以线上跑的就是这份代码。
104
+
105
+ ## 发版
106
+
107
+ 1. 改 `version`,在 `CHANGELOG.md` 补一条。
108
+ 2. `git commit -am "chore: release vX.Y.Z" && git tag vX.Y.Z && git push --follow-tags`
109
+ 3. [`publish`](.github/workflows/publish.yml) 工作流通过 **Trusted Publishing (OIDC)** 发布到 npm —— 仓库里不存在 `NPM_TOKEN` 密钥 —— 并自动创建对应的 GitHub Release。
110
+
111
+ 因为发布权限绑定了本仓库,npm 的 Trusted Publisher 设置里工作流文件名必须保持 `publish.yml`。
112
+
113
+ ## License
114
+
115
+ MIT。基于 `@leaf233/dsh-llm-rate-limiter`,双版权行见 [LICENSE](LICENSE)。
@@ -0,0 +1,3 @@
1
+ - insert:
2
+ - id: llm-multi-rate-limiter
3
+ name: '@naced/llm-multi-rate-limiter'